Architecture 如何在干净的体系结构中使用事务?

Architecture 如何在干净的体系结构中使用事务?,architecture,clean-architecture,Architecture,Clean Architecture,我在网上找不到它的任何实现,实际上都没有给你提供一种与框架无关的、实用的实现方法 我已经看到了一些关于解决这个问题的低劣建议: 使存储库方法原子化 使用例原子化 两者都不理想 案例#1:大多数用例依赖于不止一个存储库方法来完成工作。当您“下订单”时,您可能需要调用“订单存储库”的“插入”方法和“用户存储库”的“更新”方法(例如:扣减门店积分)。如果“插入”和“更新”是原子的,这将是灾难性的——您可以下订单,但实际上无法让用户为此付费。或者让用户付费,但订单失败。两者都不理想 案例2:。如果每个

我在网上找不到它的任何实现,实际上都没有给你提供一种与框架无关的、实用的实现方法

我已经看到了一些关于解决这个问题的低劣建议:

  • 使存储库方法原子化

  • 使用例原子化

  • 两者都不理想

    案例#1:大多数用例依赖于不止一个存储库方法来完成工作。当您“下订单”时,您可能需要调用“订单存储库”的“插入”方法和“用户存储库”的“更新”方法(例如:扣减门店积分)。如果“插入”和“更新”是原子的,这将是灾难性的——您可以下订单,但实际上无法让用户为此付费。或者让用户付费,但订单失败。两者都不理想


    案例2:。如果每个用例都位于一个思洛存储器中,那么它就可以工作,但是除非您想复制代码,否则您经常会发现自己的用例依赖于其他用例的操作

    假设您有一个“下订单”用例和一个“给予奖励点数”用例。两个用例都可以独立使用。例如,在您的系统发布周年纪念日登录时,老板可能希望给系统中的每个用户“奖励分数”。当用户进行购买时,您当然会使用“下订单”用例

    现在,您的系统发布10周年纪念日即将到来。你的老板决定——“好吧,Jimbo——2018年7月,无论何时有人下订单,我都要给点奖励”

    为了避免直接改变这种一次性想法的“下单”用例,这种想法可能会在明年被放弃,您决定创建另一个用例(“促销期间下单”),该用例仅称为“下单”和“给予奖励点数”。太好了

    只有。。。你不能。我是说,你可以。但你又回到了原点。您可以保证“下订单”是否成功,因为它是原子的。你可以保证如果“给予奖励积分”成功的原因相同。但是如果其中一个失败了,你就不能支持另一个。它们不共享相同的事务上下文(因为它们在内部“开始”和“提交”/“回滚”事务)



    对于上述场景,有几种可能的解决方案,但没有一种是非常“干净的”(想到工作单元——在用例之间共享一个工作单元可以解决这个问题,但UoW是一种丑陋的模式,而且仍然存在知道哪个用例负责打开/提交/回滚事务的问题).

    我将事务放在控制器上。控制器知道更大的框架,因为它可能至少有类似于元数据的框架注释

    至于工作单位,这是个好主意。您可以让每个用例启动一个事务。在内部,工作单元启动实际事务或增加调用的启动计数器。然后,每个用例将调用commit或reject。当提交计数等于0时,调用实际提交。拒绝跳过所有这些,回滚,然后出错(异常或返回代码)

    在您的示例中,包装用例调用start(c=1)、placeorder调用start(c=2)、placeorder提交(c=1)、bonus调用start(c=2)、bonus调用commit(c=1)、包装提交(c=0)以便实际提交


    我将子事务留给您

    通常,建议将事务定义放在用例层中,因为它具有propper级别的抽象和并发需求。 在我看来,最好的解决方案是你在案例2中暴露的方案。为了解决重用不同用例的问题,一些框架在事务中使用了概念传播。例如,在Spring中,您可以定义需要或需要新的事务


    如果您在UseCase PlaceOrderAndGiveAwards中定义了Transactional REQUIRED,则该事务将在“基本”用例中重用,而内部方法中的回滚将使整个事务回滚

    我知道这是一个老问题, 但希望这个答案能帮助寻找示例实现的人 由于清洁拱门,所有层均指向内部而非外部

    因此,由于事务是应用程序层关注的问题,因此我会将其保留在应用程序层中

    在快速示例中,我使应用程序层具有IDBContext接口 这是我将使用的所有数据库集 如下

    public interface IDBContext
    {
        DbSet<Blog> Blogs { get; set; }
        DbSet<Post> Posts{ get; set; }
        Task<int> SaveChangesAsync(CancellationToken cancellationToken);
        DatabaseFacade datbase { get; }
    }
    
    下面是我使用事务的命令处理程序

    await using var transaction = await context.datbase.BeginTransactionAsync();
    
    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;
    
    
        }
    
    公共异步任务句柄(TransactionCommand请求、CancellationToken CancellationToken)
    {
    int=0;
    wait using var transaction=wait context.datbase.BeginTransactionAsync();
    尝试
    {
    var blog=newcore.Entities.blog{Url=$“只需测试发送的编号={request.number}”;
    wait context.Blogs.AddAsync(blog);
    wait context.saveChangesSync(cancellationToken);
    对于(int i=0;i<10;i++)
    {
    var post=新的Core.Entities.post
    {
    BlogId=blog.BlogId,
    Title=$“{blog.Url}的Title{i}”
    };
    wait context.Posts.AddAsync(post);
    wait context.saveChangesSync(cancellationToken);
    更新++;
    }
    var divresult=5/request.number;
    wait transaction.CommitAsync();
    }
    捕获(例外情况除外)
    {
    var msg=例如消息;
    返回0;
    }
    更新的回报;
    }
    
    下面是我刚刚创建的示例的示例,用于详细解释我的答案

    请记住,我在15分钟内完成了这个例子,就像t
    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;
    
    
        }