Home > Articles > Programming

  • Print
  • + Share This
This chapter is from the book

Format Indicator

formatindicator_icon.jpg

Use a Format Indicator to specify the current compositional definition of a given Message (130) type. This technique is discussed in the “Integrating Bounded Contexts” chapter in Implementing Domain-Driven Design [IDDD] by using a Format Indicator as part of a Published Language [IDDD].

When a Command Message (202), a Document Message (204), or an Event Message (207) is first defined, it contains all the information necessary to support all consumers. Otherwise, the systems depending on the given message—in fact, depending on the many messages needed for a complete implementation—would not work. Yet, within even a short period of time any given message type could fail to pack all of the current information for the changing requirements. I’m not limiting this discussion to just one system but possibly many that are integrated.

Over time, there is simply no way that the original definition of all solutionwide messages will remain unchanged. As requirements change, at least some messages must also change. As new integrating systems are added to the overall solution, new messages must be added, and existing messages must be refined. The use of a Format Indicator, as shown in Figure 6.8, can ease the tension between systems that can continue to use the original or earlier format and those that force changes and thus must consume the very latest definition.

Figure 6.8

Figure 6.8 Use a Format Indicator to specify the current compositional definition of a given Message (130) type.

As Enterprise Integration Patterns [EIP] asserts, some systems can continue to support the original format of any given message. Even so, newer integrators or subsystems with more demanding refinement goals will force existing message types to be enhanced. Quite possibly no two teams involved in the overall solution development will be able to agree on synchronized release dates, let alone merging the schedules of every team involved.

So, how does a Format Indicator work? Enterprise Integration Patterns [EIP] defines three possibilities, and I add a fourth, shown here:

  • Version Number: This approach is discussed in Implementing Domain-Driven Design [IDDD]. Each message type embeds a version number as an integer or a text string. The version allows consuming systems to branch on deserialization or parsing1 logic based on the indicated message format. Generally, at least some, if not most, of the consuming systems may be able to ignore the version number as long as all message changes are additive rather than subtractive. In other words, don’t take current correct information properties away from working subsystems; only add on newly required properties.
  • Foreign Key: This could be the filename of a schema, a document definition, or other kind of format, such as "messagetype.xsd". It could be a URI/URL or some other kind of identity, such as a key that allows for a database lookup. Retrieving the contents of what the foreign key points to would provide the format’s definition. This may be less effective since it requires all message consumers to have access to the location that the foreign key points to.
  • Format Document: Use this to embed the full format definition, such as a schema, into the message itself. This has the obvious size and transport disadvantages when the containing message must be passed between systems.
  • New Extended Message Type: This approach actually doesn’t modify the older message format at all but instead creates a new message that is a superset of the previous message format. Thus, all subsystems that depend only on the original/current version of a message will continue to work, while all subsystems that require the new message can recognize it by its new and distinct type. The new message type name may be closely associated with the one that it extends. For example, if an original Event Message (207) is named OrderPlaced, the newer extending message could be named OrderPlacedExt2. Adding an increasing digit at the end of the message name will allow it to be enhanced multiple times.

The following uses the Version Number approach to enhance the ExecuteBuyOrder Command Message (202):

// version 1
case class ExecuteBuyOrder(
    portfolioId: String,
    symbol: String,
    quantity: Int,
    price: Money,
    version: Int) {
  def this(portfolioId: String, symbol: String,
                quantity: Int, price: Money)
    = this(portfolioId, symbol, quantity, price , 1)
}

// version 2
case class ExecuteBuyOrder(
    portfolioId: String,
    symbol: String,
    quantity: Int,
    price: Money,
    dateTimeOrdered: Date,
    version: Int) {
  def this(portfolioId: String, symbol: String,
           quantity: Int, price: Money)
    = this(portfolioId, symbol, quantity,
           price, new Date(), 2)
}

Version 1 of the ExecuteBuyOrder message specifies a total of four business properties: portfolioId, symbol, quantity, and price. On the other hand, version 2 requires a total of five business properties: portfolioId, symbol, quantity, price, and dateTimeOrdered. The design of both versions of ExecuteBuyOrder allows clients to construct both versions passing only four parameters.

val executeBuyOrder = ExecuteBuyOrder(portfolioId, symbol,
                                      quantity, price)

In version 2, the dateTimeOrdered is automatically provided by the constructor override. The Format Indicator version adds an additional property to each of the message types. An overridden constructor on each version allows for the instantiation of ExecuteBuyOrder with the version indicator defaulted to the correct value, either 1 or 2.

Since this is a Command Message (202), you can assume that it is the defining and consuming subsystem (one and the same) that requires the new dateTimeOrdered property to be provided. Yet, it can still support both versions of the message by providing a reasonable default for all version 1 clients.

class StockTrader(tradingBus: ActorRef) extends Actor {
  ...
  def receive = {
    case buy: ExecuteBuyOrder =>
      val orderExecutionStartedOn =
        if (buy.version == 1)
          new Date()
        else
          buy.dateTimeOrdered
      ...
  }
}

Although all version 1 clients will have their buy orders executed based on a slightly inaccurate orderExecutionStartedOn date and time value, they can continue to function with the enhanced StockTrader actor. It is likely, however, that version 1 of ExecuteBuyOrder will be deprecated and all clients will have to update to version 2 by some near-term cutoff.

  • + Share This
  • 🔖 Save To Your Account