Reputation: 1634
In domain driven design we create bounded contexts. Which means that aggregates don't share a common model.
This image from Microsoft shows how aggregates communicate using commands and events (source).
Assuming those aggregates are at least two different microservices coded in different repositories. How should they share the scheme definition for the commands and events they communicate with? In my case those applications are .net core applications, so serialization and deserialization compatible with .net would be appreciated.
Also, maybe I'm missing some point as I'm new to domain events and communication works different, then I would like to know, what I'm not seeing.
Upvotes: 3
Views: 1337
Reputation: 2290
Each service can have an internal model and you can use a Canonical Data Model for the communication between the services.
It's common to have a separate model used for integration that the internal model a service uses.
You can also check all the patterns in enterpriseintegrationpatterns.com and get the book. It's highly recommended.
Edit:
Here's an example with the order system. I'll skip some of the data that the objects will have to simplify the example and try to concentrate on the important parts i.e. the CDM and the internal model.
NOTE For a simple example, it's always difficult to justify some of the decisions as most of the things will be a lot simpler with the additional stuff. The implementation is done this way to help with the example.
Before I continue: Having a CDN will have some overhead in translation from the internal to the external model. If you think you can go without it, do so. Do not make things more complex than they need to be. The problem here is that if you need to change a command or event, this will propagate to all services and will cause a huge ripple effect in your system. The Domain-Driven Design book has a section on the Anti-Corruption Layer. Do check it out.
Why do we use a CDM? For Encapsulation. The same way an object encapsulates its data, the service encapsulates its internals. The CDM is intended to be used only for the communication/integration between the services and is intended to be shared between them.
Sharing the Internal Model of a Service with other Services is a bad thing because it will lock the developers from changing this Internal Model. Also sometimes details from one service can leak to other services and can cause huge problems.
Sharing a Special Data Model designed only for the communication between services is a good thing because it enforces a well-defined model for the communication and your system won't become a mess of events and commands with unknown schemas where every Service posts and consumes whatever it likes. I've seen those kinds of horrors, trust me you don't want that!
This model should be designed at a higher level of abstraction: The system level, having it's processes and flows in mind. It should be void of any details about the internals of the individual services.
What you need is Translation between the internal and external models. Also, you can use ContentFilter and ContentEnricher if you need to filter incoming events or commands and add more data to outgoing events or commands.
// Canonical Data Model
namespace CDM {
public interface ICommand { }
public interface IEvent { }
public class CustomerInfo {
public Guid Id { get; set; }
// notice here the CDM uses the two separate properties for the first and last name
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class LineItemData {
public Guid ProductId { get; set; }
public Quantity Quantity { get; set; }
public Money ListPrice { get; set; }
}
public class PlaceOrderCommand : ICommand {
public CustomerInfo CustomerInfo { get; set; }
public IReadOnlyList<LineItemData> LineItems { get; set; }
}
public class OrderPlacedEvent : IEvent {
public Guid OrderId { get; set; }
public IReadOnlyList<LineItemData> LineItems { get; set; }
}
} // end Canonical Data Model namespace
// Order Service Internals
// the name is done this way to differentiate between the CDM
// and the internal command, do not use it this way into production
public class LineItem {
// the internal one includes the OrderId { get; set; }
public Guid OrderId { get; set; }
public Guid ProductId { get; set; }
public Quantity Quantity { get; set; }
public Money ListPrice { get; set; }
}
public class PlaceOrderInternalCommand {
public Guid CustomerId { get; set; }
public string CustomerFullName { get; set; } // a single full name here
public IReadOnlyList<LineItemData> LineItems { get; set; }
}
public class Event { }
public class OrderPlacedInternalEvent : Event {
public Guid OrderId { get; set; }
public IReadOnlyList<LineItem> { get; set; }
}
// this is part of the infrastructure, received messages and translates/transforms
//them from the external CDM to the internal model.
// This is the input/receiving part of the service
public class CommandDispatcher {
public void Dispatch(ICommand cmd) {
// here we will use a MessageTranslator, check PatternsUsed section
var translator = TranlatorsRegistry.GetFor(cmd);
var internalCommand = translator.Translate(cmd);
Dispatch(internalCommand);
}
}
public class CommandHandler {
public void Handle(PlaceOrderInternlCommand cmd) {
// this will create the OrderCreated event
var order = CreateOrder(cmd);
// this will save the OrderCreated event
OrderRepository.Save(order);
}
}
// Another part of the infrastructure that publishes events.
// This is the output/sending part of the service
public class EventPublisher {
public void Publish(Event event) {
// another usage of the MessageTranslator pattern, this time on events
var translator = EventTranslatorRegisty.GetFor(event);
var cdmEvent = translator.Translate(event);
// publish to kafka or whatever you are using
PubilshToMessageMiddleware(cdmEvent);
}
}
Patterns Used:
Canonical Data Model MessageTranslator CommandMessage EventMessage
Upvotes: 4