See the Intro to this series, which has links to all the parts.
Message types in our service bus are just regular .NET classes. We don't have a base message class or an interface that message types must implement. The only requirement of a message type stems from our use of WCF: it must have DataContract and DataMember attributes so that WCF can use the DataContractSerializer to send it over the wire.
In Part 3, I talked about transactional messaging and how those messages are handled differently than non-transactional ones. But how does a message type become transactional?
A developer declares whether a message type is transactional or non-transactional by convention: If the message type has a static bool property named IsTransactional, its value determines whether the message is tx. If the property does not exist, the default is non-tx. Developers must opt-in to make a message transactional.
A similar convention is used to determine message lifetime -- a static TimeSpan property named TimeToLive. Messaging is asynchronous, and we need a way to tell the system, "If a message of this type isn't handled within X time, consider it to have failed." The TimeToLive in our system defaults to 1 hour.
The default values for IsTransactional and TimeToLive are appropriate for our system, but will certainly not be appropriate for all systems. They form part of the contract for a message type, which is why we decided to bake them into the message type itself.
In Part 5, I'll talk about message handling failures, poison messages and dead letters.