Defining consistency boundaries

Hello, guys.

I’m having some trouble defining bounded contexts that aren’t just application level singletons for all but the most isolated use cases. For example, think about an invitation program where users can invite other people to our platform. There are the following restrictions in place:

  • Users can only have up to 5 invitations in-flight
  • The same person can’t be invited by two different users

The first requirement nudges me to create a UserInvitationsEntity, keyed by user id, so I can limit the number of invitations consistently. The second requirement makes me think about an InvitationRecipientEntity, keyed by recipient email, to enforce the one invitation per recipient constraint. The only way I can think of to enforce both constraints consistently is to define an InvitationProgramEntity that would encompass everything.

This scenario is only an example. This thought process to determine context boundaries led me all too often to define an entity that’s effectively a singleton, creating a bottleneck. I’d love to hear about your experience designing bounded contexts and heed any advice you might have to share.

Thanks a lot,

Seems like a single “User” aggregate would solve this. Users have several states. Invited and Active being the ones from these use cases, but you can imagine many others such as “suspended” or “deleted”.

One of the domain entities of a user might be their invitations (current and past).

When a user sends an invitation, it first validates that they have an empty slot. If so, it sends a message that either creates the new aggregate or is rejected as “already existing”. This enforces both your first and second rules. When a user completes their sign-up, it can send a message back to the originating user that frees up the slot.

Thanks for you reply, David! Using that scheme, an invite operation still needs to happen across two entities — the source and destination users of the invitation. Which of them would emit a persistence event?

Maybe I’m picturing this wrong; here’s what’s in my mind: a User persistent entity (that is, an EventSourcedBehaviorWithEnforcedReplies in Akka parlance) gets an AddInvitation command. This entity keeps invitation from the use in its state, so it can validate the upper limit on invitations isn’t exceeded; it can’t, however, enforce that the destination user wasn’t already invited by someone else.

The best I can think of right now is to add another layer of coordination and a compensating action for failure. Some kind of coordination service/actor would send the AddInvitation command to the source user; if successful, send a Create command to the destination user; if the latter fails because the user already exists, compensate that with a RemoveInvitation to the source user. Does that make sense?