October 2016

Volume 31 Number 10

[Cutting Edge]

Event-Command-Saga Approach for Business Logic

By Dino Esposito

Dino EspositoIf we could work out of relatively frozen requirements, any design effort done in advance of development would certainly pay off. If you remember the term “Big Design Up Front,” you know what I mean. If not, you can check it out at bit.ly/1jVQr3g. A comprehensive domain model that handles both numerous and intricate workflows and rich and denormalized data views requires a solid and stable knowledge of the domain, while lengthening development time. Put another way, given the relevance that software has today for everyday business, a comprehensive domain model is as bad now as Big Design Up Front was perceived before the Agile movement came along.

The Event-Command-Saga (ECS) approach promotes a much more agile way to implement business workflows. It still requires solid knowledge of the processes and business rules, but it doesn’t require a big design to be in place before coding starts. Furthermore, it’s flexible in accommodating changes and, more important, business points that were initially missed or overlooked.

The ECS approach promotes a message-based formulation of business processes, which is surprisingly closer to the abstraction level of flowcharts. Therefore, it’s something that stakeholders can easily communicate and validate. A message-based formulation of business processes is also far easier to understand for developers, even when their understanding of the specific business domain is limited. The term ECS will probably sound new, but the concepts it builds on are the same you might find referred to as CQRS/ES in other sources .

In this month’s column, I’ll present a .NET-based framework specifically devised to implement the business logic of applications using relatively new concepts such as commands and sagas. For an introduction to the topic, you might want to check out my September 2016 column (msdn.com/magazine/mt767692). The term “business logic” here encompasses both the application and domain logic. The “application logic” is where you implement all use cases that depend on a given front end. The “domain logic,” instead, is invariant to use cases and entirely reusable across all flavors of presentation and application layers you might have.

MementoFX in Action

Let’s start with a new ASP.NET MVC project already configured to use common things such as Bootstrap, jQuery, Entity Framework and ASP.NET SignalR. Now add a controller class with a method and related view that displays to users an HTML form. When the user submits the form, the following code is expected to run:

[HttpPost]
public ActionResult Apply(NewAccountRequestViewModel input)
{
  _service.ApplyRequestForNewBankAccount(input);
  return RedirectToAction("index", "home");
}

At first sight, this is standard code, but the fun lies just under the surface. So open the code for the ApplyRequestForNewBankAccount method in the application layer.

Business-wise, the application user (likely, a bank employee) has just filled out the form through a customer request to open a new account. There’s a specific process to start whenever such a new request comes in. You can procedurally code all the steps of the workflow right in the application layer, or you can try the ECS approach. In the latter case, here’s what you come up with:

public void ApplyRequestForNewBankAccount(NewAccountRequestViewModel input)
{
  var command = new RequestNewBankAccountCommand(
    input.FullName, input.Age, input.IsNew);
  Bus.Send(command);
}

The RequestNewBankAccountCommand class is a little bit more than just a Plain Old CLR Object (POCO) class. It’s a POCO class but it inherits from Command. In turn, the Command class is defined in one of the NuGet packages that form the MementoFX framework. You then add the packages as shown in Figure 1.

Installing the MementoFX NuGet Packages
Figure 1 Installing the MementoFX NuGet Packages

The MementoFX framework is made up of three main parts: a core library, an event store and a bus. In the sample configuration, I used the embedded version of RavenDB for storing domain events and an in-memory bus (Postie) for the application layer and sagas to publish and subscribe to events. If you explore the NuGet platform further, you also find a bus component based on Rebus and an event store component based on MongoDB. So now, the following code compiles nicely:

public class RequestNewBankAccountCommand : Command
{
  ...
}

In the current version of MementoFX, the base Command class is a mere marker and contains no functional code, but this will likely change in future versions. The command wraps up the input parameters for the first step of the business process. To trigger the business process, the command is placed to the bus.

Configuring the MementoFX Environment

The initial setup of MementoFX is easier if you make use of an Inversion of Control (IoC) framework such as Unity. To configure MementoFX, you need to do the following three things: First, initialize the event store of choice. Second, tell MementoFX how to resolve generic interface types to concrete types (this mostly means telling the framework about the type of bus to use, the event store and the event store document). Third, you actually resolve the bus to a concrete instance and bind it to sagas and handlers as appropriate. Figure 2 summarizes the process.

Figure 2 Configuring the MementoFX

// Initialize the event store (RAVENDB)
NonAdminHttp.EnsureCanListenToWhenInNonAdminContext(8080);
var documentStore = new EmbeddableDocumentStore
{
  ConnectionStringName = "EventStore",
  UseEmbeddedHttpServer = true
};
documentStore.Configuration.Port = 8080;
documentStore.Initialize();
// Configure the FX
var container = MementoFxStartup
  UnityConfig<InMemoryBus, EmbeddedRavenDbEventStore,
     EmbeddableDocumentStore>(documentStore);
// Save global references to the FX core elements
Bus = container.Resolve<IBus>();
AggregateRepository = container.Resolve<IRepository>();
// Add sagas and handlers to the bus
Bus.RegisterSaga<AccountRequestSaga>();
Bus.RegisterHandler<AccountRequestDenormalizer>();

As shown in Figure 2, the bus has two subscribers—the mandatory AccountRequestSaga and the optional AccountRequestDenormalizer. The saga contains the code that does the job of processing the request. Any business logic you might have applies here. The denormalizer will receive information about the aggregate and, if needed, will create a projection of the data just for query purposes.

Designing the Saga

A saga is a class that represents a running instance of a business process. Depending on the actual capabilities of the bus you use, the saga can be persisted, suspended and resumed as appropriate. The default bus you have in MementoFX only works in memory. So, any saga is a one-off process that transactionally runs from start to finish.

A saga must have a starter event or command. You indicate the starter message through the interface IAmStartedBy. Any additional message (command or event) the saga knows how to handle is bound through the IHandlesMessage interface:

public class AccountRequestSaga : Saga,
  IAmStartedBy<RequestNewBankAccountCommand>,
  IHandleMessages<BankAccountApprovedEvent>
{
  ...
}

Both interfaces are made of a single Handle method, as shown here:

public void Handle(RequestNewBankAccountCommand message) { ... }
public void Handle(BankAccountApprovedEvent message) { ... }

Let’s switch back to the HTML form you assumed to have in the UI. When the bank employee clicks to submit the customer’s request for a new bank account, a command is pushed to the bus and the bus silently triggers a new saga. Finally, the Handle method of the saga for the specified command is run.

Adding Behavior to the Saga

A saga class is instantiated as shown here:

public AccountRequestSaga(
  IBus bus, IEventStore eventStore, IRepository repository)
  : base(bus, eventStore, repository)
{
}

It gets a reference to the bus so that the current saga can push new commands and events to the bus for other sagas or handlers and denormalizers to process. This is actually the key factor that enables a flexible and agile design of business workflows. In addition, a saga gets a reference to the repository. In MementoFX, the repository is a façade built on top of the event store. The repository saves and returns aggregates, except that the state of the aggregate is rebuilt every time by replaying all events it went through. Nicely enough, the MementoFX repository also offers an overload to query the state of a given aggregate at a given date.

Here’s a saga that would persist the request for a new bank account:

public void Handle(RequestNewBankAccountCommand message)
{
  var request = AccountRequest.Factory.NewRequestFrom(
    message.FullName, message.Age, message.IsNew);
  Repository.Save(request);
}

In this example, the AccountRequest class is a MementoFX aggregate. A MementoFX aggregate is a plain class derived from a specific parent. Assigning a parent class will save you from the burden of coding a bunch of things as far as management of internal domain events is concerned:

public class AccountRequest : Aggregate,
  IApplyEvent<AccountRequestReceivedEvent> { ... }

Another interesting aspect of MementoFX aggregates is the IApplyEvent interface. The type associated with the IApplyEvent interface defines a domain event that’s relevant for the aggregate to track. Put another way, it means that all events associated with the IApplyEvent interface are saved in the event store for that instance of the aggregate class. Therefore, for a bank account request, you can get to know when it was received, when it was processed, approved, delayed, denied and so forth. Moreover, all events will be stored in their natural order, meaning that it’s easy for the framework to take all events up until a given date and return a view of the aggregate at any time in the life of the system. Note that in MementoFX the use of the IApplyEvent is optional, in the sense that you’re also welcome to manually persist relevant events in the store when some other method of the aggregate is invoked. The use of the interface is a recommended practice that keeps the code more clear and concise.

When defining an aggregate, you must indicate its unique ID. By convention, MementoFX recognizes as the ID a property with the name of the aggregate class, plus “Id.” In this case, it would’ve been AccountRequestId. If you want to use another name (say, RequestId), you use the AggregateId attribute, as shown here:

public void ApplyEvent(
  [AggregateId("RequestId")]
  AccountRequestReceivedEvent theEvent)
{ ... }

In C# 6, you can also use the nameof operator to avoiding using a plain constant in compiled code. With MementoFX and the ECS approach, you need to modify a bit the persistence logic you might be used to. For example, when the saga is about to log the request for the account, it uses the factory of AccountRequest to get a new instance. Note that in order to avoid compile time errors, the factory class must be defined within the body of the AccountRequest class:

public static class Factory
{
  public static AccountRequest NewRequestFrom(string name, int age, bool isNew)
  {
    var received = new AccountRequestReceivedEvent(Guid.NewGuid(), name, age, isNew);
    var request = new AccountRequest();
    request.RaiseEvent(received);
    return request;
  }
}

As you can see, the factory doesn’t fill out the newly created instance of the aggregate, it just prepares an event and raises it. The RaiseEvent method belongs to the base Aggregate class, has the effect of adding that event to the current instance of the aggregate and calls ApplyEvent. So, in an apparently intricate way, you’re at the point of returning out of the factory a fully initialized aggregate. The benefit, though, is that not just the aggregate holds its current state, but it also holds all of the relevant events that were brought there in the current operation.

What happens when the saga saves the aggregate to the persistence layer? The Save method of the built-in repository goes through the list of pending events in the aggregate and writes them down to the configured event store. When the GetById method is called instead, it takes the ID to retrieve all the related events and returns an instance of the aggregate that results from replaying all logged events. Figure 3 shows a UI that’s pretty much the one you would imagine out of a standard approach. However, what happens under the hood is quite different. Note that in the UI I used ASP.NET SignalR to bring changes back to the main page.

The Sample MementoFX Application in Action
Figure 3 The Sample MementoFX Application in Action

A Word on Denormalizers

One of the most important changes in software recently is the separation between the model ideal to save data and the model ideal to consume data, as per the CQRS pattern. So far, you just saved an aggregate with all of the information that’s relevant to save. Each type of user, though, might have a different set of relevant information for the same aggregate. When this happens, you need to create one or more projections of stored data. A projection in this context is pretty much the same as a view in a SQL Server table. You use denormalizers to create projection of aggregates. A denormalizer is a handler bound to an event pushed to the bus. For example, imagine you need to create a dashboard for the managers responsible for approving new account requests. You might want to offer a slightly different aggregation of the same data perhaps with some indicators that are relevant to the business:

public class AccountRequestDenormalizer :
  IHandleMessages<AccountRequestReceivedEvent>
  {
    public void Handle(AccountRequestReceivedEvent message)
    { ... }
}

Denormalized data doesn’t need to go in the event store. You can reasonably use any database you like for that and most of the time a classic relational engine is the most effective solution.

Wrapping Up

This column offered a glimpse of a new way to organize business logic putting together CQRS and Event Sourcing, but without dealing with the low-level details and intricacies of both patterns. Furthermore, the ECS approach is also close to the real business to favor communication and reduce risk of misunderstanding. MementoFX is on NuGet for you to try out. I can’t wait to hear your feedback.


Dino Esposito is the author of “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2014) and “Modern Web Applications with ASP.NET” (Microsoft Press, 2016). A technical evangelist for the .NET and Android platforms at JetBrains, and frequent speaker at industry events worldwide, Esposito shares his vision of software at software2cents@wordpress.com and on Twitter: @despos.

Thanks to the following Microsoft technical expert for reviewing this article: Andrea Saltarello


Discuss this article in the MSDN Magazine forum