3

I'm trying to understand and learn how to build microservice messages the best and came up with this task for myself:

Services given:

  • Accounts Service
  • Token Service
  • Email Service

When a user registers the account needs a token for the verification email. The token service will issue tokens with a life time that can expire. The registration email can only be send when a token was received. So we will have to wait for that to complete.

When the token was received, the aggregates states is updated and a command send to the Email service to send the registration email. We don't need to wait for that this is going to complete successfully but flag the account that the email was sent.

User Registration Saga

  • Status: TokenPending
  • Command: CreateToken -> Token Service (returns token)
  • Event: TokenCreated -> Account Aggregate listens, updates itself
  • Status: EmailPending
  • Command: SendEmail -> Email Service (returns success or failure)
  • Event: RegistrationEmailSent -> Account Aggregate listens, updates itself
  • Status: Completed

When and where would I create or use the Saga object? A saga seems to be another object in my system. The example on the microservices.io shows that the saga is created before the aggregate, is this correct? So after the saga is completed I would create my new account aggregate? The Saga itself looks like an aggregate. Could it be modelled as such? See the diagram example at the end of this post, its the orchestration example from microservices.io.

Is applying the Saga pattern this way a good solution for this made up scenario? The Saga Pattern. Are there other solutions to the given scenario that might be better? If yes, why?

enter image description here

floriank
  • 471
  • 2
  • 16
  • 2
    How does your concept of Saga deal with failures, or compensating actions? For instance, if the email fails to send, what happens to your process? If the user is already registered, how does that affect the state? – Zymus Jun 03 '21 at 15:33
  • Input validation should happen before that in my opinion. A validator could be attached to the route and when the route matches execute and respond with an API error message (fail early) on failure. The controller could use a validation service. Checking if the email exists would involve a query that goes to the read model, so it would not be related to the saga, nor the aggregate at all. The saga could do it as well and just return a result object containing errors. There are several options. :) My primary concern is actually getting the messaging between the services done right. – floriank Jun 03 '21 at 16:58

2 Answers2

1

There are a number of "shapes" to a process like this. You mention Sagas, others might say Process Manager. To some, a Saga is something that has compensating actions in case of failure (rolling back multiple transactions for instance). To others, a Process Manager is something that responds to different Domain Events, and keeps some amount of state to track what's happened, and what needs to happen. There's also other strategies dealing with this in the form of routing slips. This should give you some search terms to look into. For the rest of this answer, I'll try to use the terms you've chosen (Saga).

Your question seems to be primarily focused on the lifecycle of the Saga itself. I would say that I agree with you, when you say that a Saga is another object in your system. The flow you describe is reasonable. We can't send the verification email until a token has been generated. We can't generate the token until a user has submitted a registration.

I'm not familiar with the site you mention, but it isn't unreasonable for the Saga to be the initiating actor; the coordinator that knows what needs to happen, the order it needs to happen in, and when its job is done. It could indeed be modeled as an Aggregate, so long as we keep in mind that only one Aggregate should be modified in a single transaction.

When a user submits their registration (including at least their email address), a UserRegistration is created. It has a unique identity, which sets it apart from every other UserRegistration (maybe the email). If a UserRegistration already exists for the identity, we might want to let the user know that an account already exists (potentially offering a password reset link as well). If no matching UserRegistration exists, we need to generate a token, and send the email with the token.

If these services publish their Events through some message bus, then the UserRegistration would need to subscribe to the TokenCreated and RegistrationEmailSent Events. Whether you have a separate component that is subscribed to those Events, which then calls the appropriate method on the UserRegistration, or the UserRegistration is subscribed to them directly is up to you. So the Token service needs to either support being called directly by the UserRegistration object, or there needs to be some component between them that subscribes to UserRegistration Events, and calls the appropriate method on the Token service. The same is true for the Email service.

When the UserRegistration receives the TokenCreated Event, it records it as part of its state. It does the same for the RegistrationEmailSent Event. If you need a separate type of Aggregate for a registered user (maybe with profile information, settings, or something else), it can behave in the same way, either by listening to a UserRegistrationComplete Event, or as the final thing the UserRegistration does.

Zymus
  • 2,403
  • 3
  • 19
  • 35
0

I don't think that given services are a good match for a Saga patter. Personally I would merge Token and Accounts services in to one service in which case you will not need to apply distributed transactions. Is Token creation so complicated that it needs it's own service? Does it need to scale separetly from Accounts service?

In my understanding Saga is a pattern which spans multiple services, so it's not exactly an object per se, but while applying Saga trough orchestration you neend to create SagaManager, or ProcessManager class for managing the given flow and compensation actions.

SagaManager or ProcessManager are not domain objects so they are not aggregates like in DDD. But aggreate pattern/guideline from DDD is mostly about encapsulation and object responsibilities and usually you should use those principles while creating classes in your OO language.

[EDIT]

You don't need to persist Saga object in your store (but you can ;P), for such simple scenario you can have a flag in your Account aggregate, something like TokenPending, TokenCreated, EmailSent, etc. and you can update given Account state after each step, so that ProcessManager can be aware where to pickup Account creation.

SomeGuy
  • 76
  • 3