Multiple Service Layers and Database Transactions
-
06-07-2019 - |
Question
I'm just wondering how to best handle transactions across multiple service layers. The service layers use an ORM to store and retrieve from the database. Should the transactions be known and handled within the individual service layers? Or should they be handled by another layer?
For example: I have two service layers for users and clients. I would like to:
1) Create and save a new client
2) Create and save a new user
3) Assign that user to the client
All within a single transaction.
A simple example might look like this:
$userManagementService = new UserManagementService;
$newUserData = array(...);
$newUser = $userManagementService->create($newUserData);
$clientManagementService = new ClientManagementService;
$newClientData = array(...);
$newClient = $clientManagementService->create($newClientData);
$userManagementService->assignUserToClient($newUser, $newClient);
Where should transaction logic go?
Solution
Do not try to do nested transactions within service layers or within the ORM.
Transactions are global to the DB connection. Unless your RDBMS supports nested transactions natively and your DB API exposes nested transactions, you can run into anomalies.
For details, see my answer to How do detect that transaction has already been started?
Since you're using PHP, the scope of your transactions is at most a single request. So you should just use container-managed transactions, not service-layer transa. That is, start the transaction at the start of handling the request, and commit (or rollback) as you finish handling the request.
If an exception requiring a rollback occurs deep within nested ORM actions, then bubble that up by using an Exception, and let the container (i.e. your PHP action controller) take care of it.
OTHER TIPS
Are you facing an aggregation of transactions? Does this pseudo code match what I think you're saying?
try
begin application transaction
begin ORM transaction 1
create new user
commit request
begin ORM transaction 2
create new client
commit request
begin ORM transaction 3
create user client association
commit request
commit application tx
catch()
abort ORM tx 3
abort ORM tx 2
abort ORM tx 1
abort app tx
At any point a rollback of a nested transaction will likely throw an exception, and those exceptions would logically roll back all the nested transactions in the two-phase commit.
I might not be getting what you're after tho.