Code Monkey home page Code Monkey logo

Comments (8)

mattia-battiston avatar mattia-battiston commented on June 22, 2024 23

I'd expect the transaction management to be handled only in the database module. Ideally the use case knows nothing about transactions

from clean-architecture-example.

adr-h avatar adr-h commented on June 22, 2024 12

I'm chipping in as a guy who hasn't yet read Uncle Bob's book on Clean Architecture - but from much of what I've heard/read of it, it sounds a lot like a variation of a stack that's using a Service Layer (which are basically just a collection of "Use Cases") + Repository/Query patterns.

That being the case, the "traditional" solution to the above is the "Unit of Work" pattern (https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application). Basically, just a class that acts as a sort of "factory" for Repositories. Since the "Unit of Work" generates the Repositories, it can inject the same transaction context into every Repository that it creates.

In the context of the "Clean Architecture", if you have a Use Case that's using several Repositories at once, you can pass it a "Unit of Work". It will then generate the Repositories it needs, and carry along its merry way. If any of the dependent Repositories throw an error, your Use Case can simply call the "rollback" function on the Unit of Work, and it will handle the nitty-gritty of actually rolling everything back.

I've mostly ever seen it used in the context of .NET applications, and even in that environment, developer goodwill for it seems to be pretty rare these days.

And for good reason - the Unit of Work is a messy, hacky solution. For every Repository you introduce to the system, you will have to hack open your Unit of Work and insert a new method to generate a new Repository.

But even with its downsides, I've yet to find an actual good alternative that doesn't either:
a) reinvent the Unit of Work in some way
b) depend on some kind of framework magic (e.g: Entity Framework is particularly guilty of this)

If someone comes up with a good, clean solution alternative for Unit of Work, I'd love to hear about it. It seems like the internet has been battling over the concept for half a decade and hasn't came up with any good solutions beyond point "b" (relying on framework magic)

from clean-architecture-example.

daleholborow avatar daleholborow commented on June 22, 2024 4

I was referring to the article at: http://blog.cleancoder.com/uncle-bob/2016/01/04/ALittleArchitecture.html

So while the usecase doesn't know about the transaction as such, it does seem to explicitly call the begin/end functionality of the gateway that it is injected with.

**Specifically, in Bob's example, he has:

package businessRules;
import entities.Something;

public class BusinessRule {
private BusinessRuleGateway gateway;

public BusinessRule(BusinessRuleGateway gateway) {
this.gateway = gateway;
}

public void execute(String id) {
gateway.startTransaction();
Something thing = gateway.getSomething(id);
thing.makeChanges();
gateway.saveSomething(thing);
gateway.endTransaction();
}
}**

I am really hoping bob's upcoming new book has some good examples, because I am struggling to find "good" real-world-complexity-level examples of a clean architecture, especially around business rules/validation and where those query the db. I am settling on a "Specification Pattern" where the gateways will be in charge of being able to return all data that is required to populate obejcts, and also have methods to return whatever data is required to do business validations. (.e.g UpdateActiveAccountGateway might have methods BeginTransaction(), LoadIdsOfAccountsUserIsManagerOf(), LoadExistingAccount() (the return object of which is then patched with updated data), UpdateAccount(account), CommitTransaction(), and the UseCase would use a Specification validation something like EnsureIsAllowedToAdministerAccount(idsOfAdministerableAccounts, targetAccountId) to confirm that account being edited is in the collection of those that the current user is allowed to administer.

And the logic would be run something like:

void Handle (Account updatePayload)
{
gate.BeginTrans();
var existingAccount = gate.LoadExisting(updatePayload.AccountId);
var idsOfAdministerableAccounts = gate.LoadIdsOfAccountsUserIsManagerOf();

// now validate action is allowed
EnsureIsAllowedToAdministerAccount(idsOfAdministerableAccounts, existingAccount.AccountId);

// Update is allowed, patch values
existingAccount.PatchWith(updatePayload);

gate.UpdateAccount(existingAccount);

gate.CommitTrans()
}

from clean-architecture-example.

mattia-battiston avatar mattia-battiston commented on June 22, 2024 1

@VicBell In most cases I have been able to simply put the @transactional annotation on the method in the class in the database data provider, and that was enough for me. The useCase know nothing about it.

If you need the transaction to span multiple dataProvider calls (e.g. if you need it around the whole useCase) you can either:

  • do something like what @daleholborow says, and at the beginning of the useCase call dataProvider.startTransaction(). The problem with this is that this only makes sense when the dataProvider is a database implementation...and ideally you don't want to know which dataProvider implementation you're using in the use case
  • compromise and put @transactional around the whole use case. The problem is that you're bringing in a dependency on Spring, but at the end of the day you know your software better than anyone else, so if you think the value you're getting justifies the coupling to Spring then go for it

Hope this helps
Mattia

from clean-architecture-example.

hieven avatar hieven commented on June 22, 2024 1

Sorry for bringing up this topic again.

Imagine there is a use case for user sign up. You need to insert data to both user table and email table.
Will you do it in use case or in data provider?

If it's in use case, then you expose the transaction to the use case layer

If it's in data provider, then you expose the domain knowledge to external dependency (like why a data provider needs provide a method for inserting A and B in a transaction? ideally it should only provides basic CRUD methods communicating to the database and let the use case decide how to use the data provider)

I am currently facing this kind of situation and hope I can get some answers from all of you
Thank you

from clean-architecture-example.

daleholborow avatar daleholborow commented on June 22, 2024

I believe uncle Bob states in his essays that the gateways should contain the start/end transaction methods, which are invoked by the interactor/use case they are contained in. I haven't yet decided if it's best to try to wrap calls across multiple gateways, into a unit of work pattern, or use a more basic approach (eg have one interactor have one gateway, that implements all method required for the use case, rather than have several gateways based around aggregate root domain entities

from clean-architecture-example.

blling avatar blling commented on June 22, 2024

I must manage the transiaction manully instead of use @transactional on a usecase method? it brings a lot of boilerplate work...

from clean-architecture-example.

blling avatar blling commented on June 22, 2024

@mattia-battiston If you could give some more complex example in this git library, that will be great:)

Thanks all!

from clean-architecture-example.

Related Issues (10)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.