This week I may take a break from these posts for a few days due to the Summit and Alt.Net but I should still push a few posts…
Messages as we will see are an interesting breed of objects as the same object will sometimes have differing connotations in different sections of code. In the domain messages are to be treated as value objects even though they may be treated differently later by certain types of infrastructure. There a a few aspects of being a value object that are imperative to a successful implementation of a messaging system within the domain. By looking at the general rules for value objects we can derive many of the rules for messages within our domain.
- Messages ARE immutable
- Messages should vary in equality based upon the data they hold
- Messages should have fluent builders
Messages ARE immutable…
I don’t think that I can stress this point enough. Mutable messages are an anti-pattern. They are the path to a system that is held together with duct tape and bubble gum. There are many reasons why this is the case but the simplest reason is that you suddenly end up with different semantics when you cross a serialization boundary.
Let’s imagine for a moment the ridiculous case of a simple service that receives a message, alters a field in it then sends an ack that it completed its work (the caller keeping a copy of the message checks for the changed field when it receives the ack). Even worse it does something to the message that causes the message to say raise an event back to the caller. These sound like ridiculous cases that no one would ever do but I have seen both done. Either will work just fine so long as you are in the same process but will break once you introduce a serialization boundary.
Let’s make an additional rule to get help this issue (please,never need this rule): You should never hold a reference to a message after you send it. As far as you are concerned, once you dispatch it, its gone. We will learn later that if at all possible we also want to limit our exposure to any shared state between the message sender and receiver as it is this shared state that limits scalability.
Another reason that mutable messages are evil is that you never actually know that the message is in a valid state. The use of immutable messages introduces a stronger contract for your messaging as you know that if a message exists that it is also in a valid state. You will never have a SendEmailMessage that is missing a To and From unless you specify that that is a valid way of sending an email in your system. Because you know that your messages are in a valid state you can in the case of using a shared library for messaging fail early on your requests i.e. instead of whatever processes the SendEmailMessage saying “Hey guy you need to actually send me someone who this email is to!”, the thing sending the message fails to create a valid message. This is hugely beneficial because what if the thing processing the SendEmailMessage is actually on another system? Not only have we transported an invalid request across the wire but we now also have to route an error back to the sender!
What we have done by making our messages in an assumed valid state is allowed other code to operate upon them while making assumptions that the message is meeting its minimum contract. In other words, the thing processing the SendEmailMessage does not need to actually check that the fields in the SendEmailMessage are what it needs them to be unless it is requiring something that is above the specified contract of the message (let’s say that it requires something the message defines as optional). The SendEmailMessage has been given the responsibility of insuring that it is always in a valid state.
A side benefit of this is that when dealing with messaging to infrastructure services the invariants you maintain to them are part of your messages which are part of your ubiquitous language as opposed to being implemented in the code handling the messages (infrastructure).
I like to think about using validated messages in this way in relation to design by contract. My code that sends the email doesn’t just work on a message that defines data layouts, the message also defines invariants about my data! These can be quite simple (To is not null) or they can be extremely complex like To must be a valid email address that is not at gmail.com. This becomes extremely valuable later when we go to deal with the consumption of messages; one of the biggest problems with interfaces and testing with .NET is that you cannot specify enough information to narrow your contract. You should therefore to be precise test the entire surface of the interface for every implementation (yuck, these tests add up). It is far easier to introduce the message that defines the invariants and then assume them to be true in your code.
Quite quickly we end up thinking about a contract first method of development for these types of things. In other words that we create our message objects before we create the code that sends or processes them. I personally like this method of development a lot for messaging based systems.
Serialization can cause issues with your messaging if you deal with un-trusted clients (versionization problems can cause this as well) as if you use default serialization it can (and will) deserialize messages that are not valid according to say the logic you apply in their constructor! If you are running with untrusted clients you should implement ISerializable on your messages to take control of the serialization process for them so you can insure they are only created in a valid state. For a large system you may want to consider moving this behavior to a “message” layer super type or consider mixing in an interface/implementor pair for ISerializable if you are using compile time AOP
We will look later why but never use runtime AOP on your messages or on the handling functions, its slow, redundant, and way too complicated.
Messages should vary in equality based upon the data they hold
Why someone would feel the need to compare messages for equality in a domain context is completely beyond but …. If you do, do consider them to be varying based upon the data that they hold not upon their reference. Beyond that if you can come up with a reason to do it I would love to hear it
Messages should have fluent builders
This is something I believe may be controversial and this post is getting a bit long so I will finish this part as DDDD 5 [Messages have FluentBuilders] on the bus to the summit today.