Here are a few givens. First, we have the following Domain Event type:
Assuming that this Domain Event will be published as the payload of the following Message type:
What do you think? Is this the right way, an acceptable way, or utterly the wrong way to publish a Domain Event across the enterprise, or even further, to Internet clients?
Some find the approach completely acceptable. The Domain Event type is a completely unique name since it includes the fully qualified Java package name (or possibly the C# namespace). Using this as the message type helps make the message-based Event self-describing. Essentially the type is being used to define a specific resource type within a custom media type, or it may be thought of as a schema name. Either way, the message type is being used to name a data contract between the publisher of the Event and all subscribers, no matter how broadly distributed the messages may be.
On the other hand, some would argue that the Domain Event’s type should not be leaked into the enterprise, or possibly much worse, into the big world of many consumers across the Internet. Those against this approach might say that you are exposing the type of an object in your domain model; that is, you are giving away details of your implementation and even your platform and programming language. This camp might rather define a canonical name for the associated message, which is basically a level of indirection:
Note that I am unwilling to reduce the canonical name to ProductCreated alone, without a prefix. You might well imagine that clients/subscribers may be interested in messages of various sources of ProductCreated Events. The name prefix is important to describe what context this ProductCreated Event is from. I must add that if someone recommends that a canonical name of ProductCreated is sufficient, it would probably be best if the two of us not discuss this further.
Now, with these two approaches, let’s draw a comparison, both are essentially the same:
Both of these can serve as canonical names. The first approach is in fact a canonical name. Do you agree that the two are for all practical purposes the same? I think that the Event’s fully-qualified type name is a good canonical name. Of course some could come up with a lot less meaningful canonical name, for example “3” or “9571”. But seriously.
Those arguing against using the internal name of the Domain Event may state that by exposing the internal Event type you are forcing clients to tightly couple to your implementation. Well, that would be true if we were actually binding the fully-qualified type name to the message. But, alas, we are not. We just happen to use the same canonical name for the message that we use for our internally modeled Domain Event.
To prove the practicality of this approach, consider what would happen if we internally replaced our formerly modeled ProductCreated and named the new Event EnrichedProductCreated. Okay, admittedly that’s probably a bad name, but it’s only to make the point that the new Event is an enriched version of the old one. By enrichment it implies that the new Event supports 100% of the old Event’s data contract. We now have choices. We could decide to continue to publish the message as formerly:
Domain Event Type:
Or we could instead change the canonical name to match the new type:
The first approach would allow all existing clients that depended only on the original message type to continue to function unchanged. Obviously this has a big advantage. If instead we chose to use the new EnrichedProductCreated as the canonical name then all clients depending on ProductCreated are broken and must be modified to support the new message type. (Remember, new instances of ProductCreated are no longer published anywhere, although the Event type remains to allow existing instances to be loaded from the Event Store and even published to a brand new exchange as a matter of historical record.) Under this situation existing clients must be modified to support the new EnrichedProductCreated message type. Your mileage may vary, but this seems much less desirable since it involves coordinating releases of some number of separate systems, even if only inside the firewall.
However, another way of dealing with this situation is to instead version Domain Events by marking each new version with an increasing integer value. You start out, for example, with ProductCreated having an eventVersion attribute equal to 1. When you determine that ProductCreated needs to be enriched with additional properties, you increment the version to 2. Increasing the eventVersion does not negate the need to design in support for version 1 in its entirety. This is done by only adding new properties but not removing any of the original ones (or renaming some), which will allow existing clients to function without enhancing their code to understand the new properties of the enrichment.
Note that since most clients may continue to depend only on version 1 of a given Event, over time these clients may remain completely oblivious that the version may have increased substantially. The only clients that may care about the version of the Event are those that absolutely depend on a specific version (or higher) other than 1.
Of course if properties of the original ProductCreated are renamed or removed, things will not go so smoothly unless you design your message payload to support both old and new properties depending on the version of the Event that the client agrees to accept. This might warrant the use of Protobufs or some similar serialization format that supports versioning properties.
Now, this leads to the actual point of this post. What is just as important, or perhaps even more important than the canonical name of the message, is the canonical format of the entire message. What I am talking about is what has been called a canonical message model. It’s the data format of the message itself. But why might the use of a canonical message model be necessary?
If my DDD Bounded Context is implemented in Java, for example, we don’t want to force subscribers across the enterprise to have to consume messages by deserializing them into Java objects. Rather, we should allow the message subscribers to consume the messages on their own terms. If one subscriber is written in C# on .NET, and another one is written in Ruby and running on Rails, we should allow each of those to consume messages as C# objects and as Ruby objects, respectively. Actually, it would be even better to give each subscriber the ability to consume messages in any way they choose, even as name-value pairs if that’s what they want to do.
This means that the canonical message model or format that we choose should match client diversity with equal versatility.
What does this mean in practical terms? You could choose XML, JSON, or a more generic name-value format for your canonical message model. Or, as discussed previously, if there is a chance that your Event-carrying messages could have to support complex versioning, Protobufs may be a better option for your canonical model. Protobufs could also be a better option than XML or JSON if your messages must support a more compact payload.
In my opinion, we should not put so much emphasis on the message type name while losing focus on the whole message with its Event payload. The message itself should be just as carefully designed as the name is, if not more so.
The canonical message model I have been discussing here is precisely what is presented in Chapter 13, Integrating Bounded Contexts, of my book, Implementing Domain-Driven Design. In Chapter 13 I use the DDD Context Mapping name Published Language, yet it is a closely related concept. For more reading on a canonical message model, see the definition on Ward Cunningham’s wiki, which there is referred to as the Canonical Message Data Format. There are other Web resources that discuss this topic as well.
As a follow up, I am linking to an excellent discussion of this topic by Clemens Vasters of Microsoft. Our viewpoints align very closely, and his suggestion to document message data formats is similar, if not the same, as what I discuss in Chapter 13 of IDDD.