In association with heise online

Task-based user interface

The starting point for most processes is the user. The task of the UI is therefore to record the user's intention when working with the software and to translate it into commands for processing by the business logic layer. For this to be possible, the presentation layer must have an internal model of the tasks which will be performed by the user (see fig. 3).

A sample task-oriented user interface
Zoom Figure 3: A sample task-oriented user interface. Users do not modify data (remove the tick from "Employee" and save), but instead initiate processes (dismiss staff member) with parameters.

It would not, for example, be possible to freely edit and save customer address data. Instead, by providing the user with appropriate prompts, the fact that the customer is moving would be noted and the new address entered. Within the business logic, a distinction is then made between correcting an address and moving. If required, appropriate actions can then be triggered, such as a welcome letter containing vouchers and information on the nearest branch to the new address.

Command objects

A core element of all CQRS implementations is data transfer objects, which encapsulate a command passed to the business logic and all of the data required to execute it. To avoid losing the intention recorded in the user interface, specific classes are used, with names which are formulated in the imperative and as expressive as possible (see the following pseudocode example).

class AcceptOrder{
Guid OrderId;
Guid CustomerId;
String OrderNumber;
OrderItem Lines;
Decimal PercentageDiscount;
Date ExpectedDeliveryDate;
}

As pure data structures with no behaviour, command objects can be passed between clients and servers in a serialized way and stored for logging purposes. Command objects are also suitable for buffering parallel user queries.

The command objects required for a function are determined by the requirements of that function. If these are present as a use case, for example, the description of the use case can often be transferred directly to one or more commands. If, by contrast, you are attempting to realise a new implementation of an existing, poorly documented application, commands can be extracted from the method signatures of a domain model [12]. Command objects merely describe a command to be executed by the system. Other than any input validation, they do not contain any logic.

Interface with the business logic (command handler)

A thin, but important, interface layer, known as a "command handler" is responsible for executing commands (see also the listing below). As well as passing command objects to the responsible domain model entities in the form of method calls, it also takes care of subsidiary issues such as authorisation, logging and transactions. Lastly, it is responsible for dealing with results and any error messages. In our example, this means saving events published by the domain model and passing them onto subscribers. Ultimately, command handlers represent a setup under which execution of each defined command can be handled differently. While, for example, short transaction scripts can generally be employed for simple subdomains, an object-oriented implementation would generally be selected for complex and volatile areas of the business logic.

void Handle(AcceptOrder cmd){
CheckAuthorisation(cmd);
var customer = _customerRepository.get(cmd.CustomerId);
customer.AcceptNewOrder(cmd.OrderId,
cmd.OrderNumber,
cmd.Lines, ...);
SaveInEventStoreAndPublish(customer.GetNewEvent);
}
Object-oriented domain model

Domain models are responsible for executing commands. They map specific specialist requirements to program structures and encapsulate desired system behaviour and the data required for that behaviour. In our example, the responses of the model are published as events.

class Customer{
// [...]
void AcceptNewOrder(Guid id, string ordernumber,
OrderItem lines, ...){
CheckCustomerNotBlocked();
var netVolume = lines.sum(_.Net);
CheckCreditworthiness(netVolume);
NewEvent(new OrderForCustomerHasBeenAccepted(id,
ordernumber, netVolume, ...));
}
// [...]
}

One tried and tested method for designing domain models is domain driven design (DDD). It provides basic building blocks for constructing domain models. Entities are objects with an identity, such as "customer" or "product". Value objects, by contrast, are objects which are defined only in terms of their value, such as "5 GBP" or "5 Some Street, Some Town, AA1 1AA". Object-independent domain rules (e.g. VAT calculation) are mapped in stateless services. Aggregates combine closely cooperating parts of the domain model, the state of which frequently changes in concert.

Persistence via event sourcing

In principle, persistence mechanisms for the domain and for the read model are an internal implementation detail for the particular area. At the component level, and in part even at the class level, the technique employed within a component is therefore a matter of personal choice. This article's authors have had good experiences with event sourcing, the basic idea behind which is that all domain model responses to external commands should be published in the form of events. An event generally describes a change of domain state and primarily encompasses the reason for it and secondarily the data pertaining to the change of state:

class OrderForCustomerHasBeenAccepted{
Guid CustomerId;
Guid NewOrderId;
String OrderNumber;
Currency NetVolume;
Date OrderDate;
Date TargetDeliveryDate;
[...]
}

Rather than make the state of the domain model persistent, an event history is maintained, onto which new events are appended. The database containing this history is denoted the "event store" (1, 2). The history thus contains all information required to track any changes to the domain model. If the state of an aggregate is subsequently required in order to process a further command, it can be reconstructed at any time by projecting the history. The state of the domain model as a structure therefore exists only temporarily in memory and is not fixed in a database structure unnecessarily. This firstly does away with the problem of "object-relational impedance mismatch", usually resolved by deploying heavyweight O/R mappers. Secondly, this significantly simplifies the process of updating business logic, as no data schema restructuring is required.

By definition, completed events cannot be changed; therefore they are readily replicated and cached. The history also serves as a log for auditing purposes. This is guaranteed to be correct and complete, as both the state of the domain model and all data in queries can be determined from this history.

Read models with projected data

The query side should provision data required by clients as rapidly as possible. The structure of these models is therefore oriented around the expected queries. If these are heterogeneous, the differing requirements can be met by parallel read models. Lists, list-independent detailed records and analytical models analogous to OLAP systems are normally used:

class CustomerListEntry {
Guid CustomerId;
String CustomerNumber;
String Name;
Currency OutstandingOrderVolume;
Date LastOrderDate;
Currency SalesThisFinancialYear;
}

class CustomerDetails {
Guid CustomerId;
String CustomerNumber;
String Name;
OrderDetails OutstandingOrders;
OrderDetails CompletedOrders;
}

class OLAPDataPoint{
int FinancialYear;
ProductGroup ProductCategory;
Currency NetSales;
}

These read models are provided with data via projections from the command side. This is achieved using push semantics, so that updating occurs immediately when the source is changed. This means that up-to-date data is always available for querying. In implementations with event sourcing, events are the source from which the query side is created. The differing read models mean that data is always stored redundantly. This is useful, as it permits optimisation for differing use cases without coupling such use cases to each other. The DRY (don't repeat yourself) principle is not applicable; all data ultimately comes from a single authoritative source – the event store.

This architecture permits read models to be modified and extended as required at any time. Read models defined subsequently can be updated to the current state from the event history in just a few seconds by performing a new projection. Backing up data is also superfluous. It is sensible to implement this side of the CQRS architecture such that it is able to regenerate itself from the domain model history autonomously at any time. The type of data storage for each group of read models can in fact be selected to suit. For small, frequently used data sets, memory is sufficient. Lists and detailed records will, in the ideal case, be stored as documents in a key-value store. Relational and OLAP databases represent suitable storage for large data sets serving ad hoc queries.

The cycle illustrated in figure 2 is completed by a thin facade, which authorises queries for the presentation layer and returns the corresponding read models.

And the database?

CQRS moves the focus of software development away from data storage and data structure and towards behaviour. Although this does not make data storage superfluous, it does transform it into a minor implementation detail. Any databases required within individual components of the system can be selected as required. Where event sourcing is used, event history is persistent within the event store [9].

For components which do not use event sourcing, a simple key-value store is sufficient, as aggregates are always written and read as a whole. Where read models are not provisioned within the server's main memory, this task can be performed by key-value stores or relational or OLAP databases. Backups of data and clusters and database functions such as views and stored procedures are not usually required. Operating these databases is therefore comparatively simple.

Next: Scenarios

Print Version | Permalink: http://h-online.com/-1803276
  • Twitter
  • Facebook
  • submit to slashdot
  • StumbleUpon
  • submit to reddit
 


  • July's Community Calendar





The H Open

The H Security

The H Developer

The H Internet Toolkit