Writing Decoupled and Scalable Applications

Preface

This article is the first in a series of three articles. It's sole purpose is to introduce you to domain events and commands. The next article will show how, with almost no effort (thanks to Griffin.Decoupled) you can get started with the command/event type of applications. The series will end with an article showing how you can use the DDD domain model together with the command and event handling.

Introduction

When we start our profession as developers we complete applications very quickly. We giggle a bit for ourselves and look at the older programmers which finish just half of the application in the same amount of time. Our applications look good and work great. But in fact they are just lipstick on a pig:

lipstickpig.jpeg

The problems don't surface until you have to start maintaining the application. You change the code in one place and suddenly a new bug appears in another. This is one of the most common problems when developing applications. The problem is really that you have high coupling between your classes. It might be because of leaky abstractions or violated layer boundaries. It's a menace.

Red-Menace.jpg

Another typical problem is that we have to hard-code the application flow as a fixed sequence:

public void Register(email, string userName, string word) 
{
   
// 1. create
    var user = _repository.Create(email, userName, word);
    
   
// 2. notify user
    var email = new MailMessage("[email protected]", email, "Account created", "Welcome DUUUDE!");
    var client = new SmtpClient();
//configured in app.config
    client.Send(email);
    
   
// 3. Notify admin
    var email = new MailMessage("[email protected]", "[email protected]", "Account created", "User registered");
    client.Send(email);
}

Which will force us to make changes that will affect the entire sequence.

Consider the following: after a period of time you get a new requirement: The customer wants to audit all new users. To be able to do that we have to modify that code above. Changing the code breaks the Open/Closed principle (one of the SOLID ones). Why is that? Because every code change is a potential new bug. No modifications also means that we do not have to test it either. No changes = No new bugs (a bit simplified, but anyhow...).

To avoid changes we need some way to act upon actions/events.

Enter domain events


Domain events are one of the answers to our prayers.

250px-The_Event_2010_Intertitle.svg.png

Domain events are a concept which I've borrowed (shamelessly stolen) from domain driven design. A domain event is a change (which is most often) driven by a user. It can be upon user login, when an administrator locks a user account or when a user posts a new forum reply.

Events are named like UserRegistered, ReplyPosted or OrderPayed. Events should be defined in a way that the average client would understand. An event named OrderUpdated is far to broad and doesn't really say anything (compared with OrderPayed, ItemAddedToOrder or OrderPaymentMethodChanged).

As you've might have figured out the domain events should always be named in past tense (it's just part of the best practice). Let's define a small event:

public class ReplyPosted : DomainEventBase
{
    public ReplyPosted(string threadId, int postedById, string comment)
    {
        ThreadId = threadId;
        PostedById = postedById;
        Comment = comment;
    } 
    public string ThreadId { get; private set; }
    public string PostedById { get; private set; }
    public string Comment { get; private set; }
}

Notice that we always use IDs instead of entities/objects in the domain events. We also keep the number of properties at a minimum. Only include information which is important to identify the entities in question and the information that describes the change in the system.

Keeping the events lightweight makes them less likely to change in the future. Trust me, you do not want to have to change all your event subscribers when your application has grown. Compare the ReplyPosted event with an OrderUpdated event and the amount of information that it would have had to contain (and the amount of logic that all subscribers would have had to have).

Let's examine how the previous code snippet would look like when we are using domain events:

public void Register(email, string userName, string word)
{
    var user = _repository.Create(email, userName, word); 
   
// publish the event, the method is implementation specific
    DomainEvent.Publish(new UserRegistered(user.Id));
}

The event is simple enough; see:

public class UserRegistered : DomainEventBase
{
    public UserRegistered(int userId)
    {
        if (userId < 1) throw new ArgumentOutOfRangeException("userId", userId, "Specify a valid user id"); 
        UserId = userId;
    } 
    public int UserId { get; private set; }
}

Our event is lean enough and will be published for us. Now we only need to act upon that event. That's done with the help of subscribers. Let's create two (to handle the code in the original method).

public class WelcomeEmailSender : ISubscribeOn<UserRegistered> //read it as "I Subscribe On UserRegistered"
{
    public void Handle(UserRegistered domainEvent)
    {
        var email = new MailMessage("[email protected]", email, "Account created", "Welcome DUUUDE!");
        var client = new SmtpClient();
//configured in app.config
        client.Send(email);
    }

public class UserRegisteredEmailSender : ISubscribeOn<UserRegistered>
{
    public void Handle(UserRegistered domainEvent)
    {
        var email = new MailMessage("[email protected]", "[email protected]", "Account created", "User registered");
        var client = new SmtpClient();
//configured in app.config
        client.Send(email);
    }
}

You should always create a new class for every handler. It keeps them lightweight and easier to read/maintain. If you are using an IoC container you can also inject dependencies into them.

Let's also add support for the new client requirement: To be able to audit new users.

public class AuditNewUsers : ISubscribeOn<UserRegistered>
{
    IForbiddenWordsRepository _repository;
    IUserRepository _userRepository; 
    public AuditNewUsers(IForbiddenWordsRepository repository, IUserRepository userRepository)
    {
        _repository = repository;
        _userRepository = userRepository;
    } 
    public void Handle(UserRegistered domainEvent)
    {
        var user = userRepository.Get(domainEvent.UserId);
        if (_repository.Exists(user.UserName))
        {
            var email = new MailMessage("[email protected]", "[email protected]", "Action required", "You might want to inspect user #" + user.Id);
            var client = new SmtpClient();
//configured in app.config
            client.Send(email);
        }
    }
}

As you see we only had to create a new event.

no-code-harmed-300x155.png

I hope that you've got the grasp of domain events and how they help you to write more decoupled applications.

Command me, sire!

With the introduction of domain events we've got smaller classes which is more SOLID. It also means that we've captured the essence of what the user intended to do (the use case) by moving out all non-essential actions to event handlers. That's a good start. But we can decouple our architecture even further.

Commandoposter.jpg

A traditional layered application looks like this (may or may not be using multiple tiers):

cdraw.png

That design is great since it's very easy to follow what happens and when. The problem is that the service classes tend to get fat and changed often. And as you might know by now, every change of existing code is a potential bug. The solution for this is to retire those service classes and instead introduce commands.

Task based UIs vs CRUD UIs

But before we get into commands we'll have a prerequisite that we have to fulfill: Our UIs must be task based.

A typical CRUD based UI looks something like this:

mqI60.png

CRUD applications are often data-centric. That is, our application is modeled from the database perspective. We just create UIs to allow the user to edit everything. The problem with that is that there is not really a way to validate that the correct combination of fields are properly validated.

Task-based applications are user-centric. The tasks are not something that you make up by yourself but something which is defined by your client. They most often correspond to a use case/story.

The corresponding task based UI looks like:

hfQQh.png

The change is really that we define a set of actions that the user can take. Each of these actions are defined by your client. Your client probably doesn't say "I want to be able to edit all fields" but more likely "I want to be able to assign a case to a user". So you have a task named AssignCaseToUser. That my dear ladies and gentleman is a command.

With this approach it's much easier to validate the information since we have a specific action. For instance, it doesn't make sense to change the title when assigning a task. So we can exclude that field from the action. Also, we know that we should always update the AssignedAt field. Hence we do not expose that field but just update it our-self.

What is a command?

A command is a use case, not a database operation or something technical. The command is what the user (your client) wants to achieve. That also means that you should not try to write/design commands for reuse. That will come back and haunt you every night. Design a command to do what the user wants. Nothing more and nothing less.

RegisterUser, AssignTask, LockForum are use case driven commands while UpdateUserTable is more of an operation (and should really be part of a command).

This distinction is important if we want to be able to re-factor or scale our systems. The distinction makes our commands agnostic of the actual implementation.

Commands are always in a valid state

A command should always make sure that it's information is valid. You've probably seen commands that look like this:

public class DelegateTask
{
    public int TaskId { get; set; }
    public int UserId { get; set; }
}

The problem with that is that the command will fail if one of the fields are not specified. Always validate the information when it's assigned, not later.

We should use the following convention:

  1. Mandatory fields should be specified in the constructor, the setters should be private.
  2. Optional fields should have get/set, but the setters should validate the contents

Command validation vs UI validation

Command validation does not replace view model validation or similar. The command validation is used to increase the chance of success when the command is actually executed while the UI validation is used to ensure that the user entered all information correctly (and instantly provide the user feedback).

The difference is subtle but important. When you enter information into the command you've probably already validated it once.

The UI validation might be done by a different team/developer than the command validation (or by the same developer but in different time periods). It's therefore important that the validation is made in both places so that we catch any changes early (instead of getting failing commands).

Commands do not return anything

Commands should not return anything. EVER!.

This is probably the hardest thing with commands to get accustomed to. Today we are so accustomed to giving the user feedback directly. And that isn't really possible with commands since they will be executed in the future (and not directly).

cdraw (1).png

Here is a solution for most common problems:

Need to act upon the generated ID

Switch to GUIDs

GUIDs provide the worst performance when looking up information in the database. But they are implementation agnostic and can safely be generated by the code.

There are however GUIDs (not really GUIDs if we should be anal, but similar enough) that provides you better performance. Google "sequential GUIDs". Many database engines have similar GUIDs.

Act upon the generated domain event instead

We have our domain events now, right? Use them to handle additional processing if possible.

Need to present the result to the user

  • Trick the user

    You have all the information already. Just update the UI with it. For instance stackoverflow.com uses this approach. The command will most likely have been processed by our system when the user updates the page.
     
  • Distract the user

    Show the user a "Thank you so bloody much for the extra work, we'll save it into our systems !ASAP". (Being a developer you'll know that we meant "not As Soon As Possible", but the user will think that we've mistyped. That's what I call a win-win situation).

That page/message will provide our system a chance to process the command before the user does something else.

  • Command vs handler

    In my own implementation I've decided to split the command request from it's processing. The command classes are in a strict sense just DTOs. They are used to carry the information to the actual handler. I find this acceptable since the command dispatcher will throw an exception if there are no handlers. (The distributed version will generate errors when the host can't find a handler.)
     
  • Commands vs services

    Let's compare commands to the services. Services are most often just facades for root aggregates (Order, User etc). The services will therefore most often be much larger than the corresponding commands, which in turn reduce readability (it's not as easy to get a grip over what a services does). A command is more re-factor friendly since we can move sub actions into own methods (usually avoided in services since it's hard to tell what each method is used by). The service is also more prone to bugs since it wraps all operations for a root aggregate (and it's children). Changing one method may affect another one (and therefore also affect all usages).

    MvcController (or whatever you're using) will likely use one or more services. If we are using services in a controller then we have obtained the dependencies of all the service classes. Any change in the service can affect one or more of the controller methods. If we are using commands, each controller method has a dependency on a single command. Hence changing the command handler will only affect a single controller method.

The code

The customer has moved to a new place. Thus we need to change the delivery address:

public class ChangeDeliveryAddress
{
    public ChangeDeliveryAddress(int userId, string street, string zipCode, string city)
    {
        if (userId < 1) throw new ArgumentOutOfRangeException("userId", userId, "Specify a valid user id");
        if (string.IsNullOrEmpty(street))
            throw new ArgumentException("street", "Street must be specified");
        if (string.IsNullOrEmpty(zipCode))
            throw new ArgumentException("zipCode", "ZipCode must be specified");
        if (string.IsNullOrEmpty(city))
            throw new ArgumentException("city", "City must be specified"); 
       
//assignment here
    } 
    public string UserId { get; private set; }
    public string Street { get; private set; }
    public string ZipCode { get; private set; }
    public string City { get; private set; } 
   
// State may be null
    public string State
    {
        get;
        set;
    }
}

That was only the command request. We also need something that handles the command.

public class ChangeDeliveryAddressHandler : IHandleCommand<ChangeDeliveryAddress> //"I Handle Command ChangeDeliveryAddress"
{
    IUserRepository _repository; 
    public ChangeDeliveryAddressHandler(IUserRepository repository)
    {
        _repository = repository;
    } 
    public void Invoke(ChangeDeliveryAddress command)
    {
        var user = _repository.Get(command.UserId);
        user.ZipCode = command.zipCode;
       
// [...] 
        DomainEvent.Publish(new DeliveryAddressChanged(user.Id, /* and all changed fields */));
    }
}


Do note that the command in this case is just a data source wrapper. That may not be in the future. The important thing today is that the event is generated and we can therefore act upon the command in a decoupled way.

What belongs in the command?

Now that we have obtained the domain events and commands there is still an issue to address. And that is, what belongs in the command or in the domain event handler.

The answer is that it's quite easy to solve. Ask your client. For instance: "Should we abort the user registration if the welcome email can't be sent? Or is it enough if we notify your support department so that they can contact the new user?".

Everything which is not fatal in a usecase / userstory should be placed in domain event handlers.

Handling customer feedback

We'll have to have a new way to interact with the users, since we should treat all commands as some asynchronous handlers which won't give us a result back. The easiest way to do that is to introduce a notification system. Simply create a new database table where all notifications are stored. Read it every time a new page is displayed.

The notification system should of course be a command and a domain event too. If you're writing a native client you can simply subscribe to the NotificationCreated event to be able to display it to the user.

Let me entertain you

To entertain you a bit, here is a command handler of mine (from my upcoming web startup). It subscribes to some domain events to capture the entire task delegation flow in the same place.

Look at the subscribed events and think of when they happen. When you get it you'll understand that the logic handles several different application flows. So it's more complex than it looks .

(Note that the domain events are published from within the domain models since I try to follow Domain Driven Design.)

public class RequestTaskDelegationHandler :
IHandleCommand<RequestTaskDelegation>,
ISubscribeOn<InvitationAccepted>,
ISubscribeOn<FriendRequestAccepted>,
ISubscribeOn<FriendRequestRejected>
{
    private readonly ITodoItemStorage _todoItemStorage;
    private readonly IUserStorage _userStorage;
    private readonly IFriendDataStore _friendDataStore;
    private readonly ICommandDispatcher _commandDispatcher; 
    public RequestTaskDelegationHandler(ITodoItemStorage todoItemStorage, IUserStorage userStorage, IFriendDataStore friendDataStore, ICommandDispatcher commandDispatcher)
    {
        _todoItemStorage = todoItemStorage;
        _userStorage = userStorage;
        _friendDataStore = friendDataStore;
        _commandDispatcher = commandDispatcher;
    } 
    public void Invoke(RequestTaskDelegation command)
    {
        var item = _todoItemStorage.Load(command.TaskId);
        var delegatedBy = _userStorage.Load(command.DelegatedFromUserId);
        var delegateTo = command.DelegateTo.Contains("@")
//userIds do not contain @
        ? _userStorage.LoadByEmail(command.DelegateTo)
        : _userStorage.Load(command.DelegateTo); 
        var isNotFriend = delegateTo == null || !_friendDataStore.IsFriend(command.DelegatedFromUserId, delegateTo.Id);
        if (isNotFriend)
        {
            item.RequestDelegationToInvited(delegatedBy, delegateTo != null ? delegateTo.Email : command.DelegateTo);
            MakeFriendsAndThenDelegate(command, delegateTo);
            return;
        } 
        item.RequestDelegationTo(delegatedBy, delegateTo);
    } 
    ///
<summary>
    /// Hmm. No relationship between the users. Start by a friendship request.
    /// </summary>
    /// <param name="command"></param>
    /// <param name="delegateTo"></param>
    private void MakeFriendsAndThenDelegate(RequestTaskDelegation command, IUserLink delegateTo)
    {
       
// Not a user yet, hence we need to connect using an ID,
        // email can't be used since the user may loging with another one.
        if (delegateTo == null)
        { 
            var inviter = _userStorage.Load(command.DelegatedFromUserId); 
            var invited = _userStorage.GetInvitedByEmail(command.DelegateTo);
            if (invited == null)
            {
                invited = new InvitedUser(command.DelegateTo, inviter);
                _userStorage.Save(invited);
            } 
            var pendingDelegation = new PendingDelegation
            {
                ActivationKey = invited.ActivationKey,
                FromUserId = command.DelegatedFromUserId,
                TaskId = command.TaskId
            };
            _todoItemStorage.StorePending(pendingDelegation); 
        }
       
else
        {
            _todoItemStorage.StorePending(new PendingDelegation
            {
                DelegatedToUserId = delegateTo.Id,
                FromUserId = command.DelegatedFromUserId,
                TaskId = command.TaskId
            });
        } 
        var inviteCmd = new MakeFriend(command.DelegatedFromUserId, command.DelegateTo);
        _commandDispatcher.Dispatch(inviteCmd);
    } 
    ///
<summary>
    /// Accepted our invitation, now let's make that delegation request for real.
    /// </summary>
    /// <param name="e">The event</param>
    public void Handle(InvitationAccepted e)
    {
       
// need to convert to using user id
        var delegations = _todoItemStorage.GetPendingByAcceptanceKey(e.ActivationKey);
        foreach (var delegation in delegations)
        {
            var newDelegation = new PendingDelegation
            {
                DelegatedToUserId = e.InvitedUserId,
                FromUserId = e.InvitedByUserId,
                TaskId = delegation.TaskId
            };
            _todoItemStorage.StorePending(newDelegation);
        }
    } 
    ///
<summary>
    /// Friendship requested, now we can request delegation
    /// </summary>
    /// <param name="e">The event</param>
    public void Handle(FriendRequestAccepted e)
    {
        var delegations = _todoItemStorage.GetPendingByUserId(e.AcceptedBy);
        foreach (var delegation in delegations)
        {
            var item = _todoItemStorage.Load(delegation.TaskId);
            var to = _userStorage.Load(delegation.DelegatedToUserId);
            var from = _userStorage.Load(delegation.FromUserId);
            item.RequestDelegationTo(from, to);
            _todoItemStorage.Delete(delegation);
        }
    } 
    ///
<summary>
    /// Need to remove a pending request.
    /// </summary>
    /// <param name="e">The event</param>
    public void Handle(FriendRequestRejected e)
    {
        var delegations = _todoItemStorage.GetPendingByUserId(e.RejectedBy);
        foreach (var delegation in delegations)
        {
            _todoItemStorage.Delete(delegation);
            var item = _todoItemStorage.Load(delegation.TaskId);
            var user = _userStorage.Load(e.RejectedBy);
            item.RejectDelegation(user, e.Reason);
        }
    }
}

Summary

This article was created by Jonas Gauffin. Expect more articles in the future on the same subject.


Similar Articles