How to write functionality using DDD / CQRS

I have a bank account domain listed below. May be SavingsAccount, LoanAccount, FixedAccount and so on. One user can have multiple accounts. I need to add new functionality - get all user accounts. Where should the function be written and how?

It would be great if the solution follows the principles of SOLID (Open-Closed principle, ...) and DDD.

Any refactoring that makes the code better is welcome.

Note. AccountManipulator will be used by the website client through the web service.

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() { } } } 

READING:

+6
source share
4 answers

First, to really answer your question, it is important to know why you need to get all user accounts? You:

  • Getting a list of accounts to display on the screen for the user. then execute a command / transaction on a single account?
  • Executing a single command / transaction for all user accounts - for example, "Block all user accounts"?

The reason I'm asking for is because you only need to consider the DDD aspect if it is the latter. If the reason for this "functionality" is the first (and after reading your question, I suspect that it is) - I really recommend just creating a thin layer of a query service that receives the user account information needed for the screen. You do not need to add DDD "restrictions" for this; No transactions or model state changes. Providing this functionality does not have to include a domain model . Simply define a simple POCO DTO and use the Entity Framework to retrieve data and transfer it back to the user interface.

This is what CQRS is about; you donโ€™t need repositories, factories, or aggregates to give the user interface a list of user accounts to choose from - you would complicate it and make A LOT more for yourself.

If there is a scenario requiring a single transaction across all user accounts, I would do something like:

 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); } } } } 

Where AccountService is a web service, i.e. an application tier.

In conclusion, my advice is: consider only DDD in the context of commands for which transactions are required. To select data lists; create a simple query service that the user interface can use.

PS I noticed the misuse of the Factory pattern in your question and some answers. Factories are intended to provide a strategy for CREATING an object, taking into account specific data. There should not be a GetAccount (accountId) method that calls the database; repositories invoke the database and then pass the data to Factory to create the object.

+3
source

if your AccountManipulator is the facade of your domain, I would not put the account number in the constructor. I would reorganize it like this:

 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); } 

To indicate whether your domain is SOLID, you should analyze it using the principle:

  • Single responsibility: each object has its own responsibility (and only one):
    • AccountFactory: creates IAccounts
    • SavingsAccount: IAccount implementation that reads / writes (database? Web service?)
    • AccountManipulator: provide a minimal and simple set of operations with domain objects.
  • Open / Closed: are you classes open for extensions and closed for changes?
    • AccountFactory: well, no. If you are writing a new IAccount implementation, you must change the AccountFactory to use it. Summary: factory
    • SavingsAccount? It depends on the use of external dependencies. Need more code to say.
    • AccountManipulator: yes. If you need to perform another operation with domain objects, you can directly use other services without changing the AccountManipulator. Or you can inherit from it.
  • Liskov substitution: can you replace any class with another implementation? Need more code to say. You now have no other implementations of IAccount or IAccountFactory.
  • Dependency Inversion:
    • AccountManipulator should depend on abstractions: AccountFactory and UserRepository should be interfaces.
+4
source

First of all, why do you need an AccountManipulator? It does absolutely nothing, but makes the code more complex.

As for getting all user accounts, the most logical place to host this method would be the User class. You can pass the factory account to this method, further implementation will probably depend on how you store the accounts.

+1
source

I would rename "AccountFactory" to AccountRepository and add an additional GetAccountsForUser( int userId ) method to it that retrieves all the accounts for a specific user.

If AccountManipulator is a web service, then this class will use AccountRepository , for example:

 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); } } 
0
source

Source: https://habr.com/ru/post/918434/


All Articles