-1

Suppose you have two bounded contexts (BC1 and BC2) with separate databases.

They both contain a User entity that needs to be kept in sync across the bounded contexts. In this case, suppose that we accomplish the synchronization through integration events.

And in this example, the User entity 'originates' in BC1, meaning that a request is always made to BC1 to create/update/delete a User. Whatever operation was performed on the User in BC1 is then broadcast to BC2 via an integration event. BC1 is always the broadcaster and BC2 is always the consumer of integration events related to Users.

Suppose furthermore that the database of BC2 was originally designed to have fewer fields in its User entity than BC1's User entity. For example, BC2 didn't need a User.EmailAddress field up to a certain point, and this field was excluded from the database schema in BC2.

But then a change in requirements makes User.EmailAddress required in BC2.

In that case, we start by adding this field to the database schema in BC2. But if you are already in a production environment, you will need to make this field optional (or give it a default value).

The next step would have to be migrating the email addresses from BC1 to the corresponding Users in BC2.

You might want to just have a single database migration in BC2 that adds an optional User.EmailAddress field, populates that field from BC1's database, and then makes User.EmailAddress required in BC1. But that would require a cross database query, and that is not always possible to do.

My question then is simply, what is the best way to populate the User.EmailAddress field of pre-existing Users in BC2?

You could, of course, write a script to do it. But then you have to update the script whenever a new field is added to the User entity in BC2.

The source of the difficulty is that the UserCreated integration event from BC1 has already been consumed by BC2, and when this happened, BC2 simply ignored the email address of the User. You can't rebroadcast the integration event in order to get the email address across because integration events are only broadcast in response to an actual action in the domain, and technically, nothing in the domain of BC1 has changed.

One option I am considering is to have 'identity' domain and integration events. The 'identity' domain event could be published on demand and wouldn't indicate any change in the domain. It would just be a way of broadcasting the state of a given entity to other bounded contexts. Perhaps this technically violates some principles of DDD, but it avoids me having to update a script every time a field is added in BC2.

1 Answers1

0

But that would require a cross database query, and that is not always possible to do.

This is actually against the idea of bounded context. Bounds are not meant to be crossed, especially at persistence-level. This is because by bypassing the business layer of BC1, you risk violating business constraints in the overall system.

You could, of course, write a script to do it. But then you have to update the script whenever a new field is added to the User entity in BC2.

You don't need to. Consider this operation as a one-time migration that will:

  1. add the new column with not null constraint and empty default value
  2. update your integration event handler in BC2 to include emails
  3. iterate over your rows and populate by lookup from BC1

Since email can only be changed in BC1, updates are idempotent so you don't care about concurrency. You only need to run this once, then you're done. The ulterior work is done by your updated event handler. If you need to add another column later, just handle that as a new migration with the same operations, no need to update anything.

The 'identity' domain event could be published on demand and wouldn't indicate any change in the domain.

An event is a change in the system's state. If there is no change in the domain, this is called a query. Your problem is one of the known drawbacks of doing database-per-service with CQRS pattern: it requires eventual consistency which requires complex data migrations.

If you need to do a lot of these column changes, you should take a step back and ensure your bounded contexts are well designed (bad designs cause verbose inter-BC communication) or if you should not consider switching your architecture to API composition.

ArwynFr
  • 1,383
  • 7
  • 13
  • "If you need to add another column later, just handle that as a new migration with the same operations, no need to update anything." Well perhaps I would write a brand new script instead of updating the one used for the first migration, but my point is that it's going to require extra work beyond adding a migration to BC2 and updating the integration event handler. It would be nice if I could accomplish everything with a single PR in BC2. I would like to avoid writing ad-hoc scripts if I can. But if it goes against the principles of DDD, then I guess my proposed solution is a no-no. –  Feb 21 '22 at 01:00