Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you use transactions in the Clean Architecture?

Tags:

No implementations of it that I can find online actually give you a framework agnostic and practical way of implementing it.

I've seen several subpar suggestions towards solving it:

  1. make Repository methods atomic

  2. make Use Cases atomic

Neither of them are ideal.

Case #1: most Use Cases depend on more than a single Repository method to get their job done. When you're "placing an order", you may have to call the "Insert" methods of the "Order Repository" and the "Update" method of the "User Repository" (e.g: to deduct store credit). If "Insert" and "Update" were atomic, this would be disastrous - you could place an Order, but fail to actually make the User pay for it. Or make the User pay for it, but fail the Order. Neither are ideal.


Case #2: is no better. It works if each Use Case lives in a silo, but unless you want to duplicate code, you'll often find yourself having Use Cases that depend on the operation of other Use Cases.

Imagine you have a "Place Order" use case and a "Give Reward Points" use case. Both use cases can be used independently. For instance, the boss might want to "Give Reward Points" to every user in the system when they login during your system's anniversary of its launch day. And you'd of course use the "Place Order" use case whenever the user makes a purchase.

Now the 10th anniversary of your system's launch rolls by. Your boss decides - "Alright Jimbo - for the month of July 2018, whenever someone Places an Order, I want to Give Reward Points".

To avoid having to directly mutate the "Place Order" use case for this one-off idea that will probably be abandoned by next year, you decide that you'll create another use case ("Place Order During Promotion") that just calls "Place Order" and "Give Reward Points". Wonderful.

Only ... you can't. I mean, you can. But you're back to square one. You can guarantee if "Place Order" succeeded since it was atomic. And you can guarantee if "Give Reward Points" succeeded for the same reason. But if either one fails, you cannot role back the other. They don't share the same transaction context (since they internally "begin" and "commit"/"rollback" transactions).


There are a few possible solutions to the scenarios above, but none of them are very "clean" (Unit of Work comes to mind - sharing a Unit of Work between Use Cases would solve this, but UoW is an ugly pattern, and there's still the question of knowing which Use Case is responsible for opening/committing/rolling back transactions).

like image 578
aetheus Avatar asked Jun 15 '18 07:06

aetheus


People also ask

What are the four layers of clean architecture?

Clean architecture vs. The logical layers of this style are as follows: Presentation layer ( accounts for the presentation to the user) Business logic layer (contains the business rules) Data access layer (processes the communication with the database)

What is the purpose of clean architecture?

Clean architecture is a software design philosophy that separates the elements of a design into ring levels. An important goal of clean architecture is to provide developers with a way to organize code in such a way that it encapsulates the business logic but keeps it separate from the delivery mechanism.

How many layers are there in clean architecture?

The layers are the main core of a clean architecture. In our app, we will use three layers: presentation, domain, and model. Each layer should be separated and shouldn't need to know about other layers.

What is clean architecture pattern?

What is clean architecture? Clean architecture is a category of software design pattern for software architecture that follows the concepts of clean code and implements SOLID principles.


1 Answers

I put the transaction on the controllers. The controller knows about the larger framework since it probably has at least metadata like annotations of the framework.

As to the unit of work, it’s a good idea. You can have each use case start a transaction. Internally the unit of work either starts the actual transaction or increases a counter of invoked starts. Each use case would then call commit or reject. When the commit count equals 0, invoke the actual commit. Reject skips all of that, rolls back, then errors out (exception or return code).

In your example the wrapping use case calls start (c=1), the place order calls start(c=2), place order commits (c=1), bonus calls start (c=2), bonus calls commit (c=1), wrapping commits (c=0) so actually commit.

I leave subtransactions to you.

like image 104
Virmundi Avatar answered Oct 01 '22 10:10

Virmundi