Over the last few days, I sepent time refining architectural patterns in a modular .NET backend centered around payroll processing, approvals, and workflow-driven operations.
One area I focused on heavily was aggregate design and state protection. Instead of exposing mutable collections directly from entities, aggregates internally manage their own state exposing read-only access externally.
private readonly List _monthlyAllowance = new();
public IReadOnlyCollection
MonthlyAllowances => _monthlyAllowance ;
This prevents external code from bypassing aggregate rules;
employee.MonthlyAllowances.Add(...)
while still allowing controlled state transitions through aggregate methods:
public void AddMonthlyAllowance(MonthlyAllowance allowance)
{
if(_monthlyAllowances.Any(x => x.PayrollMonth == allowance.PayrollMonth && x.SalaryItemId == allowance.SalaryItemId))
{
throw new DuplicateMonthlyAllowanceException();
}
_monthlyAllowances.Add(allowance);
}
Another major refinement was moving workflow…