【问题标题】:How to write functionality using DDD / CQRS如何使用 DDD / CQRS 编写功能
【发布时间】:2012-06-19 06:13:35
【问题描述】:

我有一个如下所列的银行账户域。可以有 SavingsAccount、LoanAccount、FixedAccount 等。一个用户可以有多个帐户。我需要添加一个新功能——获取用户的所有帐户。函数应该写在哪里,怎么写?

如果解决方案遵循 SOLID 原则(Open-Closed 原则,...)和 DDD,那就太好了。

欢迎任何可以使代码更好的重构。

注意:AccountManipulator 将由网站客户端通过网络服务使用。

namespace BankAccountBL
{
public class AccountManipulator
{
    //Whether it should beprivate or public?
    private IAccount acc;

    public AccountManipulator(int accountNumber)
    {
        acc = AccountFactory.GetAccount(accountNumber);
    }

    public void FreezeAccount()
    {
        acc.Freeze();
    }

}

public interface IAccount
{
    void Freeze();
}

public class AccountFactory
{
    public static IAccount GetAccount(int accountNumber)
    {
        return new SavingsAccount(accountNumber);
    }
}

public class SavingsAccount : IAccount
{
    public SavingsAccount(int accountNumber)
    {

    }

    public void Freeze()
    {

    }
}
}

阅读:

  1. When to use the CQRS design pattern?

  2. In domain-driven design, would it be a violation of DDD to put calls to other objects' repostiories in a domain object?

  3. Refactoring domain logic that accesses repositories in a legacy system

  4. Which of these examples represent correct use of DDD?

  5. Good Domain Driven Design samples

  6. Advantage of creating a generic repository vs. specific repository for each object?

【问题讨论】:

  • 聚合边界在哪里?
  • 扫描提议的代码,它看起来非常名词-沉重,就像大多数传统的OO代码一样。在他的 DDDD 草稿中,Greg Young 清楚地解释了为什么刻板的 OO 方法不适用于 DDD:abdullin.com/storage/uploads/2010/04/…
  • 一旦你开始从 CQRS 的角度考虑 DDD,你会经常发现域对象并不是真正的“系统的名词”,而是系统中发生的命令和事件.

标签: c# .net design-patterns domain-driven-design cqrs


【解决方案1】:

如果您的AccountManipulator 是您域的外观,我不会将帐号放入构造函数中。我会这样重构它:

public class AccountManipulator
{
    private  AccountFactory _factory;
    private UserRepository _users;

    public AccountManipulator(AccountFactory factory, UserRepository users)
    {
       _factory = factory;
       _users = users;
    }

    public void FreezeAccount(int accountNumber)
    {
       var acc = _factory.GetAccount(accountNumber);
       acc.Freeze();
    }

    public IEnumerable<IAccount> GetAccountsOf(User user) {
       return _users.GetAccountIds(user).Select(_factory.GetAccount);
    }
}

public interface UserRepository {
    IEnumerable<int> GetAccountIds(User user);
}

为了说明你的域是否为SOLID,你应该用原理来分析它:

  • 单一职责:每个对象都有自己的职责(并且只有一个):
    • AccountFactory:创建 IAccounts
    • SavingsAccount:实现 IAccount 读取/写入(数据库?Web 服务?)
    • AccountManipulator:提供一组与域对象相关的最小且简单的操作。
  • 打开/关闭:您的课程是否对扩展开放而对更改关闭?
    • AccountFactory:嗯,没有。如果您编写 IAccount 的新实现,为了使用它,您必须更改 AccountFactory。解决方案:抽象工厂
    • 储蓄账户?这取决于它是否会使用外部依赖项。需要更多代码来说明。
    • AccountManipulator:是的。如果您需要对域对象进行其他操作,您可以直接使用其他服务而无需更改 AccountManipulator。或者你可以继承它
  • Liskov 替换:你能用另一个实现替换任何类吗?需要更多代码才能说。您现在没有其他 IAccount 或 IAccountFactory 实现
  • 依赖倒置:
    • AccountManipulator 应该依赖于抽象:AccountFactory 和 UserRepository 应该是接口。

【讨论】:

  • 不要让 AccountManipulator 成为 Web 服务,而是让 Web 服务使用它。您应该将 Web 服务签名与外观分离。
  • Web 服务可以使用 DependencyInjection 容器来构建 AccountManipulator。 AccountManipulator 的客户端是 Web 服务。
  • 谢谢。您能否为“DependencyInjection container to build up AccountManipulator”提供一些好的和简单的文章参考?
  • 如果您的 Web 服务是使用 WCF 制作的,您可以使用 Castle 和 WCF 工具,您可以从这里开始阅读:stackoverflow.com/questions/10795365/…
【解决方案2】:

首先,要真正回答您的问题,了解为什么您需要获取所有用户帐户很重要?你是:

  1. 获取要在屏幕上显示的帐户列表供用户使用 然后对单个帐户执行命令/事务?
  2. 对所有用户帐户执行单个命令/事务 - 例如“冻结所有用户帐户”?

我问的原因是因为如果是后者,您只需要考虑 DDD 方面。如果这种“功能”的原因是前者(在阅读了您的问题后,我怀疑它是) - 我真的建议只创建一个瘦查询服务层来获取您需要的用户帐户数据屏幕。您不需要为此添加 DDD 的“限制”;不涉及交易或模型状态更改。提供此功能根本不需要涉及域模型。只需定义一些简单的 POCO DTO 并使用 Entity Framework 获取数据并将其传递回 UI。

这就是CQRS 的意义所在;您不需要存储库、工厂或聚合来为 UI 提供一个可供用户选择的帐户列表 - 您将过于复杂化并为自己制作A LOT更多工作。

如果存在 需要对所有用户帐户进行一次交易的场景,那么我会执行以下操作:

public class AccountService : IAccountService
{
    private IAccountRepository _accountRespository;

    public void FreezeAllAccountsForUser(Guid userId)
    {
        IEnumerable<IAccount> accounts = _accountRespository.GetAccountsByUserId(userId);

        using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create())
        {
            foreach (IAccount account in _accounts)
            {
                account.Freeze();
                _accountRespository.Save(account);
            }
        }
    }
}

其中 AccountService 是一个网络服务,即应用层。

总之,我的建议是:仅在需要事务命令上下文中考虑 DDD。用于获取数据列表;创建 UI 可以使用的简单查询服务。

附:我注意到您的问题和一些答案中滥用了工厂模式。工厂的设计目的是在给定特定数据的情况下提供对象的 CREATION 策略。不应该有调用数据库的“GetAccount(accountId)”方法;存储库调用数据库,然后将数据传递给工厂以创建对象。

【讨论】:

【解决方案3】:

首先,为什么需要 AccountManipulator?它绝对什么都不做,但会使代码更复杂。

至于获取一个用户的所有账户,这个方法最合乎逻辑的地方是在用户类中。您可以将帐户工厂传递给该方法,进一步的实现可能取决于您存储帐户的方式。

【讨论】:

    【解决方案4】:

    我会将“AccountFactory”重命名为AccountRepository,并在其中添加一个额外的方法GetAccountsForUser( int userId ),用于检索特定用户的所有帐户。

    如果AccountManipulator是一个webservice,那么这个类将使用AccountRepository,像这样:

    public class AccountManipulator
    {
    
       public void FreezeAccount( int accountNr )
       {
            var repository = new AccountRepository();
            var account = repository.GetAccount(accountNr);
            account.Freeze();
            repository.Save(account);        
       }
    
       public ICollection<Account> GetAccountsForUser( int userId )
       {
            var repository = new AccountRepository();
            return repository.GetAccountsForUser (userId);
       }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-08-12
      • 2019-03-01
      • 1970-01-01
      • 2014-10-21
      • 1970-01-01
      • 2011-01-31
      • 1970-01-01
      • 2011-05-12
      相关资源
      最近更新 更多