Brick and Mortar 2.0 — no shoplifter, but tech hipster
Uncovering and Distilling Knowledge with EventStorming and Domain-Driven Design — You’ve got questions, we’ve got answers
This is how I had envisioned the future, the implementation of which I chose as a challenge for my master’s thesis at the University of Applied Sciences Zwickau, Germany. Consequently, I started planning and developing a prototype in spring 2020. From the very beginning, it was clear that the implementation of the shopping scenarios described above would present software developers with enormous challenges. The business domain is complex and has many unknowns. Several departments from the supermarket context are involved and have an influence on the implementation of the software. To reduce the risk of misunderstandings and undesirable developments, it was essential to learn from experts in the self-checkout supermarkets — through discussions or activities that promote knowledge transfer. Therefore a workshop was held at the beginning of the project, where domain experts and software developers discussed and modeled business processes with each other — back then, Corona forced this to be a remote workshop. EventStorming was the tool of choice to specify the business processes. Initially, events were used to describe business processes. Events represent facts that have occurred as part of a business process, e.g. an event describes that an item has just been added to a basket (“Item added to basket”).
The great thing about EventStorming is that the participants do not need any prior technical or general knowledge of this methodology. In the first step, the Chaotic Exploration, the participants get a first overview of the business processes. Using sticky notes, the processes are made visible on a large (virtual) wall.
Because the notation is free from connections and based only on stickies, the depicted flows remain flexible to change — stickies can be rearranged to visualize a different flow, and they can be put aside if discussions reveal that the flow is different after all. This encourages experimenting with the processes and stimulates the knowledge exchange between domain experts and software developers.
Experimenting was very important for the project itself and for successfully delivering my master thesis in time. Many technical processes were not known at the beginning of the project; as is often the case, it was not clear in which direction the requirements would develop. It was clear that experimentation would be necessary to develop a working solution in an acceptable amount of time. If I had simply prototyped the project and written code quickly, the project would have reached a state of “big ball of mud” after a month or two at the latest. Implementing new features would have taken too long and the flexibility and easy adaptability of the software, so necessary for experimentation, would have been lost.
The part of Domain-driven Design (DDD) known as “strategic design” provides techniques through which communication between experts and developers can be improved and the knowledge of the experts can be translated directly to the structure of the software. One of these techniques is referred to as “context mapping.” The aim is to create an overview of all the domains involved. In a context map, large ovals represent a bounded context and connections visualize relationships between them. These relationships describe both technical mechanisms and sociological structures between the teams involved in the development of the software. The sociological relationship describes, for example, hierarchies, which in turn influence the technical integration. Thus, context maps explicitly describe the relationship between bounded contexts and the teams that develop them. In addition, dependencies are made visible. Incidentally, our system could have been broken down as shown in the context map below.
The business needs should dominate the source code so that domain concepts can be easily implemented and adapted. Technical concerns should not be allowed to get in the way of the development process. For this purpose DDD offers a set of design patterns known as tactical patterns. These are design patterns, each of which has a specific purpose to fulfill so that domain concepts can be directly mapped into the source code. “Entities” represent an individual unique thing and “Value Objects” represent expressions of values. By combining entities and value objects, concepts of reality (aka the “universe of discourse”) can be modeled. In DDD, Entities and Value Objects are combined into “Aggregates.” Aggregates ensure that business rules are always maintained. Aggregates therefore ensure atomic change behavior of the associated Entities and Value Objects. The business rules guaranteed to be true are, therefore, also known as invariants, i.e. conditions that should always hold.
In the diagram above, the domain concept “Basket” is depicted. An Aggregate always has an entry point, called “Root Entity.” In the example above, the Basket Entity takes on the role of the Aggregate Root. The Root Entity is considered the entry point into a model; therefore, in the example, Items can’t be accessed directly from the outside. Only the Aggregate Root can access its Items. Aggregates help to make sure that the entire model maintains a consistent state. For example, if a new Item is added to the Basket, the totalPrice of the Basket would change depending on the quantity and unitPrice. This also simplifies persistence and avoids unnecessary transactions, since the new state of the Aggregate can be saved within a single transaction.
A data model that consists only of data fields and implements only getters and setters is not a domain model. In DDD, such a model is referred to as an “anemic domain model.” In order to breathe life into a model, it should provide functions of the domain’s specialized operations. In the case of our prototype, one such function is placing an Item into the Basket. At first glance, this process appears to be completely simple and mundane. If you have direct access to the list of Items in the shopping cart, all you have to do is call the add function of the Items list and add the new Item. In the domain of the cashierless supermarket, however, this process can become more complex. Is the Item already in the virtual shopping cart? If yes, then the number should be increased, otherwise a new item entry must be created in the shopping cart. How does the total price change? Based on the newly added Item, the newly created total value of the shopping cart must also be adjusted. It is also conceivable that other factors will influence the total value of the shopping cart. Is there a promotion where you can buy three items for the price of two? Are there coupons that affect the total pricing? For vegetables and fruits, we determine the price based on weight instead of quantity. All this must be taken into account.
A single model alone was not enough for the prototype. Only when the models communicate with each other and tell each other what has just happened or another model is supposed to execute something, then the entirety of the business processes can be mapped into the software. Commands and domain events are used for this purpose. Commands and events are initially nothing more than data structures with readable data. They do not have any behavior.
A command expresses an intention that a particular aggregate is supposed to do something. Commands are formulated in the imperative, such as “Remove the item from the basket!”.
A domain event, on the other hand, expresses that something has just happened in a domain as a matter of fact. For example, if a new item has just been added to the basket aggregate and the state of the aggregate has changed successfully as a result, a domain event “An item has just been added to a basket” is subsequently emitted, which informs other domains about completion of this domain-related process.
Aggregates communicate with each other through domain events. Other aggregates that are interested in a domain event can subscribe to it and perform further actions with the data they contain. For example, when a customer enters the supermarket, the bounded context Check-In emits an event that is received by the Basket bounded context, which translates the event into the internal CreateBasket command, whereupon a new Aggregate is created for a virtual shopping basket. Events are exchanged between bounded contexts using message busses.
VLINGO XOOM for the Win
VLINGO XOOM is an open source platform for the development of reactive domain-driven software. I used XOOM in my master’s thesis because my supervisor at the University of Applied Sciences Zwickau, Professor Frank Grimm, recommended it to me as it implements the DDD principles very well. XOOM helps a great deal with implementing even complex domain-oriented models directly in software — without the domain-orientedness being muddled through technical concerns (such as microservices, threading, persistence, messaging, etc.) which will cause the software to become hard to maintain. XOOM solves most software-architectural needs, saving an enormous amount of time.
Based on the EventStorming model, which was created at the beginning of my master thesis in collaboration with my colleagues, I started to implement the first prototype of the software with XOOM. Now, of course, the question is how it is possible to use an EventStorming model to develop a software system. The practical thing here is that the concepts from EventStorming can be found in XOOM. The commands mentioned in EventStorming can be implemented directly into the software and ensure that methods are invoked on objects. These commands are applied to Aggregates, which are realized by XOOM as actors. Actors can be thought of as small computing machines that accept input and process it asynchronously. If an actor successfully executes a command and thereby changes its state, the actor emits an event. The change of state could mean that an Item has been placed in a shopping cart and because this change of state is emitted system-wide, other software components can react accordingly.
One of the great things about XOOM is that it enables Event Sourcing. Similar to other frameworks, event-sourced entities are used to persist the state. A sequence of domain events represents the state of an Aggregate (i.e., its entities and value objects). With event sourcing, this sequence of events gets persisted. If an Aggregate is to be reused later, even though it is no longer in memory at runtime, its current state can be restored by reapplying all events (“replaying” the events).
All these technical details seem quite complex and it would take a lot of time to implement them by hand. However, at the beginning we had set the goal that, in the source code, technical concerns should not bury the domain knowledge and that concepts such as domain events and commands should be directly reflected in the implementation. To achieve such clarity, XOOM Designer offers the perfect solution. The XOOM Designer offers a graphical user interface through which the aggregates, events and commands familiar to domain experts and developers alike — because they have been identified and defined during the previous activities, such as EventStorming — are mapped into the software. It, thus, becomes possible to model the domain knowledge piece by piece and then generate executable software from the designed model. No additional effort is required for successfully implementing messaging, except maybe for setting up a RabbitMQ instance through which domain events are exchanged between microservices. In addition to events and commands, XOOM allows users to choose from a number of persistence mechanisms, such as the aforementioned event sourcing, which eliminates tedious implementation by hand and thereby saves precious time.
When developing distributed applications, losing oversight of what information is being exchanged is a problem. Further challenges arise when the structure of the exchanged information changes. For instance, in a new version of a distributed application, the structure of an event type has to be changed because new fields were added or others were removed because they were no longer relevant. When this happens, it affects all the services that depend on that information and the previous structure. In the worst case, the organization in charge of updating events has no information about which services depend on the old event format. These services might become unable to process events conforming to the new event structure — the services understanding of the event structure is incompatible to the new structure published by the service which updated the event structure. So is there a way to keep track of the information exchanged and to make the dependencies of services on messages traceable?
Exactly these problems are addressed by XOOM Schemata. As a schema registry, XOOM Schemata’s purpose is (a) to provide a single source of truth for the definitions of messages and their structure and (b) manage the exchange of information about events within an organization and to make it accessible to potential consumers of these events. This approach is related to the domain-driven design concept of the Published Language. In the schema registry, all message-schemas are published. This makes transparent, which messages are exchanged throughout the organization. Using a domain-specific language, the structure of a message is specified through which we can generate source code. If another bounded context wants to use this message, it can have source code generated from the message’s well-defined structure, which is particularly helpful for maintaining type safety.