How to handle one to many relationship in CQRS/ES/Saga architecture?

39 Views Asked by At

Here is the aggregate I currently have:

  1. ServiceGroup (service_group_id, service_group_name)
  2. Service (service_id, service_start_time, service_end_time)
  • Service cannot live without Service Group.
  • ServiceGroup contains a list of service, this list can be huge.
  • Service start time and end time should not overlap with other Services.
  • Where ServiceGroup is deleted, all underlying Services should be deleted.

Since ServiceGroup can have huge list of Services, seems it is not suitable to put all the Services to the aggregate state of ServiceGroup... It looks like I should add a "service_id" field referencing the Service Group into Service.

So... I have two aggregate roots

  1. ServiceGroup (service_group_id, service_group_name)
  2. Service (service_group_id, service_id, service_start_time, service_end_time)

Then I've the following command handlers:

  • ServiceGroup:CreateServiceGroup -> ServiceGroup:ServiceGroupCreated(service_group_id, service_group_name)

  • ServiceGroup:DeleteServiceGroup -> ServiceGroup:ServiceGroupDeleted(service_group_id)

  • Service:CreateService -> Service:ServiceCreated(service_group_id, service_id, service_start_time, service_end_time)

  • Service:DeleteService -> Service:ServiceDeleted(service_id)

Now, my question is how do I ensure the service_group_id specified in the Service:ServiceCreated exists? I'm ok going with compensation action (Service:DeleteService) if checked the ServiceGroup doesn't exists afterward.

Here is some possible solutions I thought.

Option 1: Create a ServiceGroupLifecycleSaga (identified by service_group_id):

  • Service:ServiceCreated -> ServiceGroupLifecycleSaga:CreateServiceStarted + ServiceGroup:CheckIfExists (It is dummy command to check if ServiceGroup exists...., it won't change the aggregate state, seems violate event-sourcing)
  • ServiceGroup:ExistenceChecked -> ServiceGroupLifecycleSaga:CreateServiceCompleted
  • ServiceGroup:NotExists -> Service:DeleteService
  • Service:ServiceDeleted -> ServiceGroupLifecycleSaga:CreateServiceFailed

Option 2: Move the CreateService command to ServiceGroup, then create a ServiceLifecycleSaga (identified by service_id):

  • ServiceGroup:ServiceCreated (this event also no changes to the aggregate state...) -> ServiceLifecycleSaga:CreateServiceStarted + Service:CreateService
  • Service:ServiceCreated -> ServiceLifecycleSaga:CreateServiceCompleted

I'm not sure is it a good practice to check aggregate existence like this way.

But, no matter which way I choose, its hard to satisfy:

  • Service start time and end time should not overlap with other Services.
  • Where ServiceGroup is deleted, all underlying Services should be deleted.

The start time and end time are stored in the Service aggregate, and Service Group does not contain any reference to its underlying service. Only store id reference is not possible as the list is quite huge and aggregate state will be loaded in the command handler. It looks not quite possible to check across Service aggregates....

The Service Group does not contain any reference to its underlying service. When Service Group receive the DeleteServiceGroup command, it cannot derive the underlying service ids to propagate the DeleteService command....

What's the common practice to handle this usecase in CQRS/ES/Saga system?

0

There are 0 best solutions below