shobhit vaish
shobhit vaish

Reputation: 951

Domain driven design for accounting System

I am following the DDD approach for the accounting system I am developing.

There are two parts(see domain below):

  1. Create a transaction: A transaction(AccountingTransaction) is created as a parent(aggregate root) when cash is moved from Ledger to the Client's account or vice versa, or from one Ledger to another. A transaction can be multi-legged, i.e., Transaction(AccountingTransaction) can have multiple LedgerTransaction or CashTransaction.
  2. Reconcile accounts: I need to match credit and debits in each ledger daily.

This is how my domain looks like:

  1. AccountingTransaction is the aggregate root.
  2. LedgerTransaction and CashTransaction are the child entities of the AccountingTransaction.
  3. AccountingTransaction will have a list of CashTransactions and LedgerTransactions.
  4. Ledger is the aggregate root.
 public class AccountingTransaction : AggregateRoot
    {
        public string AccountingTransactionId { get; private set; }
        public TransactionStatus TransactionStatus { get; private set; } //enum
        
        private List<CashTransaction> _cashTransactions = new List<CashTransaction>();
        public IReadOnlyList<CashTransaction> CashTransactions => new ReadOnlyCollection<CashTransaction>(_cashTransactions);
        private List<LedgerTransaction> _ledgerTransactions = new List<LedgerTransaction>();
        public IReadOnlyList<LedgerTransaction> LedgerTransactions => new ReadOnlyCollection<LedgerTransaction>(_ledgerTransactions);
        private AccountingTransaction(string transactionId, List<CashTransaction> cashTransactions, List<LedgerTransaction> ledgerTransactions, TransactionStatus transactionStatus)
        {
            //Code omitted for brevity
        }
        public static IResult<AccountingTransaction> CreateTransaction(string transactionId, List<CashTransaction> cashTransactions, List<LedgerTransaction> ledgerTransactions, List<ClientAccount> clientAccounts, List<Ledger> ledgerAccounts)
        {
            
            //Factory method
            //Maintain invaraint and creates a new transaction
        }
        public IResult CancelTransaction(string username)
        {
            
        }
    }

 public class LedgerTransaction
    {
        public int Id { get; private set; }
        public string LedgerAccountId { get; private set; }
        public string TransactionId { get; private set; }
        public string EntryDescription { get; private set; }
        public DateTime? ReconciledOn { get; private set; }
        public TransactionAmount TransactionAmount { get; private set; } //Value object
        private LedgerTransaction(int id, TransactionAmount transactionAmount,
            string transactionId,
            string entryDescription,
            string batchId,
            string ledgerAccountId)
        {
            //Code omitted for brevity
        }

        internal static IResult<LedgerTransaction> CreateTransaction(/*List of arguments*/)
        {
            //Factory method
            //Code omitted for brevity
        }
    }

public class CashTransaction
    {
        public int Id { get; private set; }
        public string ClientAccountId { get; private set; }
        public string TransactionId { get; private set; }
        public TransactionAmount TransactionAmount { get; private set; }//Value object
        public string EntryDescription { get; private set; }
        private CashTransaction(int id, 
            TransactionAmount transactionAmount,
            string transactionId,
            string entryDescription,
            string clientAccountId)
        {
            Id = id;
            TransactionAmount = transactionAmount;
            TransactionId = transactionId;
            EntryDescription = entryDescription;
            ClientAccountId = clientAccountId;
        }
        internal static IResult<CashTransaction> CreateTransaction(/*List of arguments*/)
        {
            //Factory method
            //Code omitted for brevity
        }
    }

public class Ledger : AggregateRoot
    {
        public string AccountId { get; private set; }
        public string Name { get; private set; }
        public LedgerType LedgerType { get; set; }
        public Currency Currency { get; private set; }
    }

So the first part(create transaction) works pretty well, and I am stuck on how I should approach the Ledger accounts reconciliation.

Problem: To reconcile accounts, for a given day, I need to fetch all ledger transactions belonging to a particular Ledger where ReconciledOn(see domain class) is null. Then I need to make sure that all debits and credits sum is 0 - if not, I need to report the error. It is also possible that matching debit and credits belong to the different aggregate roots (AccountingTransaction). This also means I need to fetch Ledger transactions outside of the aggregate root(AccountingTransaction), which is against DDD, and then probably do a write operation directly on the LedgerTransactions table.

Please advise how should I approach this. Is there a flaw in the domain classes?

I appreciate your help.

Thank you!

Upvotes: 1

Views: 860

Answers (2)

Vladimir
Vladimir

Reputation: 1660

The domain looks good at first glance. A couple of options here:

  1. Fetch all AccountingTransactions that have unreconciled ledger transactions and pass them all to do the reconciliation service. This would require you to put some domain logic to the repository (the knowledge of what an unreconciled transaction is), but that should be fine.

  2. Make LedgerTransaction its own aggregate root.

Upvotes: 1

Levi Ramsey
Levi Ramsey

Reputation: 20541

That conflict suggests that LedgerTransaction wants to be an aggregate root.

Upvotes: 0

Related Questions