【问题标题】:@Transactional equivalent in C# and concurrency for DDD application services@C# 中的事务等效项和 DDD 应用程序服务的并发性
【发布时间】:2013-11-14 09:08:05
【问题描述】:

我正在阅读 Vaughn Vernon 关于实施领域驱动设计的书。我也经历过book code, C# version, from his github here

本书的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));
        }
}

在 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
            }
        }
}

这给我留下了以下问题:

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

编辑

使用事务有两个有用的答案,因为我没有选择持久层知道怎么加!)。

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

那么,我可以只使用锁,还是需要考虑在我的内存存储库中添加 TransactionScope 支持?

【问题讨论】:

    标签: c# design-patterns domain-driven-design


    【解决方案1】:

    TL;DR 版本

    您需要将代码包装在 System.Transactions.TransactionScope 中。顺便提一下多线程。

    完整版

    所以聚合的意义在于定义一致性边界。这意味着任何更改都应该导致聚合的状态仍然尊重它的不变量。这不一定与交易相同。真正的事务是一个跨领域的实现细节,所以可能应该这样实现。

    关于锁定的警告

    不要锁定。试着忘记你对实现悲观锁定的任何想法。要构建可扩展系统,您别无选择。数据需要时间来请求并从磁盘传输到屏幕这一事实意味着您具有最终的一致性,因此您应该为此构建。您不能真正保护免受竞争条件的影响,您只需要考虑它们可能发生的事实,并能够警告“失败”的用户他们的命令失败。通常,您可以稍后开始发现这些问题(几秒钟、几分钟、几小时、几天,无论您的领域专家告诉您 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();
            }
        }
    }
    

    您还没有采用这种方法,因此要使您的事务成为一个横切关注点,您需要将它们在堆栈中移到更高的级别。这高度依赖于您使用的技术(ASP.NET、WCF 等),因此如果您添加更多细节,可能会有明显的地方放置这些东西。

    【讨论】:

    • 感谢@Neil 真的很有用。目前我只是使用存储库,它们只是用于内存存储。我没有使用任何支持事务的底层存储技术,原因是我将最后编写持久性
    • TransactionScope 仍然是答案。如果您不使用 ACID 持久性,那么您将不会获得 ACID 属性,但 TransactionScope 应该在适当的位置(在正确的层中),以便当您拥有 ACID 存储时,它会满足您的需求。跨度>
    • 所以总结一下使用我的原始示例代码,其中backlogItemRepository.backlogItemOfId(...)teamRepository.teamOfId(...) 都采用 ID aTenantId,事务范围将负责并行操作,其中删除具有该 ID 的租户在事务进行期间,服务层操作之一会因异常而失败?
    【解决方案2】:

    锁定不允许在这些代码路径上任何并发。

    我认为您正在寻找 transaction scope

    【讨论】:

      【解决方案3】:

      我不知道你要使用什么持久层,但标准的如 ADO.NET、Entity Framework 等支持 TransactionScope 语义:

      using(var tr = new TransactionScope())
      {
          doStuff();
          tr.Complete();
      }
      

      如果调用 tr.Complete(),则提交事务。在任何其他情况下,它都会回滚。

      通常,聚合是事务一致性的单位。如果您需要将事务分散到多个聚合中,那么您可能应该重新考虑您的模型。

      【讨论】:

      • 感谢@Bartłomiej 的回复,这就是有趣的地方......我还没有选择持久层 - 我正在使用内存存储库。如果您能指出使我自己的内存存储库具有事务性的方向,我会非常感兴趣
      【解决方案4】:
      lock(lockForAssignTeamMemberToTask)
      {
          // application code as before
      }
      

      这负责同步。但是,如果出现任何异常,您还需要还原更改。因此,模式将类似于:

      lock(lockForAssignTeamMemberToTask)
      {
          try {
              // application code as before
          } catch (Exception e) {
              // rollback/restore previous values
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-11-01
        • 2011-09-29
        • 1970-01-01
        • 2012-05-06
        • 2019-08-23
        • 2011-02-06
        • 1970-01-01
        相关资源
        最近更新 更多