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