0
\$\begingroup\$

There is a follow-up on this question.

I have created this bank ATM app mockup which I believe implement some Domain-Design Design layered architecture, repository pattern and uow pattern, if not fully. This app have 3 basic functions:

  • Check balance
  • Deposit
  • Withdraw

In my solution, I have these projects (layer):

  • ATM.Model (Domain model entity layer)
  • ATM.Persistence (Persistence Layer)
  • ATM.ApplicationService (Application layer)
  • ATM.ConsoleUICore (UI layer)

ATM.Model (Domain model entity layer)

namespace ATM.Model
{
public class BankAccount
{
public int Id { get; set; }
public string AccountName { get; set; }
public decimal Balance { get; set; }

public decimal CheckBalance()
{
    return Balance;
}

public void Deposit(int amount)
{
    // Domain logic
    Balance += amount;
}

public void Withdraw(int amount)
{
    // Domain logic
    //if(amount > Balance)
    //{
    //    throw new Exception("Withdraw amount exceed account balance.");
    //}

    Balance -= amount;
}
}
}

namespace ATM.Model
{
public class Transaction
{
public int Id { get; set; }
public int BankAccountId { get; set; }
public DateTime TransactionDateTime { get; set; }
public TransactionType TransactionType { get; set; }
public decimal Amount { get; set; }
}

public enum TransactionType
{
Deposit, Withdraw
}
}

ATM.Persistence (Persistence Layer)

namespace ATM.Persistence.Context
{
public class AppDbContext : DbContext
{        
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder.UseSqlServer(@"[connstring]");
}

public DbSet<BankAccount> BankAccounts { get; set; }
public DbSet<Transaction> Transactions { get; set; }
}
}

namespace ATM.Persistence.Repository
{
public class RepositoryBankAccount
{
public AppDbContext context { get; }

public RepositoryBankAccount()
{
    context = new AppDbContext();
}

public BankAccount FindById(int bankAccountId)
{
    return context.BankAccounts.Find(bankAccountId);
}

public void AddBankAccount(BankAccount account)
{
    context.BankAccounts.Add(account);
}

public void UpdateBankAccount(BankAccount account)
{
    context.Entry(account).State = EntityState.Modified;
}
}
}

namespace ATM.Persistence.Repository
{
public class RepositoryTransaction
{
private readonly AppDbContext context;

public RepositoryTransaction()
{
    context = new AppDbContext();
}

public void AddTransaction(Transaction transaction)
{
    context.Transactions.Add(transaction);
}
}
}

ATM.ApplicationService (Application layer)

namespace ATM.ApplicationService
{
public class AccountService
{        
private readonly UnitOfWork uow;

public AccountService()
{            
    uow = new UnitOfWork();
}

public void DepositAmount(BankAccount bankAccount, int amount)
{            
    bankAccount.Deposit(amount);
    uow.BankAccounts.UpdateBankAccount(bankAccount);

    var transaction = new Transaction()
    {
        BankAccountId = bankAccount.Id,
        Amount = amount,
        TransactionDateTime = DateTime.Now,
        TransactionType = TransactionType.Deposit
    };

    uow.Transactions.AddTransaction(transaction);

    try
    {
        uow.Commit();
    }
    catch
    {
        uow.Rollback();
    }
    finally
    {
        uow.Dispose();
    }
}

public void WithdrawAmount(BankAccount bankAccount, int amount)
{            
    bankAccount.Withdraw(amount);
    uow.BankAccounts.UpdateBankAccount(bankAccount);
    //repoBankAccount.UpdateBankAccount(bankAccount);

    var transaction = new Transaction()
    {
        BankAccountId = bankAccount.Id,
        Amount = amount,
        TransactionDateTime = DateTime.Now,
        TransactionType = TransactionType.Withdraw
    };

    uow.Transactions.AddTransaction(transaction);

    try
    {
        uow.Commit();
    }
    catch
    {
        uow.Rollback();
    }
    finally
    {
        uow.Dispose();
    }
}

public decimal CheckBalanceAmount(int bankAccountId)
{
    BankAccount bankAccount = uow.BankAccounts.FindById(bankAccountId);

    return bankAccount.CheckBalance();
}
}
}

ATM.ConsoleUICore (UI layer)

namespace ATM.ConsoleUICore
{
class Program
{
static void Main()
{
    AccountService accountService = new AccountService();
    RepositoryBankAccount repoBankAccount = new RepositoryBankAccount();

    var bankAccount = repoBankAccount.FindById(2);

    Console.WriteLine("1. Check balance");
    Console.WriteLine("2. Deposit");
    Console.WriteLine("3. Withdraw");
    Console.WriteLine("Enter option: ");
    string opt = Console.ReadLine();
    switch (opt)
    {
        case "1":
            Console.WriteLine($"Your balance is ${bankAccount.CheckBalance()}");
            break;
        case "2":
            // User to input amount.
            // Data validation to make sure amount is greater than zero.
            // Pass the input amount to Application layer.

            accountService.DepositAmount(bankAccount, 50);

            // After getting the operation status from Application service layer.
            // Print operation status here: Either success or fail
            Console.WriteLine("Deposit successfully");
            break;
        case "3":            
            break;
        default:
            break;
    }

}
}
}

What do you think of this mock-up in regards to Domain-Driven Design or layered architecture design and repository pattern and uow pattern point of view?

\$\endgroup\$
  • 4
    \$\begingroup\$ You haven't told us anything about your application. What features does it have? How do they work? Why have you separated it in these particular layers? \$\endgroup\$ – t3chb0t Aug 19 at 14:29
  • \$\begingroup\$ It has few features like check balance, deposit and withdraw function. Just to see if I implement DDD, repository and uow pattern correctly. \$\endgroup\$ – Steve Ngai Aug 19 at 14:37
  • 3
    \$\begingroup\$ Rather than listing these functions in a response could you please add them to the top of the question. \$\endgroup\$ – pacmaninbw Aug 19 at 16:13
  • 1
    \$\begingroup\$ Just wanted to point out DbContext is essentially a UOW class. From a DDD perspective shouldn't your Deposit and Withdraw methods new up a Transaction? Shouldn't all of the logic for what happens when a deposit/withdrawal is made be inside of the BankAccount? Another developer could come along call one of those methods and the BankAccount without creating a new Transaction and assume everything is correct. \$\endgroup\$ – matt_lethargic Aug 23 at 11:15
  • 1
    \$\begingroup\$ You may want to also look at clean / hexagonal architecture instead of N-Tier and layering in the way you build things. Layering like this is a pretty much outdated way of development. \$\endgroup\$ – matt_lethargic Aug 23 at 11:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.