6

I'm building a web application with C#, and I have an aggregate root which has several entities and value objects. Then I have a repository object which persists the aggregate to the database (I'm using ADO.NET as my infrastructure persistence layer).

The aggregate also has several events attached to its methods, so that other actions can be taken outside the aggregate (for example, the method OpenRequisition of a Customer aggregate fires the event RequisitionCreatedEvent, and a handler then queues an action to update statistics regarding the customer.

This is a simplified sample.

class Customer {
    private Guid _id;
    private string _shortName;
    private string _legalName;
    private TaxCode _taxCode;
    private ContactInfo _primaryContact;
    private List<Requisitions> _requisitions; //Requisition is an entity
    private IDomainEvents _events;
    // and so many other fields

    public Requisition OpenRequisition(RequisitionDefinition definition, User requestedBy) 
    {
        Requisition req = ... // create the new requisition somehow
        _requisitions.Add(req);
        _events.Invoke(new RequisitionCreatedEvent(this, req));

        return req;
    }

    // and so on
}

The problem I'm facing is not with how to persist, but how to reconstruct the aggregate object from the database. Many of the customer's fields have setters, such as the ShortName or LegalName, or even TaxCode. Others does not, such as the _requisitions field. I have no direct way of reconstructing existing requisitions. If I expose _requisitions somehow (as an List property), then I could simple do something like this in my repository:

var requisitions = ... // get requisitions for this customer
var customer = new Customer(id);
customer.Requisitions.AddRange(requisitions);

But then elsewhere one could do:

customer.Requisitions.Add(new Requisition());

bypassing the logic and validations set in OpenRequisition method. On the other hand, if I invoke OpenRequisition method from my repository, then the event handler will get called each time, and it shouldn't, because I'm reconstructing the object from the database, not actually opening a new requisition.

What I have so far, is to create a constructor and then pass all these parameters in there, something like this:

public Customer(IDomainEvents events, Guid id, IEnumerable<Requisition> requisitions, ... and so on) 
{
    ... // perform validations
    _events = events;
    _id = id;
    _requisitions = new List<Requisition>(requisitions);
    ... // and so on
}

However, this aggregate has several other children nodes, and so the constructor is growing and growing, and it doesn't feel right. At one point I tried to create factory class, but I still need the large constructor, or something like having internal setters even for _events and _requisitions, and it still doesn't seem right.

Hence, my question is: how to reconstruct complex aggregate roots, how to pass all the parameters the aggregate needs to keep functioning without having the object to perform already made work (such as firing the event handler). Otherwise, what options would you consider on scenarios such as this?

1
  • Using a Factory class which sets the internal properties of your aggregate seems fine to me.
    – MetaFight
    May 15, 2016 at 10:20

2 Answers 2

3

This is a common issue when implementing aggregates. It usually gives rise to separating aggregate state onto a another object. That object can be a pure data bucket with public getters/setters for convenience. Outside callers could be aware of the aggregate's state class, but it remains private to the aggregate (as a private field, for instance). The repository could directly load aggregate's state, which can be passed as a single object to the aggregate's constructor.

You would have:

public CustomerState
{
    public Guid Id { get; set; }
    public string ShortName { get; set; }
    public string LegalName { get; set; }
    public TaxCode TaxCode { get; set; }
    public ContactInfo PrimaryContact { get; set; }
    public List<Requisitions> Requisitions { get; set; }
}

public Customer
{
    private readonly CustomerState _state;
    private readonly IDomainEvents _events;

    public Customer(CustomerState state, IDomainEvents events) 
    {
        _state = state;
        _events = events;
    }
}

...

public Requisition OpenRequisition(RequisitionDefinition definition, User requestedBy) 
{
    Requisition req = ... // create the new requisition somehow
    _state.Requisitions.Add(req);
    _events.Invoke(new RequisitionCreatedEvent(this, req));

    return req;
}

Notice that I placed IDomainEvents in the aggregate instead of the state class, because it has some behavior (likely dispatching). For your state object, you only want pure data.

In a very surface way, this organizationally resembles an Anemic Domain Model because the state is separated, and the aggregate essentially just contains methods, similar to a Facade pattern. But the difference being that an anemic domain allows the user to directly manipulate state and run a Transaction Script on their manipulations. But with this, user is not allowed to directly manipulate state... we retain control of validated state changes through domain methods, but add easier loading (and saving).

Now, separating the state in this way does present it's own challenges. Usually naming confusion is one initially. You probably don't want to name your data table CustomerState (and it's probably already named Customer). But naming the table Customer will result in confusion when the aggregate of the same name is structurally dissimilar. If you're up for some reorganization, you might consider naming your aggregate Sales or Ordering or the like (based on what area it deals with) and keep the customer's state named as Customer. You might find that leads to better organizational clarity for your use cases anyway.

4
  • Thank you @Kasey, for your answer. It actually makes a lot of sense, the ctor will still make validation of the state object. Plus, I already have some DTO classes which I use to get data from the DB, so this classes could be the state objects (perhaps creating an interface, ICustomerState, to avoid linking directly to the infrastructure layer. It may very well be needed some reorganization, but I feel it won't be too much. Thanks again, it was a great answer. May 16, 2016 at 3:39
  • 1
    In my experience, validating in the constructor (aggregates... messages are a different story) can lead to some issues. What happens if it is invalid from the database? Loading is not something you expect to crash aside from database outage, and is hard to remediate without emergency data fixes or code patches. Ensuring state remains valid through method calls is usually the best line of defense. You can enforce this by making the constructor internal and providing static methods to load and create the aggregate. it doesn't prevent misuse, but at least it makes wrong usage feel awkward. May 16, 2016 at 3:54
  • @FernandoGómez Forgot to say: I appreciate your comment! Glad that is helpful. I ran into the same issue and that was my solution. May 16, 2016 at 4:01
  • Thanks again @Kasey, I'll follow that advice. I got so excited that I couldn't wait until tomorrow to try this out, and I'm implementing it now, Sunday night --it's actually going pretty smooth, with very few changes overall. Hope this question helps other fellow programmers, because I just couldn't find anything on Google but trivial examples. Best regards! May 16, 2016 at 4:12
0

I would make a class to save value objects and another to load each value object. Value objects here mean primitives only. Also, validate and try immutability so no thread locking is needed (can't data data race if immutable). Builder pattern may help.

Then for agretes named Foo make a persist Foo class. It holds all value objects in Foo as final and has a builder. The Foo class has two static methods, to Persist and from Persist. They are just conversions methods but can't fail.

Now, just add save and load class for that. Should just be delegation at that point and save and load .

One last tip, save and load takes parameters sometimes but has no state with it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.