如何使用DDD / CQRS编写功能

时间:2012-06-19 06:13:36

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

我有一个银行帐户域名,如下所示。可以有SavingsAccount,LoanAccount,FixedAccount等。一个用户可以拥有多个帐户。我需要添加一个新功能 - 为用户获取所有帐户。编写函数应该在哪里以及如何编写?

如果解决方案遵循SOLID原则(开放原则,......)和DDD,那将是很好的。

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

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

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?

4 个答案:

答案 0 :(得分:4)

如果您的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:读取/写入(数据库?Web服务?)的IAccount的实现
    • AccountManipulator:提供与​​域对象相关的最小且简单的操作集。
  • 打开/关闭:您的课程是否对扩展程序开放并且对更改无效?
    • AccountFactory:嗯,不。如果您编写IAccount的新实现,为了使用它,您必须更改 AccountFactory。解决方案:抽象工厂
    • SavingsAccount?这取决于它是否会使用外部依赖项。需要更多代码来说明。
    • AccountManipulator:是的。如果您需要对域对象执行其他操作,则可以直接使用其他服务而无需更改AccountManipulator。或者你可以继承它
  • Liskov替换:你可以用其他实现替换任何类吗?需要更多代码来说明。您现在没有其他IAccount或IAccountFactory实现
  • 依赖倒置:
    • AccountManipulator应该依赖于抽象:AccountFactory和UserRepository应该是接口。

答案 1 :(得分:3)

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

  1. 获取要在用户显示的屏幕上显示的帐户列表 然后针对单个帐户执行命令/交易?
  2. 对所有用户帐户执行单个命令/事务 - 例如“冻结所有用户帐户”?
  3. 我问的原因是因为如果是后者,你只需要考虑DDD方面。如果这个“功能”的原因是前者(并且在阅读了我怀疑它的问题之后) - 我真的建议只创建一个瘦查询服务层来获取用户所需的帐户数据屏幕。您不需要为此添加DDD的“限制”;没有涉及的交易或模型状态变化。提供此功能根本不必涉及域模型。只需定义一些简单的POCO DTO并使用Entity Framework获取数据并将其传递回UI。

    这是CQRS的意思;您不需要存储库,工厂或聚合来为UI提供一个帐户列表供用户选择 - 您可能会使其复杂化并使很多为您自己做更多工作。

    如果 某个场景需要单个交易超过用户帐户的所有,那么我会执行以下操作:

    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是Web服务,即应用程序层。

    总之,我的建议是:只考虑需要交易命令环境中的DDD。用于获取数据列表;创建一个UI可以使用的简单查询服务。

    P.S。我注意到你的问题和一些答案中滥用了Factory模式。工厂旨在根据特定数据提供对象的CREATION策略。不应该有一个调用数据库的'GetAccount(accountId)'方法;存储库调用数据库,然后将数据传递给工厂以创建对象。

答案 2 :(得分:1)

首先为什么需要AccountManipulator?它绝对没有,但使代码更复杂。

至于获取用户的所有帐户,放置此方法的最合理的位置将在User类中。您可以将帐户工厂传递给该方法,进一步的实施可能取决于您存储帐户的方式。

答案 3 :(得分:0)

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

如果AccountManipulator是网络服务,那么此课程将使用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);
   }

}