Updated projection-events between microservices contexts

Let’s say I have two bounded contexts in my system, responsible for Products and Orders respectively.

In the Products context, I have services responsible for aggregates that listen to all events on product streams and keep a representation up to date, and can give current info about it. The same in the Orders context. Both separate and working well.

Then I want to make a read side service, let’s call it CartViewService that is able to give data about a not yet finished order with product information embedded, like { items: [{ product: 123, image: 'http://a.cdn/345.jpeg' }]}. That is a projection of projections from two different contexts.

My design thought here is that only the Orders context service should know about all the event types that can appear on an Order, and likewise for the Products context. So they would each make their projections (in service code, not in EventStore projection code, for reasons outside of this topic…). And the CartViewService, not knowing all event types, would just need to get notified on NewVersion of the Orders and Products in that order.

I’m hesitant of the best approach here. One solution I see would be that the aggregate services, after receiving a domain event (like new-product-photo), would publish an event on a PublicProduct-<uuid> stream with the event type NewVersion and the full projection of the product. That event could just go to a pub/sub message broker as well as EventStore.

Is this reasonable, or am I missing something obvious? I do NOT want the readside to need to know about all event types of products and orders or share code between these contexts.

Frederik,

Not sure if this is much help, but we have a similar system (product, orders and sales) and we have a single Projection that listens to $ce-Products, $ce-Orders and $ce-Sales. We keep track of what is happening with a Product (has it been ordered, delivered, stock changes) and emit a new event to a stream that our read model subscribes to.

So you use a design where the read model doesn’t have to know anything about the granular domain events, but can just get new versions of the full model whenever updated?

We get an event informing us “something” happened to our product.
The read model then picks this and updates the database.

I have put an example of this event:

{
  "storeProductId": "78264b26-151c-9016-614f-4d8bd641672a",
  "ActivityDateTime": "2021-11-04T08:41:48.473Z",
  "storeId": "6b73ff1c-3438-2cb5-1382-dd177095bf5a",
  "organisationId": "3a382f25-a864-4a6f-b04f-e4274ab495a9",
  "CurrentStockLevel": 75,
  "organisationProductId": "0db34c0a-e1e4-6461-4ecd-707466aca8d0",
  "IsSale": true,
  "salesTransactionId": "65c3facd-7018-da22-23cc-8ba26effeb08",
  "NumberOfItemsSold": 1,
  "state": {
    "lastSale": "2021-11-04T08:41:48.473Z",
    "lastDelivery": "2021-09-18T09:38:00Z",
    "lastOrder": "2021-11-01T19:00:46.33Z",
    "currentMpl": 3,
    "previousMpl": 0,
    "lastMplChangedDate": "2021-10-14T10:03:10.247Z",
    "outOfStockSince": "2021-10-18T08:14:08.023Z",
    "lastTimeOutOfStock": "2021-10-18T08:14:08.023Z",
    "numberOfTimesOutOfStock": 2
  }
}

There are various optional fields (regarding order / deliver etc) that are only included when the domain event was related to that.

1 Like

@fredrik, for sure, exposing all the granular events to other services is not an approach that I’d recommend. This ends up with leaking abstraction and making changes harder. I recommend defining what’s needed to be published from a module in the global context. So think from 2 perspectives: specific module and system-wide.

For example, the payment module doesn’t have to know all the details of completing the order (e.g. adding, removing items). It’s interested only in the confirmed order. If the payment module needs to gather all of the information from the granular events, each change to this process could potentially impact payment processing.

Thus, it’s good to enrich and transform the granular internal events into external information. It doesn’t have to be just a state snapshot, best if it’s an event that gathers specific business information. Still, pragmatically if we’re just synchronising data in read models, then what @steven.blair suggested may be, of course, a good option.

See the example enricher: https://github.com/oskardudycz/EventSourcing.NetCore/blob/b480d9dab7703fa71ae46314c8810ae9ce8d9a42/Sample/ECommerce/Carts/Carts/Carts/FinalizingCart/CartFinalized.cs#L65. It listens to CartConfirmed internal, granular event, gets additional data and builts external event with additional data.

I also wrote longer on that topic: https://event-driven.io/en/events_should_be_as_small_as_possible/