I am following the DDD approach for the accounting system I am developing.
There are two parts(see domain below):
- 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.
- Reconcile accounts: I need to match credit and debits in each ledger daily.
This is how my domain looks like:
- AccountingTransaction is the aggregate root.
- LedgerTransaction and CashTransaction are the child entities of the AccountingTransaction.
- AccountingTransaction will have a list of CashTransactions and LedgerTransactions.
- 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!
That conflict suggests that LedgerTransaction wants to be an aggregate root.