Software Design Blog

Journey of Rinat Abdullin

Improved DSL Syntax for DDD and Event Sourcing

As you probably already know, we are currently finishing Salescast2 project internally at Lokad (cloud-hosted big-data analytics platform for retail). This project benefits from the latest development improvements in the area of DDD/CQRS applications with event sourcing (and drives some of these improvements back into the open source samples that we share).

One of the side effects of event sourcing (in addition to ability to ditch all SQL dependencies, decrease maintenance costs, reduce migration problems and improve performance) is that there is a large number of event and command classes. These classes tend to change quite frequently during the development as well (it is a part of DDD modeling process).

To simplify generation of these commands we use real-time code generation of these contract classes from a compact syntax. Below I’ll introduce the concept to new developers and also explain recent changes and improvements including support for syntax colors.

By the way, as an alternative to running a DSL.exe in background, you can plug the syntax into T4 code generator. But this is a bit too constraining in my opinion

In reality this works like this. When I plan to start changing some contracts (or adding new ones) in my project, I launch DSL.exe project and leave it running. From now on, whenever *.ddd file is changed, corresponding code contract files are changed. ReSharper immediately picks up the changes. Also any breaking changes will prevent code from compiling.

Lokad DSL generator (which is included in Lokad Sample Project as source code) will also make sure that resulting classes have proper array initialization and attributes for work with DataContract and ProtoBuf serializers. Generated classes are implemented as immutable.

In essence, majority of the template code looks like this:

CreateUser? (security)
    explicit "Create user {id} for security {security}"

    UserCreated! (security, TimeSpan activityThreshold)
        explicit "Created user {id} ({security}) with threshold {activityThreshold}"

ReportUserLoginFailure? (DateTime timeUtc, string ip)
  explicit "Report login failure for user {Id} at {timeUtc}"

Editing experience looks like this:

And results look like this:

[DataContract(Namespace = "Lokad.SaaS")]
public partial class UserCreated : IEvent<UserId>
{
    [DataMember(Order = 1)] public UserId Id { get; private set; }
    [DataMember(Order = 2)] public SecurityId SecurityId { get; private set; }
    [DataMember(Order = 3)] public TimeSpan ActivityThreshold { get; private set; }

    UserCreated () {}
    public UserCreated (UserId id, SecurityId securityId, TimeSpan activityThreshold)
    {
        Id = id;
        SecurityId = securityId;
        ActivityThreshold = activityThreshold;
    }

    public override string ToString()
    {
        return string.Format(@"Created user {0} ({1}) with threshold {2}", Id, SecurityId, ActivityThreshold);
    }
}

Plus you get autogenerated (and auto-updated) interfaces like this:

public interface ISecurityApplicationService
{
    void When(CreateSecurityAggregate c);
    void When(CreateSecurityFromRegistration c);
    // ... skipped
    void When(AddPermissionToSecurityItem c);
}

public interface ISecurityState
{
    void When(SecurityAggregateCreated e);
    void When(SecurityPasswordAdded e);
    // ... skipped
    void When(PermissionAddedToSecurityItem e);
}

If you inherit a class from such interface, implementing actual method is just a few key-strokes away (“implement missing member”), plus compiler will not let you forget to handle these commands or deal with events in an aggregate.

Syntax of DDD files is documented rather well in the Sample project: messages.ddd

Changes

If you were tracking progress of this blog, then you are familiar with the previous version of the DSL syntax approach. Well, it changed slightly since then:

  • let keyword became const;
  • using keyword became if;
  • entity keyword became interface;
  • added namespace for defining namespace of the generated file (e.g.: namespace Company.Product.Module;);
  • added using keyword for importing additional namespaces;
  • added extern keyword for defining data contract namespace for all generated contracts;
  • added explicit keyword (and special syntax) for defining ToString() in-place;
  • made sure that Visual Studio provides nice syntax coloring.

In order for the syntax coloring to work, we simply abuse C# or C++ syntax highlighters built into visual studio. Here’s how you set them up:

The default way is to associate ddd files with C# editor. However, if you don’t like C# editing experience or want to be able to define highlights for your own custom types, you can also associate ddd files with C++, if you have essentials installed (more info). This way you can add highlights for value objects and identities specific to your projects.

How does this look? :)