I have a question about the interactions between several microservices.
Assume we add another microservice to manage all the outgoing communications like emails and sms. Now, we want to send an email when an order is paid to inform the customer that we just get his payment and his order will continue to process (whatever the content of the mail, it's not the point here).
My question is: How to provide all the data that our communication service needs to populate the content of his emails and sms ? Is it the role of the integration events ? Or are they just there to inform that something happens ?
To continue with our example, the current implementation is that when an order is paid, the payment API publishes an integration event who contains the identifier of the order just paid, see the OrderPaymentSuccededIntegrationEvent.
So, in my opinion, we have two options here :
- The first one is to add all the data our email needs in the event (like the amount of the order, maybe the articles it contains, etc) so our communication service has just to catch and to handle this event, but it creates a strong relationship between the payment service and the communication service because if someday we want to add another information in our email, we will have to update the event the Payment API publishes.
- In the communication service, when he catches and handles the event, he makes some http calls to others services to get all the data he needs to generate the content of the email.
To me, the second option seems a much better choice than the first one but I didn't see any implementation like this in this project.
Which is the good way to do this respecting the philosophy of this project ?
Related to #484
There's no single right answer to this, IMO. It's a balance. When you raise integration events, the purpose is to inform other services/applications that something happened, so they can act upon it. At a minimum you can send just an event and some ID, like "CustomerUpdatedEvent (some customer id)". With this approach, your events are very small and you just assume that anybody interested in this event will make separate calls back to get whatever additional information they need. Pros: These events are very stable, and thus so is the code that raises them. Cons: Performance. This is the chattiest possible approach, since basically every event handler is going to need to immediately turn around and request more information.
On the flip side, you can send as much information as you know with the event. "CustomerChangedEvent(CustomerAggregateDTO)" which would include the fully serialized customer. Now the events are much bigger and every time customer changes you probably need to update the DTO and where you raise the event, and most handlers probably don't need everything in the Customer to do their work. Pros: Less chatty. Cons: Less stable; more expensive to raise and send events.
If you're doing purely speculative design, you may end up in one of these to cases. Usually, though, when you're raising an integration event you have a specific reason to do so (at least for the first one). So, a pragmatic approach is to optimize the event for this specific use case. In your case, you might include whatever would be required to send an email. This will give you a "goldilocks" not-too-chatty-but-not-too-big event optimized for the current use case. When (if!) the next use case comes along, you can consider whether the existing event is sufficient or if you need to add to it, on a case-by-case basis.
HTH.
Well, "no right answer, it's a balance" was the answer I was afraid of :)
To me the second option (keep the event the smallest as possible) is the one respecting the most the philosophy of this sample project, the payment API doesn't have to know that the payment of an order will trigger the sending of an email, but it's just a point of vue.
Thank's you for your time and your answer.
As a follow up, I was just curious what the intent was with Payment.API. Currently it is only passing an order id in the message. There is no buyer credit card information, so there is insufficient information to actually carry out a payment transaction. I understand that it was just a just an example, but was curious of best practices. Should the buyer information be passed in the message or should it retrieve it from the Order.API? Maybe the answer is either way has it's pros and cons, but was just curious what others thought.
Thanks!
This microservice was just an example of yet another microservice, but it is clearly not implemented as it is currently kind of a dumb microservice.
The Payment API should "own" anything related to the payment. Ideally its related database should have the credit card info or any other info related to Payment. Those attributes would be here instead of in the global User's data (ASP.NET Identity). The user's attributes should be split into multiple entities depending on the BOUNDED-CONTEXT and microservice.
You could also have:
Owning different user's attributes depending on the BC or microservice they belong to.
A different thing is that in some cases an Order might have a specific credit card to be used only for that transaction instead of using the "by default" credit card of a user. That depends on the business rules for each eCommerce. This reference sample is not trying to be a reference on eCommerce, only on microservices architecture implemented with .NET Core. Therefore, many use cases are not completed.
@CESARDELATORRE Thanks so much for your insightful response! I will keep that in mind as I go through the great examples in this repo.
Most helpful comment
There's no single right answer to this, IMO. It's a balance. When you raise integration events, the purpose is to inform other services/applications that something happened, so they can act upon it. At a minimum you can send just an event and some ID, like "CustomerUpdatedEvent (some customer id)". With this approach, your events are very small and you just assume that anybody interested in this event will make separate calls back to get whatever additional information they need. Pros: These events are very stable, and thus so is the code that raises them. Cons: Performance. This is the chattiest possible approach, since basically every event handler is going to need to immediately turn around and request more information.
On the flip side, you can send as much information as you know with the event. "CustomerChangedEvent(CustomerAggregateDTO)" which would include the fully serialized customer. Now the events are much bigger and every time customer changes you probably need to update the DTO and where you raise the event, and most handlers probably don't need everything in the Customer to do their work. Pros: Less chatty. Cons: Less stable; more expensive to raise and send events.
If you're doing purely speculative design, you may end up in one of these to cases. Usually, though, when you're raising an integration event you have a specific reason to do so (at least for the first one). So, a pragmatic approach is to optimize the event for this specific use case. In your case, you might include whatever would be required to send an email. This will give you a "goldilocks" not-too-chatty-but-not-too-big event optimized for the current use case. When (if!) the next use case comes along, you can consider whether the existing event is sufficient or if you need to add to it, on a case-by-case basis.
HTH.