Events that don't have enough data for the read side

Coordinator
Nov 24, 2011 at 7:00 PM

When adapting the code to better match the current specs, I had one problem: how would you define/handle an event in the read if the event doesn't include all the data you need to adapt the view DTOs?

Concrete scenario: We now have two disjunct commands in the Contracts.tt file that deal with associated persons:

EnterPerson? (personId, firstName, lastName, Nullable DateOfBirth)
PersonEntered! (personId, firstName, lastName, Nullable DateOfBirth)

AssociatePerson? (sponsoredChildId, version, personId, string Relationship)
PersonAssociated! (sponsoredChildId, personId, string Relationship)

As you can see, the EnterPerson command allows one to add a new person to the database, AssociatePerson associates a known person with a sponsored child (in a specific version).

The ChildListView handler handles the PersonAssociated event:

        public void Handle (PersonAssociated domainEvent)
        {
            using (IDocumentSession session = _documentStore.OpenSession())
            {
                ChildListDto dto = LoadChildById (session, domainEvent.SponsoredChildId);

                // TODO: How to get the first name and last name of the associated person?
                // dto.AssociatedPersons.Add (domainEvent.PersonFirstName + " " + domainEvent.PersonLastName);
                dto.AssociatedPersons.Add ("Dummy");
                SaveChanges (session, dto, domainEvent);
            }
        }

The handler needs to know the first and last name of the person being associated with the child, but the event doesn't include that data because the event was defined independently from the read model. I can think of a few options here, but I'd be interested to know: What's the suggested practice in such a case?

Coordinator
Nov 24, 2011 at 7:57 PM

I would rethink of AssociatedPerson (or Person) is an aggregate on its own or it belongs to the Child aggregate and therefor the command "AssociatePerson" belongs to the Child AR. In my opinion "EnterPerson" is not a good name for a command. It's very difficult to get the intention behind this command - at least for me.

Where would you show the data of the associated person? On the same page as the child (read) data? -> then add it to the "ChildListView" class. Model the DTO to hold a list of associated personens in addition. It was already in the sample:

public class ChildListView : HandlesEvent<NewChildRegistered>, HandlesEvent<AssociatedPersonAdded>, HandlesEvent<ChildNameChanged>
    {
        private readonly IDocumentStore _documentStore;

        public ChildListView(IDocumentStore documentStore)
        {
            _documentStore = documentStore;
        }

        public void Handle(NewChildRegistered domainEvent)
        {
            using (var session = _documentStore.OpenSession())
            {
                var dto = new ChildListDto { AggregateRootId = domainEvent.ChildId, Name = domainEvent.FirstName + " " + domainEvent.LastName };
                
                session.Store(dto);
                session.SaveChanges();
            }
        }

        public void Handle(AssociatedPersonAdded domainEvent)
        {
            using (var session = _documentStore.OpenSession())
            {
                var dto = session.Load<ChildListDto>(Dto.GetDtoIdOf<ChildListDto>(domainEvent.ChildId));
                dto.AssociatedPersons.Add(domainEvent.PersonFirstName + " " + domainEvent.PersonLastName);
                
                session.SaveChanges();
            }
        }

        public void Handle(ChildNameChanged domainEvent)
        {
            using (var session = _documentStore.OpenSession())
            {
                var dto = session.Load<ChildListDto>(Dto.GetDtoIdOf<ChildListDto>(domainEvent.ChildId));
                dto.Name = domainEvent.FirstName + " " + domainEvent.LastName;
                
                session.SaveChanges();
            }         
        }
    }

    public class ChildListDto : Dto
    {
        public ChildListDto()
        {
            AssociatedPersons = new List<string>();
        }

        public string Name { get; set; }

        public IList<string> AssociatedPersons { get; set; }
    }
Coordinator
Nov 24, 2011 at 8:39 PM

Yes, I knew that you already had a solution with Person being a part of the SponsoredChild aggregate. But my question is: If Person is a separate aggregate, how do you get the necessary data within the Handle(AssociatedPersonAdded domainEvent) method?

(We can of course discuss whether Person is an aggregate. I've my reasons why I believe it is one, but I'd like to know in general: what do you do in such a case when you add an association between aggregates?)

I've split your event in two: EnterPerson adds a new person to the system. AddAssociatedPerson then adds an association from a sponsored child to a person:

AssociatePerson? (sponsoredChildId, version, personId, string Relationship) 
PersonAssociated! (sponsoredChildId, personId, string Relationship)

Of course, the command doesn't include the person data - the system already knows the person identified by personId.

The PersonAssociated event could of course include the data. But I wonder if it is the recommended way to modify the event declarations because a specific view needs it. I.e., whenever I add a view that needs some more data, I'll just add more info to the events - is this the way to go?

 

Coordinator
Nov 26, 2011 at 5:57 AM

Yes, event enrichment is the often recommended way.


I guess in some cases it would also be ok to query the read side when processing the event. This should be considered if the events, because of the additional needed properties, would grow too big (-> overhead for transporting/storing events).

The last and dirtiest option IMO is to perform multiple queries on the client (first ask for the child and then for the associated persons).

Coordinator
Nov 26, 2011 at 6:51 AM
Okay, thanks for the answer. I've to say, I don't really like the event enrichment option because I believe the domain layer (of which the events are a part) shouldn't know or care about the views in the application.

I think building separate SponsoredChildListPersonDtos and query them either in the event handler for PersonAssociated or the UI would be better from a Separation of Concerns standpoint.

(Although event enrichment is probably simpler...)

Gesendet von meinem Windows Phone

Von: sburgstaller
Gesendet: 26.11.2011 07:57
An: Schmied, Fabian
Betreff: Re: Events that don't have enough data for the read side [godfather:280765]

From: sburgstaller

Yes, event enrichment is the often recommended way.


I guess in some cases it would also be ok to query the read side when processing the event. This should be considered if the events, because of the additional needed properties, would grow too big (-> overhead for transporting/storing events).

The last and dirtiest option IMO is to perform multiple queries on the client (first ask for the child and then for the associated persons).