vendredi 25 mai 2018

Changing aggregate behavior during its lifetime

Imagine that we have an Aggregate that has a life cycle, such that it can change its behavior during its lifetime. During the first part of its life, it can do some things and during the second part, it can do other things.

I´d like to hear opinions on how should we restrict what the aggregate can do on each phase. To make it a little more tangible, lets take an financial trade as an aggreagate example.

  • A trader creates a trade informing the contract, and its price.
  • A risk manager validates a trade, giving a reason for such.
  • The BackOffice can submit the trade to the ledger, providing accounting information.
  • After the trade is submitted, the accounting information can never be changed.

The trade clearly has 3 distinct phases, which I´ll call Typed, Validated and Submitted

My first thought is to pollute the aggregate with InvalidOperationExceptions, which I really don´t like:

public class Trade 
{
    private enum State { Typed, Validated, Submited }
    private State _state = State.Typed;

    public Guid Id { get; }
    public Contract Contract { get; }
    public decimal Price { get; }

    public Trade (Guid id, Contract contract, decimal price) { ... }

    private string _validationReason = null;
    private AccountingInformation _accInfo = null;

    public void Validate(string reason)  {
        if (_state != State.Typed)
            throw new InvalidOperationException (..)
        ...
        _validationReason = reason;
        _state = State.Validated;
    }

    public string GetValidationReason() {
        if (_state == State.Typed)
            throw new InvalidOperationException (..)
        return _validationReason;
    }

    public void SubmitToLedger(AccountingInformation info) {
        if ((_state != State.Validated))
            throw new InvalidOperationException (..)
        ...
    }

    public AccountingInfo GetAccountingInfo() { .. }
}

I can do something like a Maybe pattern, to avoid the exceptions on the Get... methods. But that would not work for the behavior methods (Validate, SubmitToLedger, etc)

Oddly, if I were to be working on a functional language (such as F#), I would probably create a different type for each state.

type TypedTrade = { Id : Guid;  Contract: Contract; Price : decimal }

type ValidatedTrade = {  Id : Guid;  
                        Contract: Contract; 
                        Price : decimal;
                        ValidationReason : string}

type SubmittedTrade =  {  Id : Guid;  
                        Contract: Contract; 
                        Price : decimal;
                        ValidationReason : string; 
                        AccInfo : AccountingInfo }

// TypedTrade -> string -> ValidatedTrade
let validateTrade typedTrade reason = 
    ...
    { Id = typedTrade.Id; Contract = typedTrade.Contract;
            Price = typedTrade.Price; Reason = reason }

// ValidatedTrade -> AccountingInfo -> SubmittedTrade
let submitTrade validatedTrade accInfo = 
    ...
    { Id = validatedTrade.Id; 
    Contract = validatedTrade.Contract;
    Price = validatedTrade.Price; 
    Reason = validatedTrad.Reason;
    AccInfo = accInfo }

And the problem would gracefully go away. But to do that in OO, I would have to make my aggregate immutable and maybe create some kind o hierarchy (in which I would have to hide base methods !? ouch!).

I just wanted an opinion on what you guys do on these situations, and if there is a better way.

Aucun commentaire:

Enregistrer un commentaire