Поделиться через



September 2016

Volume 31 Number 9

[Cutting Edge]

Message-Based Business Logic in Practice

By Dino Esposito | September 2016

Dino EspositoIt’s common knowledge that to create efficient business software, you need to carefully model it around business events and faithfully mirror business processes.

Some 20 years ago, the flowchart used to be the ideal tool to represent large chunks of an application. Over the years, the size of flowcharts grew so big to become impractical to handle and engineers started looking elsewhere to find an appropriate model to outline the complexity of the business. Comprehensive, all-inclusive Unified Modeling Language (UML) diagrams appeared to be the way to go, starting from the class diagram, which is used to convey the domain model. That seemed to be the essence of Domain-Driven Design (DDD) and it worked for many projects, but also failed for reasons including poor design skills, lack of communication, inadequate range of technologies and, above all, insufficient understanding of the mechanics of the business processes.

An emerging and more modern approach to software engineering is to look at business events and use such events to gather requirements and build a deeper understanding of the mechanics of the system. In other words, instead of artificially creating a new model for the observed business, you aim at mirroring through code the same business processes that stakeholders and analysts describe and doing so using the procedures with which end users are familiar.

In this column, I’ll provide a practical demonstration of this approach by expressing relevant business processes through software entities called “sagas.” In addition, I’ll use ad hoc view models to populate each screen with which users are familiar. Finally, I’ll break up the steps of each application workflow into a combination of messages (commands and events) exchanged by involved parties. You’ll be surprised how easier it becomes expressing even complex pieces of business logic and, more than anything else, how quick and reliable it can be changing the implementation to stay in sync with variable business rules.

I’ll introduce a bunch of new software entities such as sagas, commands, events, buses and application services. To not reinvent the wheel for any new application—and to save a great deal of repetitive code—I’ll also sketch out the characteristics of a framework that can help you implement this kind of event-based architecture.

Alternative Way of Achieving the Same

Put together, the strategies “mirroring the business processes” and “mirroring the users’ procedures” make up for quite a different way of designing software. The overall methodology becomes top-down rather than bottom-up. A viable way could be to start designing the system from the presentation layer, which is primarily driven by the view models acting behind the user-facing screens and wizards. Altogether, users enter information in screens, which forms the view model of the screen and becomes input for any subsequent business actions to be taken against the system.

As users work with the UI, commands are pushed to the application layer—the topmost substrate of the business logic in charge of capturing any user input and returning any desired views. The application layer is seen as the orchestrator of the logic behind all the use cases of the application. Typically, the application layer is implemented as a set of services so a class library populated with methods that are one-to-one with user actions will definitely do the job. In the context of an ASP.NET MVC application, an application layer class can be one-to-one with controller classes and each controller method that triggers a business operation ends up calling into a particular application layer method in a one-to-one manner, as shown in Figure 1.

View Model and Application Layer
Figure 1 View Model and Application Layer

Next, I’ll show you how to rewrite a typical business action such as registering an invoice using a message-based approach.

From Presentation and Back

As a reference, I’ll use an excerpt of the sample application that Andrea Saltarello and I originally wrote as companion code for our Microsoft Press architecture book, “Microsoft .NET—Architecting Applications for the Enterprise, 2nd Edition” (2014). In the past year, the code evolved significantly and you can find it at bit.ly/29Nf2aX under the MERP folder.

MERP is an ASP.NET MVC application articulated in two areas. Each area is close to being a bounded context in the jargon of DDD and each can be considered, more generically, a microservice. In ASP.NET MVC, an area is a distinct collection of controllers, models and Razor views. Say, then, you have an InvoiceController class with an Issue method. Realistically, the Issue action is implemented through distinct GET and POST methods, as shown in Figure 2.

Figure 2 GET and POST Methods for a Controller Action

[HttpGet]
public ActionResult Issue()
{
  var model = WorkerServices.GetIssueViewModel();
  return View(model);
}
[HttpPost]
public ActionResult Issue(IssueViewModel model)
{
  if (!this.ModelState.IsValid)
  {
    return View(model);
  }
  WorkerServices.Issue(model);
  return Redirect("/Accountancy/");
}

The member WorkerServices is the entry point in the application layer and is a reference injected in the controller class through dependency injection:

public InvoiceController(InvoiceControllerWorkerServices workerServices)
{
  if(workerServices == null)
    throw new ArgumentNullException(nameof(workerServices));
  WorkerServices = workerServices;
}

The structure of the GET method is fairly simple: The application layer retrieves the view model that contains all the data necessary to present the user screen to trigger the “Issue” action. The data the worker service returns is passed as is to the Razor view.

The POST method follows a CQRS logic and executes the command that will issue the invoice, thus altering the state of the system. The command doesn’t necessarily produce any direct feedback for the user, but a redirect occurs to physically separate the command from any successive queries that will actually update the UI.

Breaking up the Necessary Logic

In a classic implementation scenario, the worker service method will collect input data and trigger a sequential hardcoded workflow. At the end of the workflow, the service method will pick up results and package them into a data structure to be returned to upper layers of code.

The workflow is typically a monolithic procedure made of conditional branches, loops and whatever serves to express the required logic. The workflow is likely inspired by a flowchart diagram drawn by domain experts, but at the end of the day it turns out to be a software artifact in which the flow of the work is flattened through the constructs and intricacies of the programming language or ad hoc workflow frameworks. Making even a small change might have significant regression effects. While unit tests exist to control regression, they’re still often perceived as an extra burden, requiring extra costs and effort.

Let’s see what happens, instead, if you orchestrate the business processes using commands.

Pushing Commands to the Bus

In message-based software terms, a command is a packet of data, namely a POCO class with only properties and no methods. In abstract terms, instead, a command can be described as an imperative message delivered to the system to have some tasks performed. Here’s how you can trigger a task by pushing a command to the system:

public void Issue(IssueViewModel model)
{
  var command = new IssueInvoiceCommand(
    model.Date,
    model.Amount,
    ...
  );
  Bus.Send(command);
}

The view model contains all the input data for the system to process. The model binding layer of ASP.NET MVC already does good mapping work between posted data and command data. It would come as no surprise if you were thinking of using the command class as the target of ASP.NET MVC model binding. In practice, though, model binding takes place in the controller context which is part of the presentation layer, whereas a command is a message that follows any decision made at the application layer level or even later. Admittedly, in the previous code snippet, the command class is close to being a copy of the view model class, but that’s mostly due to the extreme simplicity of the example.

Once you have a command instance ready, the next step is delivering it to the system. In a classic software design, a command has an executor that takes care of it from start to finish. The difference here is that any command can be rejected by the system and, more important, the effect of the command, as well as the list of actual handlers, might be different depending on the state of the system.

A bus is a publish/subscribe mechanism that’s primarily responsible for finding a handler for the command. The effect of the handler can be a single atomic action or, more likely, the command can trigger a saga. A saga is an instance of a known business process that typically comprises multiple commands and notifications of events for other sagas and handlers to which to react. 

The bus is a central element in a message-based architecture. At the very minimum, the bus interface is likely to include the following methods:

public interface IBus
{
  void Send<T>(T command) where T : Command;
  void RegisterSaga<T>() where T : Saga;
  void RegisterHandler<T>();
}

In addition to the Send method used to publish commands, the bus typically offers methods to register sagas and handlers. Sagas and handlers are similar software components in the sense that both handle commands. A handler, however, starts and finishes in a single run without ever getting back to the bus. A common type of handler is the denormalizer, namely a component that saves a read-only projection of the current state of an aggregate in a full CQRS architecture. A saga can be a multi-step process made of multiple commands and event notifications pushed back to the bus for other handlers and sagas to react.

Configuring the Bus

Configuration of the bus takes place during the application’s startup. At this time, business processes are split in sagas and optionally handlers. Each saga is fully identified by a starter message (command or event) and a list of messages it will handle. Figure 3 is an example.

Figure 3 Configuring the Bus

// Sagas
var bus = new SomeBus();
...
  bus.RegisterSaga<IncomingInvoiceSaga>();
  bus.RegisterSaga<OutgoingInvoiceSaga>();
// Handlers (denormalizers)
bus.RegisterHandler<IncomingInvoiceDenormalizer>();
bus.RegisterHandler<InvoiceDenormalizer>();
bus.RegisterHandler<OutgoingInvoiceDenormalizer>();
// Here’s a skeleton for the saga components above.
public class IncomingInvoiceSaga : Saga,
  IAmStartedBy<RegisterIncomingInvoiceCommand>
{...
}
public class OutgoingInvoiceSaga : Saga,
  IAmStartedBy<IssueInvoiceCommand>
{
  ...
}

A saga class typically inherits from a base class that conveniently packages references to assets you’re likely going to use such as the event store, the bus and the aggregate repository. As mentioned, a saga is also characterized by the starter message summarized by the IAmStartedBy<T> interface where T is either an event or a command:

public interface IAmStartedBy<T>
{
  void Handle(T message);
}

As you can see, the IAmStartedBy<T> interface counts a single method—Handle—which gets an instance of the type T. The body of any Handle method contains the actual code to register the invoice and, more in general, to perform the business task. At the end of the task, there might be the need of notifying other sagas or handlers of what happened. In particular, you might want to raise an event to let others know that the invoice was registered successfully or whether it failed and why. Handlers of a “failed” notification will then be responsible for any compensation or rollback procedures and for generating any sort of feedback to the user.

From Flowcharts to Sagas

There’s an inherent similarity between flowcharts that domain experts may be able to use to outline a business process and how you define sagas in your code. In a way, the saga is the implementation of a flowchart where each block may be rendered through a received event or command to handle. A saga can be a long-running process and can even be suspended and resumed. Think, for example, of a business process that comprises an approval step (such as after doing offline research on a customer). The saga must be started and when the stage of approval is reached it should receive an event and get serialized by the handler. Next, the saga will be resumed when some code sends the command that witnesses the approval. As you can see, having the business logic split in micro steps also makes it easier to extend and alter the logic to stay in sync with the evolving business.

From Theory to Frameworks

So far I assumed the existence of components such as sagas and buses. Who writes the bus and deals with persistence of sagas? And what about the definitions of interfaces and base classes that I mentioned as part of a framework?

If you look at the source code of project MERP from bit.ly/29Nf2aX, you’ll see that it uses classes like Bus, Saga, Command and much more. In the latest version of the code, though, these classes come from a few new NuGet packages, collectively known as the MementoFX. A typical application based on the Memento framework requires the core MementoFX package, which defines base classes and a couple of helper packages for the bus (Memento.Messaging.Postie or Memento.Messaging.Rebus presently) and event persistence. At the moment, the MERP source code uses the Embedded RavenDB engine for persistence and wraps it up in a Memento.Persistence.EmbeddedRavenDB package: This way, the event store will be started as soon as the ASP.NET process is up.

By using the MementoFX packages, you can dramatically reduce the effort to build message-based business logic. Let me hear your thoughts!


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