0

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.

Use Cases

Add/remove an account
Add/remove an account to another account (i.e. subaccounts)
Edit account details such as name
Retreive balance of account
Create transaction
Modify transaction amount, credited account, debited account, date, etc.

Need concept of an investment account as well...

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; }
}
  • 1
    Is it truly necessary that a Transaction needs to be able to be modified? I would take a close look at your system if this is the case (because I highly doubt it is). – king-side-slide Oct 24 '18 at 15:05
  • @king-side-slide, for personal finance software, I feel as if a user needs to be able to view all transactions for an account and then modify a transaction if there is an error in the description, amount, other account, etc. – keelerjr12 Oct 24 '18 at 20:07
  • 1
    I understand. The next thing I’m going to ask is important. Is this system best-modeled using DDD? Your description of the requirements sounds more like a CRUD-type reporting system than one requiring a rich model with lots of business rules. Most of the work here is going to be creating views of the data. That said, if this is a personal growth project then I say go for it, and I will compose an answer – king-side-slide Oct 29 '18 at 17:41
  • @king-side-slide it is a personal side project. You are probably right that DDD does not apply. – keelerjr12 Oct 29 '18 at 18:19

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.