The journeylism of @yreynhout

On CQRS, DDD(D), ES, …

Message contract DSL

A long time ago, I read this post by Rinat Abdullin on teaching Microsoft Visual Studio your own language. Unfortunately, at the time I was struggling with more basic CQRS concepts and so it got somewhat pushed to the background. Today, however, after authoring the gazillionth command and event by hand, I knew something had to change. This was just too tedious, manual labor.
Granted, it only takes 5 to 10 minutes at first to define a command or event. But as you progress, you start refactoring towards composable types, creating builders for those types, and before you know it, you spent 45 minutes on datastructures. Not my idea of productivity.
As I reread Rinat’s article, I knew I had reached that point where automation needs to kick in (it just took me a little longer). I wasn’t particularly keen on using ANTLR and an AST to define my grammar, so I started looking for alternatives. The usual suspects passed by: xml (too verbose), json (better, but still too verbose), yaml (dismissed because of lack of familiarity – your mileage may vary). I continued hacking in notepad until I hit something that looked pretty much like the example below.

[MakeDogBarkCommand]
DogId=Guid
Times=Int32

[DogBarkedEvent]
Id=Guid
Version=In64
NameOfDog=String (Called)
Times=Int32 (ForNumberOfTimes)

If you’ve been developing on Microsoft Windows systems for some time, this format should look familiar. Yeah, it’s good old INI file format with conventions sprinkled all over it. Section names denote types such as events, commands, datastructures, enumerations. Key names denote properties of the type, while the values denote each property’s datatype. There’s no translation here, those are just native .NET types or your own (a big plus compared to all the type conversion between xml/json and the .NET type system). Conventions applied include: anything that ends with ‘Event’ is an event, anything that ends with ‘Command’ is a command, anything that ends with ‘Fragment’ is a fragment to be inserted at the location of reference, anything that has a number for each key value is an enumeration, anything else is just a datastructure, values with a ‘type (name)’ signature denote a property type and a custom name to be used as builder method name.

If you’re using your events and commands directly as interop datastructures, this approach might not work for you.

The beauty of this technique lies in the fact that you can define a type and reuse it pretty much everywhere, thus composing commands and events from existing types. It doesn’t take a genius to figure out that you can go from the above message contract definition to the code below.

[Serializable] [ProtoContract]
public class MakeDogBarkCommand : ICommand {
  [ProtoMember(1)]
  public Guid DogId { get; private set; }
  [ProtoMember(2)]
  public Int32 Times { get; private set; }

  MakeDogBarkCommand() {}
  public MakeDogBarkCommand(Guid dogId, Int32 times) {
    DogId = dogId;
    Times = times;
  }
}

[Serializable] [ProtoContract]
public class DogBarkedEvent : IEvent {
  [ProtoMember(1)]
  public Guid Id { get; private set; }
  [ProtoMember(2)]
  public Int64 Version { get; private set; }
  [ProtoMember(3)]
  public String NameOfDog { get; private set; }
  [ProtoMember(4)]
  public Int32 Times { get; private set; }

  DogBarkedEvent() {}
  public DogBarkedEvent(Guid id, Int64 version, String nameOfDog, Int32 times) {
    Id = id;
    Version = version;
    NameOfDog = nameOfDog;
    Times =   times;
  }
}

public class DogBarkedEventBuilder : IEventBuilder<DogBarkedEvent> {
  String _nameOfDog;
  Int32 _times;

  public DogBarkedEventBuilder Called(String value) {
    _nameOfDog = value;
    return this;
  }

  public DogBarkedEventBuilder ForNumberOfTimes(Int32 value) {
    _times = value;
    return this;
  }

  public DogBarkedEvent Build(Guid id, Int64 version) {
    return new DogBarkedEvent(id, version, _nameOfDog, _times);
  }
}

Below is a more advanced example demonstrating the use of arrays, enumerations and composing a type from fragments, other custom and native types.

[AddProductSetToCartCommand]
CartId=Guid
ProductSet=ProductSet

[ProductSet]
Id=Guid
Name=String
Items=ProductSetItem[]

[ProductIdNameCategoryFragment]
ProductId=Guid
ProductName=String
ProductCategory=ProductCategory

[ProductSetItem]
Id=Int32
Product=ProductIdNameCategoryFragment
Amount=Int32

[ProductCategory]
Toys=1
ComputerGames=2
DVDs=3
CDs=4

As a side-note, to make the transition to this DSL a little easier, I threw together a bit of reflection-based code over my existing events/commands to spit out a file in the INI format.

The thing that stood out the most for me is the terseness with which a message contract can be authored. Combined with conventions and old-fashioned code generation this can be a real time-saver.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: