dotnet-architecture / eShopOnContainers

Cross-platform .NET sample microservices and container based application that runs on Linux Windows and macOS. Powered by .NET 7, Docker Containers and Azure Kubernetes Services. Supports Visual Studio, VS for Mac and CLI based environments with Docker CLI, dotnet CLI, VS Code or any other code editor. Moved to https://github.com/dotnet/eShop.
https://dot.net/architecture
24.53k stars 10.36k forks source link

Question: Microservice A handles Event published by Microservice A #792

Closed w00519772 closed 5 years ago

w00519772 commented 6 years ago

Thank you for developing eShopOnContainers - it is a very helpful reference application.

I am trying to understand how this architecture works with CQRS when there is a write store e.g. event store or sql server etc and a read store e.g. MongoDB.

Is it "acceptable" for a Microservice e.g. 'Microservice A' to publish an integration event and then handle the integration event (when there is no other Microservice that handles it)?

CESARDELATORRE commented 6 years ago

We're not using the full CQRS approach of having different databases/stores for reads-database or writes-database. CQRS just means separation between reads/queries and writes/transactions. In many cases you don't need to have different stores/databases as that is a further complexity you might not need (In some cases you might need it, but we are not showcasing those cases because are kind of more 'rare' cases).

We do CQRS(what I call simplified CQRS) in the Ordering microservice: Queries on one hand and transactions triggered by Commands and Mediator pattern, but both are working, in this case, against the same SQL database.

Not sure I understand your last question. An 'Integration Event' should be used to integrate other microservices or external applications, not to be used just by a single microservice. If you want to use events within a single microservice, use "Domain Events", also explained in the related book/guide: https://aka.ms/microservicesebook

Hope it helps. 👍

w0051977 commented 6 years ago

If you want to use events within a single microservice, use "Domain Events", also explained in the related book/guide: https://aka.ms/microservicesebook Hope it helps.

@CESARDELATORRE, thank you for replying.

I realise you're not using the full CQRS approach. However, say I wanted to follow the full CQRS approach. I would have to make sure the read model and write model are in sync.

Your OrderContext.SaveEntitiesAsync looks like this:

public async Task SaveEntitiesAsync(CancellationToken cancellationToken = default(CancellationToken))
 {
 await _mediator.DispatchDomainEventsAsync(this);
        var result = await base.SaveChangesAsync();

        return true;
    }

If I use a Domain Event as you suggest then my Domain Event Handler would contain code like this: mongoCollection.Save(object);

What happens if the server crashes immediately after calling: mongoCollection.Save(object)? It would mean the record exists in the read database (MongoDB), however it does not exist in the write database (SQL Server)? The write database should be the single source of truth (I believe).

I am wandering if I need to create a message queue/bus inside the microservice. If I did this then there would be one message bus for communication between Microservices (via integration events) and another message queue/bus inside the Microservice to keep the read model and write model in sync.

Thanks again.

eiximenis commented 6 years ago

Hi @w00519772

My approach would be something like following, Assuming an event SomethingHappenedEvent is raised:

  1. Event handler handles this event
  2. Write store is updated
  3. A new event SomethingHappenedEventProcessed is fired
  4. Another handler handles this new event and
  5. Updates read model

Of course there is a period of time, when write model is updated and SomethingHappenedEventProcessed is not still processed, so Read and Write model are not in sync. Same could happen if step 4 fails and needs to be retried. You have to deal whis this :)

Note that event handler in 3 and in 5 do not need to belong to the same microservice (it depends on how you choose your microservice granularity).

w0051977 commented 6 years ago

Same could happen if step 4 fails and needs to be retried. You have to deal whis this :)

Thanks for replying eiximenis. How would I deal with this? (that is the point of the question) I can think of lots of ways:

1) The microservice has its own durable message queue. There is a queue for writes and a queue for reads. 2) A background task runs periodically to look for missing events.

What is the standard way of approaching this (if there is one)?

CESARDELATORRE commented 6 years ago

For full CQRS with two databases is perfectly fine (and safer) to use the Event Bus based on durable messages in RabbitMQ or Azure Service Bus or higher lever service buses like NSERVICEBUS. In order to handle issues like one message not processed or properly sent, you need to use patterns like “Outbox pattern”. We have that in eShopOnContainers with the EventLog. An additional background task could be looking for unprocessed events. Note that higher level service buses like NServiceBus already provide these patterns out of the box. NServiceBus was built by Udi Dahan and his team specifically with full CQRS in mind. Finally, another approach is to just use an EventStore if you also want to do Event Sourcing, which is a related pattern.

w0051977 commented 6 years ago

In order to handle issues like one message not processed or properly sent, you need to use patterns like “Outbox pattern”.

Thanks again. I am almost there. I will do some more reading about the Outbox pattern.

My understanding is that the write model is updated and an event is published to update the read model.

Q1) Is the event published to update the read model an integration event (as opposed to a domain event)? Q2) Is the read model infrastructure code (repositories, context etc) and write model infrastructure code contained in the same microservice? I believe it is. Q3) There is an event bus that is used to integrate microservices/other applications: https://github.com/dotnet-architecture/eShopOnContainers/tree/dev/src/BuildingBlocks/EventBus. Is this bus used to publish and subscribe to CQRS events or would you create another event bus inside the microservice for this?

CESARDELATORRE commented 6 years ago

For full CQRS with multiple databases, use Integration Events. That gives you more flexibility to have a different service in charge of processing the integration events. You could have and I recommend two different services for this, although from a concept perspective are the same “Business Microservice” or Bounded Context, similar to the Ordering microservice which is composed my several physical services. You can use the same Event Bus for integrating a “Reads-Database”. It is exactly the same concept we use but integrating different microservices.

w00519772 commented 6 years ago
     You could have and I recommend two different services for this, although from a concept perspective are the same “Business Microservice” or Bounded Context

If you were to do this with the Ordering Microservice then I assume you would have separate projects for:

Ordering.Read.API Ordering.ReadModel Ordering.Write.API Ordering.Domain Ordering.Read.Infrastructure Ordering.Write.Infrastructure

Is that right?

, similar to the Ordering microservice which is composed my several physical services.

The Ordering Microservice is made up of three services i.e. API; Background Tasks and Signal R. Is that what you mean by: "composed my several physical service"?

Finally, would the read model

1) contain the same fields as the write model (represented differently of course) or 2) contain different fields i.e. pick and choose what fields to include in the read model and which to include in the write model - similar to what you have done with the Location Repository i.e. there could be fields contained in the write model that do not exist in the read model.

Thanks for answering. I have experience with CQRS, but not really where there are two separate databases.

CESARDELATORRE commented 6 years ago

Not exactly. Usually the complexity of internal DDD patterns are only needed for the transactional area which is the “Writes” area.

So, probably something like: Ordering-WRITES/TRANSACTIONAL

Ordering-READS/QUERIES side

QUESTION: The Ordering Microservice is made up of three services i.e. API; Background Tasks and Signal R. Is that what you mean by: "composed my several physical service". --> YES

QUESTION: The Ordering Microservice is made up of three services i.e. API; Background Tasks and Signal R. Is that what you mean by: "composed my several physical service". --> YES

QUESTION: Finally, would the read model

  1. contain the same fields as the write model (represented differently of course) or
  2. contain different fields i.e. pick and choose what fields to include in the read model and which to include in the write model - similar to what you have done with the Location Repository i.e. there could be fields contained in the write model that do not exist in the read model. 
    --> Here you have to just put in the READS model the attributes you need from the client app and in the structure you need for the client app. Could be the same attributes, less or even more if you are somehow calculating or getting new data especially made for the reads/queries.

Hope I helps, Cesar

w00519772 commented 6 years ago

We have that in eShopOnContainers with the EventLog

Thanks again. Could you elaborate on that please? Perhaps refer to some code.

mvelosop commented 5 years ago

It looks like the conversation ended, so I'm closing this issue now. Feel free to comment, will reopen if needed.