Aggregate root circular dependency when data needed for calculation

by keelerjr12   Last Updated October 24, 2018 01:05 AM

My original design has a domain service that did a lot of work which resulted in an Anemic Domain Model (concepts like BalanceCalculators, AccountServices, etc.). I refactored my design which resulted in Accounts computing the the balance (which is a domain concept).

However, this resulted in me needing to hold a reference to Transactions which led to a circular dependency between Aggregate Roots. So I introduced another concept called an Entry. The idea is that when a transaction is created it will fire off an event TransactionCreatedEvent. From this I can generate an Entry from the transaction and add it to the Account. The Entry class just holds a TransactionID and some metadata that can be used to compute balances of accounts (eventually I will need to extend this account of investment accounts).

However, everything I'm seeing about DDD only has Domain Events being used by the application layer. Am I going about this the wrong way with the expectation that Domain Events should be wired up & used ONLY in the Domain Layer? Additionally, where and how would I wire up an event handler in the domain layer? I think I need to wire a specific account and transaction.

For example, let's say Transaction_1 links Account_1 and Account_2 with a value of $100.00. Entries are created in both Account_1 and Account_2. However, eventually the user modifies the value of Transaction_1 to $1,000.00. I need to let Account_1 and Account_2 that the entry values have changed.

enter image description here

Account.cs

public class Account
{
    public int Id { get; }
    public string Name { get; private set; }

    public enum AccountingType
    {
        Asset,
        Liability,
        Income,
        Expense,
        Equity
    };

    public AccountingType Type { get; private set; }

    public IEnumerable<Account> SubAccounts => _subAccounts;
    public decimal Value => ComputeValue();

    public Account(int id, string name, AccountingType type)
    {
        Id = id;
        Name = name;
        Type = type;
    }

    public void Rename(string newName)
    {
        var oldName = Name;
        Name = newName;
        DomainEvents<AccountRenamedEvent>.Publish(new AccountRenamedEvent(oldName, Name));
    }

    public void ChangeType(AccountingType newType)
    {
        Type = newType;
    }

    public void AddSubAccount(Account account)
    {
        _subAccounts.Add(account);
    }

    public void RemoveSubAccount(Account account)
    {
        _subAccounts.Remove(account);
    }

    public void PostEntry(Entry entry)
    {
        _entries.Add(entry);
    }

    public void RemoveEntry(Entry entry)
    {
        _entries.Remove(entry);
    }

    private decimal ComputeValue()
    {
        var value = 0m;

        foreach (var entry in _entries)
        {
            value += entry.Value;
        }

        foreach (var account in _subAccounts)
        {
            value += account.Value;
        }

        return value;
    }

    private readonly List<Account> _subAccounts = new List<Account>();
    private readonly List<Entry> _entries = new List<Entry>();
}

Entry.cs

public class Entry
{
    public int TransactionId;
    public decimal Value;

    public Entry(int transactionId, decimal value)
    {
        TransactionId = transactionId;
        Value = value;
    }
}

Transaction.cs

public class Transaction
{
    public Transaction(int transactionId, DateTime date, string description, decimal amount, int debitAccountId, int creditAccountId)
    {
        TransactionId = transactionId;
        Date = date;
        Description = description;
        Amount = amount;
        DebitAccountId = debitAccountId;
        CreditAccountId = creditAccountId;

        DomainEvents<TransactionCreatedEvent>.Publish(new TransactionCreatedEvent(TransactionId, Date, Description, Amount, DebitAccountId, CreditAccountId));
    }

    public void ChangeDate(DateTime newDate)
    {
        Date = newDate;

        DomainEvents<TransactionDateChangedEvent>.Publish(new TransactionDateChangedEvent(TransactionId, newDate));
    }

    public void ChangeDescription(string description)
    {
        Description = description;
    }

    public void ChangeAmount(decimal amount)
    {
        Amount = amount;
    }

    public void ChangeDebitAccountId(int newAccountId)
    {
        DebitAccountId = newAccountId;
    }

    public void ChangeCreditAccountId(int newAccountId)
    {
        CreditAccountId = newAccountId;
    }

    public int TransactionId { get; }
    public DateTime Date { get; private set; }
    public string Description { get; private set; }
    public decimal Amount { get; private set; }
    public int DebitAccountId { get; private set; }
    public int CreditAccountId { get; private set; }
}


Related Questions


Domain model design

Updated July 23, 2016 08:02 AM



Who are the domain experts?

Updated January 08, 2017 08:02 AM