如何使用DDD / CQRS编写function

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

如果解决方案遵循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. 何时使用CQRS设计模式?

  2. 在域驱动设计中,在域对象中调用其他对象的repostiories会违反DDD吗?

  3. 重构访问遗留系统中存储库的域逻辑

  4. 以下哪些例子代表DDD的正确使用?

  5. 良好的领域驱动设计样本

  6. 为每个对象创建通用存储库与特定存储库的优势?

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

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

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

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

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

 public class AccountService : IAccountService { private IAccountRepository _accountRespository; public void FreezeAllAccountsForUser(Guid userId) { IEnumerable accounts = _accountRespository.GetAccountsByUserId(userId); using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create()) { foreach (IAccount account in _accounts) { account.Freeze(); _accountRespository.Save(account); } } } } 

其中AccountService是Web服务,即应用程序层。

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

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

如果您的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 GetAccountsOf(User user) { return _users.GetAccountIds(user).Select(_factory.GetAccount); } } public interface UserRepository { IEnumerable GetAccountIds(User user); } 

为了说明您的域名是否为SOLID,您应该使用以下原则进行分析:

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

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

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

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

如果AccountManipulator是一个Web服务,那么这个类将使用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 GetAccountsForUser( int userId ) { var repository = new AccountRepository(); return repository.GetAccountsForUser (userId); } }