49

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).

aetheus
  • 671
  • 1
  • 7
  • 13

3 Answers3

8

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.

Virmundi
  • 2,497
  • 3
  • 25
  • 34
  • I think I'm leaning towards your first idea, since it would make it lighter on the use cases (have transactions be handled externally from the Use Cases - the controller or another layer perhaps being responsible for creating Repositories that share a transaction, passing these to Use Cases, and being responsible for rolling them back if any Use Case fails). That second idea of yours is intriguing, though, and one that I had not considered. Either way, thanks for your responses! – aetheus Jun 16 '18 at 00:56
  • Putting it on the controller level is a _really_ good point that I hadn't yet considered. It does unfortunately seem to suggest that you can't describe the sequence of steps in a transaction on the application level, which would be an issue if more than one application consumes your application project (e.g. web api and wpf) - or am I missing something? – Flater Jul 13 '20 at 08:29
  • The big problem is imho that putting the transaction on the controller does not solve the fundamental issue with hexagonal architecture: you can and must not assume that the Repository is even capable of participating in the transaction. How do you roll back a write to the filesystem or push into a message queue? – winson Oct 18 '22 at 15:43
  • @winson you can use UnitOfWork behind the scenes. Each repository can register a rollback function into the unit of work. If the unit of work fails, it will invoke each rollback function. Now is this perfect, no (or at least not easily), but it is doable. Rollback, however is not part of the Domain alyer. It's a detail from the dependency injection system used. When I've faced this I have no singleton repos. Every request gets a full repo suite. This allows each to register and respond to the unit of work: the request. Also works in WebSockets since each request gets a full repo suite. – Virmundi Oct 22 '22 at 12:02
  • Thanks for you response but the problem remains: the implementation of repository could be a webservice since it is supposed to be a complete abstraction. Now while you could try to undo the operation via a second call there is no guarantee or anything to leave a consistent state behind. Putting transactional in the controller is essentially hoping that the repositories play nice together. – winson Oct 24 '22 at 10:24
  • Two phase commits are a very real problem that’s orthogonal to the controller as the start of the transaction. They get worse over HTTP. However one can learn more about them here. https://en.m.wikipedia.org/wiki/Two-phase_commit_protocol – Virmundi Oct 25 '22 at 11:05
3

Generally, it is recommended to place the transactions definition in the UseCase layer because it has the propper level of abstraction and has the concurrency requirements. In my opinion the best solution is the one you expose in Case#2. To solve the issue of reusing different UseCases, some frameworks use the concept propagation in Transactions. For instance, in Spring, you can define the Transactions to be REQUIRED or REQUIRES_NEW.

https://www.byteslounge.com/tutorials/spring-transaction-propagation-tutorial

If you define Transactional REQUIRED in the UseCase PlaceOrderAndGiveAwards, the transaction is reused in the 'base' usecases and a rollback in the inner methods will make the hole transaction to rollback

Alvaro Arranz
  • 444
  • 2
  • 5
  • 7
    This recommendation goes against the principles of the Clean Architecture. The use case should be agnostic of the technology to persist data. – Orposuser Nov 27 '19 at 23:14
  • 2
    You are right, no external dependencies should be included in the UseCase layer. But using a transaction in your UseCase layer doesn't mean to use a concrete transaction implementation. A transaction is an abstraction about the atomiticity of your persistence layer. You could inyect different transaction implementations depending on your persistence implementation, both in the infrastructure layer. – Alvaro Arranz Nov 28 '19 at 10:16
  • certainly however then you would be mixing concerns if you do it in the same class. one way to properly work this is by using the proxy pattern so the proxy does the txn management and calls the 'real' use case – Orposuser Dec 06 '19 at 05:11
  • Agree! separating concerns with the proxy pattern is a good way to go. However, notice that the problem stated in the question continues to be the same: if a usecase is proxied with transaction management, then all other usecases that use that usecase will have that specific transaction management when it is used. You have to figure out how to use the same transaction when a usecase calls another usecase and rollback the whole usecase when the inner fails. – Alvaro Arranz Dec 11 '19 at 09:21
1

I know this is old question, but hopefully this answer will help someone looking for sample implementation Since Clean Arch all layers pointing inward not outward

All Layers pointing inward

So since the transaction is a concern of the application layer I would keep it in the application layer.

In the quick example I made the application layer has the interface IDBContext that has all my Dbsets I will use as following

public interface IDBContext
{
    DbSet<Blog> Blogs { get; set; }
    DbSet<Post> Posts{ get; set; }
    Task<int> SaveChangesAsync(CancellationToken cancellationToken);
    DatabaseFacade datbase { get; }
}

and in persistence layer I have the implementation of that interface

public class ApplicationDbContext : DbContext, IDBContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) :base(options)
    {

    }
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
    public DatabaseFacade datbase => Database;


    public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
    {
        
        var result = await base.SaveChangesAsync(cancellationToken);
        return result;
    }
}

Back to my application layer which I normally use IMediator and follow CQRM so I made this example which I hope it might be helpful here is the line where I start the transaction

await using var transaction = await context.datbase.BeginTransactionAsync();

here is the command handler where I am using transaction

public async Task<int> Handle(TransactionCommand request, CancellationToken cancellationToken)
    {
        int updated = 0;
        await using var transaction = await context.datbase.BeginTransactionAsync();
        try
        {
            var blog = new Core.Entities.Blog { Url = $"Just test the number sent = {request.number}" };
            await context.Blogs.AddAsync(blog);
            await context.SaveChangesAsync(cancellationToken);

            for (int i = 0; i < 10; i++)
            {
                var post = new Core.Entities.Post
                {
                    BlogId = blog.BlogId,
                    Title = $" Title {i} for {blog.Url}"
                };
                await context.Posts.AddAsync(post); 
                await context.SaveChangesAsync(cancellationToken);
                updated++;
            }

            var divresult = 5 / request.number;
            await transaction.CommitAsync();
            
        }
        catch (Exception ex)
        {
            var msg = ex.Message;
            return 0;
            
        }
        return updated;


    }

here is the Link for the sample I just created to explain my answer in details

please Keep in mind I did this example in like 15 min just as something to refer to in case there are some bad naming :)

Regards,

khaled Dehia
  • 821
  • 7
  • 13
  • 3
    This is simple and is working, but according to the principles of Clean Architecture shouldn't inner layers depend on abstractions? You defined IDbContext interface but in my opinion it's not abstraction at all as it's clearly leaking Entity Framework concepts. If now you would like to switch to NHibernate or even worse - some kind of NoSQL database, you would have to modify your handler which is part of the Core of your application. – kamilz Aug 28 '21 at 22:00
  • @kamilz totally agree but the same argument could be made about DbSet ? If you change the EF to something else, you should change all your DbSets – khaled Dehia Oct 29 '21 at 15:04
  • 3
    Sure. That's why in the pure Clean Architecture repositories should only operate on domain concepts. Neither DbContext nor DbSet are domain concept so they shouldn't be part of a repository interface, if we're thinking about Clean Architecture. But I know in real world it's not always worth to abstract everything. – kamilz Nov 02 '21 at 20:38