DDD approach to access external information

I have existing classes of banking applications, as shown below. Bank account can be SavingsBankAccount or FixedBankAccount. The operation is called IssueLumpSumInterest. For FixedBankAccount, the balance needs to be updated only if the account holder does not have another account.

This requires that the FixedBankAccount object be aware of the other accounts of the account holder. How to do this, following the template SOLID / DDD / GRASP / Information Expert?

namespace ApplicationServiceForBank { public class BankAccountService { RepositoryLayer.IRepository<RepositoryLayer.BankAccount> accountRepository; ApplicationServiceForBank.IBankAccountFactory bankFactory; public BankAccountService(RepositoryLayer.IRepository<RepositoryLayer.BankAccount> repo, IBankAccountFactory bankFact) { accountRepository = repo; bankFactory = bankFact; } public void IssueLumpSumInterest(int acccountID) { RepositoryLayer.BankAccount oneOfRepositroyAccounts = accountRepository.FindByID(p => p.BankAccountID == acccountID); int ownerID = (int) oneOfRepositroyAccounts.AccountOwnerID; IEnumerable<RepositoryLayer.BankAccount> accountsForUser = accountRepository.FindAll(p => p.BankUser.UserID == ownerID); DomainObjectsForBank.IBankAccount domainBankAccountObj = bankFactory.CreateAccount(oneOfRepositroyAccounts); if (domainBankAccountObj != null) { domainBankAccountObj.BankAccountID = oneOfRepositroyAccounts.BankAccountID; domainBankAccountObj.AddInterest(); this.accountRepository.UpdateChangesByAttach(oneOfRepositroyAccounts); //oneOfRepositroyAccounts.Balance = domainBankAccountObj.Balance; this.accountRepository.SubmitChanges(); } } } public interface IBankAccountFactory { DomainObjectsForBank.IBankAccount CreateAccount(RepositoryLayer.BankAccount repositroyAccount); } public class MySimpleBankAccountFactory : IBankAccountFactory { public DomainObjectsForBank.IBankAccount CreateAccount(RepositoryLayer.BankAccount repositroyAccount) { DomainObjectsForBank.IBankAccount acc = null; if (String.Equals(repositroyAccount.AccountType, "Fixed")) { acc = new DomainObjectsForBank.FixedBankAccount(); } if (String.Equals(repositroyAccount.AccountType, "Savings")) { //acc = new DomainObjectsForBank.SavingsBankAccount(); } return acc; } } } namespace DomainObjectsForBank { public interface IBankAccount { int BankAccountID { get; set; } double Balance { get; set; } string AccountStatus { get; set; } void FreezeAccount(); void AddInterest(); } public class FixedBankAccount : IBankAccount { public int BankAccountID { get; set; } public string AccountStatus { get; set; } public double Balance { get; set; } public void FreezeAccount() { AccountStatus = "Frozen"; } public void AddInterest() { //TO DO: Balance need to be updated only if the person has no other accounts. Balance = Balance + (Balance * 0.1); } } } 

READING

+1
source share
4 answers

The first thing I noticed was the misuse of the factory bank account. factory, like you, should be used by the repository to create an instance based on data received from the data warehouse. Thus, your call to accountRepository.FindByID will return a FixedBankAccount or SavingsBankAccount object depending on the type of AccountType returned from the data store.

If interest applies only to instances of FixedBankAccount, you can perform a type check to make sure that you are working with the correct account type.

 public void IssueLumpSumInterest(int accountId) { var account = _accountRepository.FindById(accountId) as FixedBankAccount; if (account == null) { throw new InvalidOperationException("Cannot add interest to Savings account."); } var ownerId = account.OwnerId; if (_accountRepository.Any(a => (a.BankUser.UserId == ownerId) && (a.AccountId != accountId))) { throw new InvalidOperationException("Cannot add interest when user own multiple accounts."); } account.AddInterest(); // Persist the changes } 

NOTE. FindById should only accept an ID parameter, not a lambda / Func. You indicated by the name "FindById" how the search will be performed. The fact that the value of accountId is compared with the BankAccountId property is a detail of the implementation hidden in this method. Name the method "FindBy" if you want to use a generic approach that uses lambda.

I would also NOT put AddInterest in the IBankAccount interface if all implementations do not support this behavior. Consider the separate IInterestEarningBankAccount interface that the AddInterest method provides. I would also consider using this interface instead of FixedBankAccount in the code above to make the code more convenient for maintenance and expansion if you add another type of account in the future that supports this behavior.

+2
source

From reading your requirement, here's how I do it:

 //Application Service - consumed by UI public class AccountService : IAccountService { private readonly IAccountRepository _accountRepository; private readonly ICustomerRepository _customerRepository; public ApplicationService(IAccountRepository accountRepository, ICustomerRepository customerRepository) { _accountRepository = accountRepository; _customerRepository = customerRepository; } public void IssueLumpSumInterestToAccount(Guid accountId) { using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create()) { Account account = _accountRepository.GetById(accountId); Customer customer = _customerRepository.GetById(account.CustomerId); account.IssueLumpSumOfInterest(customer); _accountRepository.Save(account); } } } public class Customer { private List<Guid> _accountIds; public IEnumerable<Guid> AccountIds { get { return _accountIds.AsReadOnly();} } } public abstract class Account { public abstract void IssueLumpSumOfInterest(Customer customer); } public class FixedAccount : Account { public override void IssueLumpSumOfInterest(Customer customer) { if (customer.AccountIds.Any(id => id != this._accountId)) throw new Exception("Lump Sum cannot be issued to fixed accounts where the customer has other accounts"); //Code to issue interest here } } public class SavingsAccount : Account { public override void IssueLumpSumOfInterest(Customer customer) { //Code to issue interest here } } 
  • The IssueLumpSumOfInterest method in account aggregation requires that the Customer aggregate help decide whether a percentage should be issued.
  • The customer aggregate contains a list of account identifiers - NOT a list of account aggregates.
  • The base class Account has a polymorphic method - FixedAccount checks that the client has no other accounts - SavingsAccount does not perform this check.
+2
source

Answer for 2 minutes of scanning.

  • Not sure why there is a need for two BankAccount RepositoryLayer.BankAccount and DomainObjectsForBank.IBankAccount views. Hide the save layer associated with it .. allow only the domain object in the service.
  • Don't Miss / Drop Nulls - I think this is good advice.
  • Search methods look like LINQ methods that select items from a collection list. Your methods look as if they want to get the first match and exit. In this case, your parameters may be simple primitives (Ids) vs lambdas.

The general idea seems to be correct. The service encapsulates the logic for this transaction, not the domain objects. If this changes, only one place will be updated.

 public void IssueLumpSumInterest(int acccountID) { var customerId = accountRepository.GetAccount(accountId).CustomerId; var accounts = accountRepository.GetAccountsForCustomer(customerId); if ((accounts.First() is FixedAccount) && accounts.Count() == 1) { // update interest } } 
+1
source

Things that seem strange to me:

  • Your IBankAccount has a FreezeAccount method, but I assume that all accounts will have very similar behavior? Perhaps there is a BankAccount class that implements some interface?
  • AccountStatus should probably be an enumeration? What happens if the Forzen account?
0
source

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


All Articles