Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CQRS Commands and Events as generic classes?

Tags:

cqrs

In most examples I saw, commands and events are represented as classes. That means you have to write a CorrectNameCommand class with name property and a NameCorrectedEvent class with name property. Given that both commands and events are serialized and deserialized in most cases and send to other parties (there goes the compile time type safety), what is the advantage of this explicit classes over a more generic class?

Example:

A Command class with a Name (that represents the type of the command), the key of the ag that should handle the command and an array of objects or name/value pairs for any other parameters.

An Event class essentially the same (perhaps we can put the shared parts in an CommandEventBase class).

The command (and event) handlers have now to check the name of the command (event) instead of its class type and have to rely on the correctness of the parameters in the list (like the deserializer has to rely that the serialized format is correct).

Is that a good approach? If yes, why is it not used in the samples and tutorials? If not, what are the problems?

like image 768
Holger Thiemann Avatar asked Jun 30 '15 13:06

Holger Thiemann


1 Answers

Duplication

It's a fair point that when Commands and Events are serialized, compile-time safety is lost, but in a statically typed language, I'd still prefer strongly typed Command and Event types.

The reason for this is that it gives you a single piece of the code base responsible for interpreting message elements. Serialization tends to be quite (type-)safe; deserialization is where you may encounter problems.

Still, I'd prefer to deal with any such problems in a single place, instead of spread out over the entire code base.

This is particularly relevant for Events, since you may have multiple Event Handlers handling the same type of Event. If you treat events as weakly typed dictionaries, you'll need to duplicate the implementation of a Tolerant Reader in each and every Event Handler.

On the other hand, if you treat Events and Commands as strong types, your deserializer can be the single Tolerant Reader you'd have to maintain.

Types

All this said, I can understand why you, in languages like C# or Java, find that defining immutable DTOs for each and every message seems like a lot of overhead:

public sealed class CorrectNameCommand
{
    private readonly string userId;
    private readonly string newName;

    public CorrectNameCommand(string userId, string newName)
    {
        this.userId = userId;
        this.newName = newName;
    }

    public string UserId
    {
        get { return this.userId; }
    }

    public string NewName
    {
        get { return this.newName; }
    }

    public override bool Equals(object obj)
    {
        var other = obj as UserName;
        if (other == null)
            return base.Equals(obj);

        return object.Equals(this.userId, other.userId)
            && object.Equals(this.newName, other.newName);
    }

    public override int GetHashCode()
    {
        return this.userId.GetHashCode() ^ this.newName.GetHashCode();
    }
}

That, indeed, seems like a lot of work.

This is the reason that I these days prefer other languages for implementing CQRS. On .NET, F# is a perfect fit, because all of the above code boils down to this one-liner:

type CorrectNameCommand = { UserId : string; NewName : string }

That's what I'd do, instead of passing weakly typed dictionaries around. Last time I heard Greg Young talk about CQRS (NDC Oslo 2015), he seemed to have 'converted' to F# as well.

like image 54
Mark Seemann Avatar answered Jan 01 '23 01:01

Mark Seemann