Al Iacovella
Al Iacovella

Reputation: 456

CQRS and Passing Data

Suppose I have an aggregate containing some data and when it reaches a certain state, I'd like to take all that state and pass it to some outside service. For argument and simplicity's sake, lets just say it is an aggregate that has a list and when all items in that list are checked off, I'd like to send the entire state to some outside service. Now when I'm handling the command for checking off the last item in the list, I'll know that I'm at the end but it doesn't seem correct to send it to the outside system from the processing of the command. So given this scenario what is the recommended approach if the outside system requires all of the state of the aggregate. Should the outside system build its own copy of the data based on the aggregate events or is there some better approach?

Upvotes: 0

Views: 384

Answers (2)

Software Engineer
Software Engineer

Reputation: 16100

You should never externalise your state. Reporting on that state is a function of the read side, as it produces reports and you'll need that data to call the service. The structure of your state is plastic, and you shouldn't have an external service that relies up that structure otherwise you'll have to update both in lockstep which is a bad thing.

There is a blog that puts forward a strong argument that the process manager is the correct place to put this type of feature (calling an external service), because that's the appropriate place for orchestrating events.

Upvotes: 0

VoiceOfUnreason
VoiceOfUnreason

Reputation: 57214

Should the outside system build its own copy of the data based on the aggregate events.

Probably not -- it's almost never a good idea to share the responsibility of rehydrating an aggregate from its history. The service that owns the object should be responsible for rehydration.

First key idea to understand is when in the flow the call to the outside service should happen.

  1. First, the domain model processes the command arguments, computing the update to the event history, including the ChecklistCompleted event.
  2. The application takes that history, and saves it to the book of record
  3. The transaction completes successfully.

At this point, the application knows that the operation was successful, but the caller doesn't. So the usual answer is to be thinking of an asynchronous operation that will do the rest of the work.

Possibility one: the application takes the history that it just saved, and uses that history to create schedule a task to rehydrate a read-only copy of the aggregate state, and then send that state to the external service.

Possibility two: you ditch the copy of the history that you have now, and fire off an asynchronous task that has enough information to load its own copy of the history from the book of record.

There are at least three ways that you might do this. First, you could have the command schedule the task as before.

Second, you could have a event handler listening for ChecklistCompleted events in the book of record, and have that handler schedule the task.

Third, you could read the ChecklistCompleted event from the book of record, and publish a representation of that event to a shared bus, and let the handler in the external service call you back for a copy of the state.

I was under the impression that one bounded context should not reach out to get state from another bounded context but rather keep local copies of the data it needed.

From my experience, the key idea is that the services shouldn't block each other -- or more specifically, a call to service B should not block when service A is unavailable. Responding to events is fundamentally non blocking; does it really matter that we respond to an asynchronously delivered event by making an asynchronous blocking call?

What this buys you, however, is independent evolution of the two services - A broadcasts an event, B reacts to the event by calling A and asking for a representation of the aggregate that B understands, A -- being backwards compatible -- delivers the requested representation.

Compare this with requiring a new release of B every time the rehydration logic in A changes.

Udi Dahan raised a challenging idea - the notion that each piece of data belongs to a singe technical authority. "Raw business data" should not be replicated between services.

  • A service is the technical authority for a specific business capability.
  • Any piece of data or rule must be owned by only one service.

So in Udi's approach, you'd start to investigate why B has any responsibility for data owned by A, and from there determine how to align that responsibility and the data into a single service. (Part of the trick: the physical view of a service can span process boundaries; in other words, a process may be composed from components that belong to more than one service).

Jeppe Cramon series on microservices is nicely sourced, and touches on many of the points above.

Upvotes: 2

Related Questions