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:
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.
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.
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.
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.
A traditional layered application looks like this (may or may not be using
multiple tiers):
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:
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:
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:
- Mandatory fields should be specified in the constructor, the setters should be private.
- 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).
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.