@Transactional等效于C#和DDD应用程序服务的并发性

g18*_*18c 1 c# design-patterns domain-driven-design

我正在阅读Vaughn Vernon关于实现领域驱动设计的书.我也经历过他的github中书代码C#版本.

这本书的Java版本有装饰器@Transactional,我相信它来自spring框架.

public class ProductBacklogItemService
{
    @Transactional
    public void assignTeamMemberToTask(
        string aTenantId,
        string aBacklogItemId,
        string aTaskId,
        string aTeamMemberId)
        {
            BacklogItem backlogItem =
                backlogItemRepository.backlogItemOfId(
                    new TenantId(aTenantId),
                    new BacklogItemId(aBacklogItemId));

            Team ofTeam =
                teamRepository.teamOfId(
                    backlogItem.tennantId(),
                    backlogItem.teamId());

            backlogItem.assignTeamMemberToTask(
                new TeamMemberId(aTeamMemberId),
                ofTeam,
                new TaskId(aTaskId));
        }
}
Run Code Online (Sandbox Code Playgroud)

C#中的等效手动实现是什么?我正在考虑以下几点:

public class ProductBacklogItemService
{
    private static object lockForAssignTeamMemberToTask = new object();
    private static object lockForOtherAppService = new object();

    public voice AssignTeamMemberToTask(string aTenantId,
        string aBacklogItemId,
        string aTaskId,
        string aTeamMemberId)
        {
            lock(lockForAssignTeamMemberToTask)
            {
                // application code as before
            }
        }

        public voice OtherAppsService(string aTenantId)
        {
            lock(lockForOtherAppService)
            {
                // some other code
            }
        }
}
Run Code Online (Sandbox Code Playgroud)

这让我有以下问题:

  1. 我们是通过应用程序服务还是通过存储库锁定?即我们不应该这样做backlogItemRepository.lock()吗?
  2. 当我们读取多个存储库作为我们的应用程序服务的一部分时,我们如何在事务期间保护存储库之间的依赖关系(其中聚合根通过标识引用其他聚合根) - 我们是否需要在存储库之间具有互连锁?
  3. 是否有任何DDD基础架构框架可以处理任何此类锁定?

编辑

有两个有用的答案来使用事务,因为我没有选择我的持久层我正在使用内存存储库,这些是非常原始的我写了它们(他们没有事务支持,因为我不知道如何加!).

我将设计系统,因此我不需要同时对多个聚合根进行原子更改,但是我需要在多个存储库中一致地读取(即,如果从多个其他聚合引用BacklogItemId,如果BacklogItemId被删除,我们需要防止竞争条件).

那么,我可以通过使用锁来逃脱,还是需要在我的内存存储库中添加TransactionScope支持?

Nei*_*ell 5

TL; DR版本

你需要将你的代码包装成一个System.Transactions.TransactionScope.关于多线程btw要小心.

完整版本

所以聚合点就是定义一致性边界.这意味着任何更改都应该导致聚合的状态仍然遵循它的不变量.这不一定与交易相同.真实交易是一个贯穿各领域的实施细节,因此应该如此实施.

关于锁定的警告

不要做锁定.试着忘记你实现悲观锁定的任何想法.要构建可扩展的系统,您没有真正的选择.事实上,数据需要时间来从磁盘到屏幕,这意味着你有最终的一致性,所以你应该为此进行构建.你不能真正保护针对特定种族条件,这样,你只需要考虑它们可能发生,并能警示"丢失"的用户,他们的命令失败的事实.通常,您可以稍后开始查找这些问题(秒,分钟,小时,天,无论您的域专家告诉您SLA是什么),并告诉用户他们可以对此做些什么.

例如,想象一下,如果两名工资单职员与银行同时支付了员工的费用.他们会在以后找到书籍平衡的时候发现并采取一些补偿措施来纠正这种情况.您不希望将工资单部门缩减为一次工作的单个人,以避免这些(罕见)问题.

我的实施

我个人使用命令处理器样式,所以我的所有应用程序服务都实现为ICommandHandler<TCommand>.该CommandProcessor本身就是查找正确的处理程序,并要求它来处理该命令的事情.这意味着该CommandProcessor.Process(command)方法可以将其全部内容处理为System.Transactions.TransactionScope.

例:

public class CommandProcessor : ICommandProcessor
{
    public void Process(Command command)
    {
        using (var transaction = new TransactionScope())
        {
            var handler = LookupHandler(command);
            handler.Handle(command);

            transaction.Complete();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

你没有采用这种方法,所以为了使你的交易成为一个跨领域的问题,你需要将它们移动到堆栈中更高的水平.这高度依赖于您正在使用的技术(ASP.NET,WCF等),因此如果您添加更多细节,可能会有一个显而易见的地方放置这些东西.