Domain-Driven Design in Practice
by Vladimir Khorikov
Have you ever felt that the topic of domain-driven design is too big, and you struggle to understand how to start applying its concepts in your own project? Or maybe you had a hard time to combine them with other well-known practices, such as unit testing and technologies, such as relational databases and object relational mappers. If so, you are on the right track. In this course, I'm going to show step by step how to grow an application from the very beginning to a full-fledged solution with DDD principles in mind. You will see the full process of building a software project using such concepts as entities, value objects, aggregates, repositories, bounded contexts, and domain events. Not only this, I will give detailed annotations along the way and explain why we make one decision over another. You will learn what DDD concepts are application in what particular case and why it is so. My name is Vladimir Khorikov and this is the Domain-Driven Design in Practice course in which I'm going to show how to apply the domain-driven design principles in a real world application, the thing I've been doing for more than five years now. Let's dive in.
Course Outline and Prerequisites
I believe the best way to learn how to apply the principles of domain-driven design in practice is to actually go through the whole process of creating an application, and this is exactly what I am going to do here. In the first module, we will talk about the core principles we will follow in this course. I will also give a quick overview of the basics of domain-driven design. You will see when the approach DDD proposes is applicable and why. I'll also introduce you to do the problem domain we'll be working in and show the UI of the application we'll be building throughout the course. In the next module, we'll start the development with some basic functionality. You will learn the differences between entities and value objects, which of them to use in your code base, and why. I'll also show the best practices for working with both concepts. In the third module, we'll discuss persistence and how to keep the domain model isolated, despite the requirements relational databases and object relational mappers impose. Next I'll show how we can combine the existing classes in aggregates. You will see how to find proper boundaries for aggregates and what trade-offs are there when it comes to defining an aggregate in your domain model. In module five, we'll talk about the repository pattern and how it correlates with aggregates. In the sixth module, we'll introduce the second bounded context and we'll discuss the guidelines behind the notion of bounded context itself. You will learn how bounded contexts relate to sub-domains, how to find boundaries for them, and more. Next, we'll talk about domain events. We will start with implementing domain events in a classic way and we'll see what drawbacks it introduces. After that, we will come up with a better approach. In the last module, we will look at possible ways of evolving the code base further on and discuss the several anti-patterns in the world of DDD. This course is going to be code heavy with a strong focus on the quality of the code. We will implement the project using the C# language, so you will need at least some basic experience with it. Besides C#, I assume you are familiar with the theory behind domain-driven design, and at least read the blue book written by Eric Evans. I also recommend you to watch the Domain-Driven Design Fundamentals course by Steve Smith and Julie Lerman here on Pluralsight.
Area of Application for Domain-Driven Design
Every good practice has its own limits. It's important to understand that domain-driven design is not a silver bullet, there are different kinds of projects and DDD is applicable to only a fraction of them. Let's elaborate on that. Every software project has a set of attributes, the most important of which are the amounts of data it operates, performance requirements, business logic complexity, and technical complexity. The amount of data the application works with may vary a lot in the modern days. Although most of the software we know works with data that can fit a single database instance, it is not necessarily the case for other types of projects. There are a lot of solutions that analyze and process huge amounts of row data, or in other words, big data. Performance requirements is another attribute that can differ for various types of projects. A simple utility, for example, often doesn't have much of them, as opposed to an online trading platform, which usually has a strict requirement to return any code with no more than tens of milliseconds timeframe. Business logic complexity are first to the degree to which the problem domain a software works in is complicated. For example, a CRUD application that performs basic create, read, update, delete operations doesn't carry a lot of complexity with it. At the same time, an ERP system, which automates a big chunk of the company's activity, must model all the processes the company acts upon and thus handle a lot of complex business roles. The business logic complexity of such a system may be extremely high. The last attribute I'd like to point out is technical complexity. You can think of it as a complexity of the algorithms you need to implement to make the software work. A good example here is low-level programming for embedded systems where you need to deal with many of the hardware systems manually. All right, so where do the domain-design practices fit in this picture? The techniques DDD proposes are useful if and only if the project you are working on has a lot of complex business rules. DDD won't help you if you work with big data, need to achieve outstanding performance, or program against hardware systems. The only purpose DDD concepts serve is to tackle business logic complexity. So, if you need to create a Twitter-like application, domain-driven design won't help you much with that, because the challenge in this type of software comes out not from its business roles. The business logic itself is pretty simple in Twitter. What makes it hard to implement is the great performance and scalability requirements. A typical example of software with complicated business logic is enterprise-level applications. It is true that most of enterprise projects don't have outstanding performance requirements, they operate moderate amounts of data, and developers working on them usually don't have to deal with technical complexity by their own, because there are plenty of tools that abstract out this kind of complexity for them. The biggest challenge in such projects is to handle business logic complexity in such a way that it would be possible to extend and maintain the solution in the long run. That is exactly the task that the domain-driven design practices are aimed to solve. They help us create code, which not only fully powers the problem, but also does it in the simplest, and thus the most maintainable way possible.
Why Domain-Driven Design?
Let's set some groundwork and talk about the basic principles we will follow in this course. In my opinion, there are two core principles in software development to which every programmer should adhere in most cases. They are YAGNI and KISS. YAGNI stands for you are not gonna need it, and basically means you should implement only the functionality you need in this particular moment. You shouldn't try to anticipate the future needs, because most of the functionality you develop just in case turns out to be unused and thus, just a waste of time. KISS stands for keep it short and simple. This principle is about making the implementation of the remaining functionality as simple as possible. The point here is that the simpler your code is, the more readable, and thus more maintainable it becomes. These principles are important, because they help solve two major problems we face when building software projects: shortening the time needed for development, and keeping the code base maintainable in the long run. The beauty of domain-driven design is that its practices complement these two software development principles. It allows us to extract the central part of the problem domain and simplify it, removing most of the necessary complexity. The ability to express business logic in the clearest way possible is a single trait that makes domain-driven design so appealing in enterprise-level applications. It is hard to estimate how important that is. The most difficult task in the modern business line software is to keep that complexity under control. There is only this much complexity we can deal with at a time. If the code exceeds it, it becomes really hard, and at some point, even impossible, to change anything in the software without introducing some unexpected side effects. Extending such a project becomes a pain and usually results in a lot of bugs. This, in turn, slows the development down and may eventually lead to failure of the project. Uncontrollable growth of complexity is one of the biggest reasons why software projects fail. Domain-driven design helps prevent it. You will see that in this course we'll make a continuous effort to keep our code base as simple and as expressive as possible.
Main Concepts of Domain-Driven Design
Let's highlight the main concepts of domain-driven design. In this course, I assume you already know the theory behind DDD, so I'll keep the description short. The first one is the notion of ubiquitous language, that is, the language structured around the domain model and used by all team members to refer to the elements of that domain. You might have noticed that in many projects, domain experts and developers use different sets of terms when they talk about the domain. This difference leads to misunderstandings and slows the overall development process down. The notion of ubiquitous language helps eliminate the barrier. Domain-driven design suggests us to explicitly point those differences out and adjust the terminology to comply with a single ubiquitous language. Let's say, for example, that you have developed a sales system. In this system, you have a class called Product, which is an atomic sale unit. Let's also say you also notice that the domain experts refer to this element as both Product and Package. In this case, you should call attention to this disparity and suggest to use a single term to avoid confusion. The concept of ubiquitous language also means you should keep your code base in sync with this single terminology and name all your classes and tables in the database after the terms in the ubiquitous language. All this helps bridge the gap and set the groundwork for efficient communication. Another important part of domain-driven design is the concept of bounded contexts. Often an application grows so much that it becomes hard to maintain its code base as a whole. Code elements that make sense in one part of the system may seem completely irrelevant in another. In this case, the best solution would be to separate these parts from each other explicitly. That is where this concept helps us. It allows us to clearly define the boundaries of these parts, hence the name bounded context. If your system consists of two parts, one of which is sales and the other is support, it would be a good decision to introduce a separate bounded context for each of them and explicitly state the relation between them. We will see how we can define bounded contexts in our code base in the future modules. The third concept is the notion of core domain. Domain-driven design states that the main part of any system is its business logic, and not all, but the most intrinsic piece of it, that is, the problem the software is meant to solve is core domain. In the sales application example, there might be lots of business logic, but not all of it is essential. For instance, you might have a bookkeeping functionality that can be easily delegated to an external software. It is easy to do that, because it is not the core problem your application is built for, and it's cheaper to just buy an existing solution rather than trying to implement it from scratch. Domain-driven design proposes that we always focus most of our efforts on the core domain. These concepts, ubiquitous language, bounded context, and core domain, are the most important parts of domain-driven design. You can think of them as the strategic elements of DDD. The other notions, such as entities, value objects, and repositories, comprise the tactics of how you should build your software project.
Domain-Driven Design Is Not Only About Writing Code
Domain-driven design is not only about writing code, though. Adhering to the DDD practices also implies a heavy communication process between developers and domain experts. It's important to have a direct access to the experts in your problem domain, because it's the only way to get the complete information about the problem you are solving. To get the most of domain-driven design, you should constantly refine your domain knowledge with the help of the experts in your company, and it shouldn't be a one-way process. If you see any inconsistencies in the language the experts use or in the model they draw, state it. It may be that you find a way to describe and solve the problem in a much simpler way that it was proposed in the beginning. Constantly work with the experts and strive to help them simplify or even completely rethink the problem. Your code, if built using the ubiquitous language, can become a great help for that. You may see some edge cases that weren't clear enough in the beginning, or you may find how to redefine the problem statement in a much cleaner and more concise manner. Another implication of domain-driven design is that you should always strive to become a domain expert yourself. Software developers are often enthusiastic about coding tasks that regard to building an infrastructure for the future project. Such tasks often seem interesting as they entail some technical challenge. Also, because of that technical nature, the knowledge acquired when solving them can be reused in other projects. All these make such activities compelling to many developers. Nevertheless, to benefit from the domain-driven design techniques the most, you should always try to dive into the domain you are working in as much as possible. This might seem boring at first, and may feel like a waste of time, because the domain knowledge you get working on one project can hardly be applied to another, but in the reality, it is not the case. First of all, if you obtain deep domain knowledge, it will help you do the best job that a programmer could possibly do. This knowledge will guide you through the code you write. It will help you look at it from the domain expert's point of view. This skill is indispensible, as it allows you to combine the best of the two worlds, write technically correct code on one hand, and express the domain knowledge with it on the other. Secondly, although problem domains are different from project to project, the skill of systematizing them with code is reusable. The more you learn the domain you work in, and the more you try to define it in your code base, the better you become at it. Eventually you will see patterns and will be able to work on new domains with ease, and it will also help you learn your domains more quickly.
Onion Architecture and Domain Isolation
Let's look at the structure of a typical application built with the domain design principles in mind. DDD notions form a construction named onion architecture. It is called so because it resembles an onion with multiple layers and a core inside. Upper layers depend on the lower ones, but the lower layers don't know of the upper. It might seem similar to a classic onion layer architecture entities. The difference here is that onion architecture emphasized the fact that the core part of this structure cannot depend on anything else, except itself. It means that the core elements of our domain model should act in isolation from others. This is an important point. We will talk about it in a minute. Let's place the building blocks of domain-driven design in this picture. The core part of this so-called onion is the notion of entity, value object, domain event, and aggregate. The next layer consists of repositories, factories, and domain services. Application services go beyond that, and finally, UI is the outermost layer, if, of course, the application contains a user interface. You might wonder where the database belongs in this picture. All work with a database should be encapsulated into repositories. They can refer to it either directly or use an ORM, but the general rule should remain. The code working with the data storage must be gathered under the repositories in your domain model. These four elements, entities, value objects, domain events, and aggregates, are the most basic. They can refer to each other, for example, and then they can contain a value object or a value object can keep a reference to an aggregate root, but they cannot work with other DDD notions, such as repositories and factories. Similarly, repositories, factories, and domain services can know of each other and the four basic elements, but they should not refer to the application services. So why is this kind of isolation is so important? Why should we keep the four core elements of the domain model separated from others? The main reason is the separation of concerns. Entities, value objects, domain events, and aggregates carry the most important part of the application, its business logic. They don't contain all of it, of course. Repositories and factories can keep some of the business logic as well, but these four elements do include most of it. In the situation where you have some elements so deeply involved in the problem domain representation, it is vital to keep them as free as possible from other duties. Hence, the notion of separation of concerns. I'd like to emphasize it once again. It is crucial to leave entities and value objects to do only one thing, represent the domain logic in your application. In practice, it means they shouldn't contain any knowledge about how they are persisted or how they are created. These two operations must be up to repositories and factories. They also shouldn't contain any knowledge about the tables and columns in the database where they are stored. This must be given away to data members. All they should know of is the domain they represent. Remember, the cleaner you keep your domain model, the easier it is to reason about it and to extend it later on. Inability to maintain proper separation of concerns in enterprise-level applications is one of the biggest reasons why code bases become a mess, which leads to delays and even failure of the project. It is not always possible to separate them completely, though, and there always will be some elements not related to your domain you cannot get rid of. An example here would be the necessity to create a parameter-less constructor in your entities to satisfy object-relational mappers. Nevertheless, it is possible to keep those elements under control so that they introduce almost no overhead to your domain clauses. You will see how to do this in practice in the next modules, how to keep the domain model clean, how to maintain proper separation of concerns, and how to deal with the side effects object-relational mappers introduce.
Modeling Best Practices
If you follow DDD principles that the main model becomes the heart of your software, this fact entails a guideline of how you should work with the applications code base. Focus on the core domain first, and pay most of your attention to it. In practice, it means that you should always start the development with modeling the core domain, even if you don't have any UI or database structure yet. Start experimenting with the model with the help of unit tests. User interface and the database are important elements of the system as well, of course, but the core domain is the part you should focus on the most. It might be hard to make such a shift, especially if you are used to building software starting from the database structure, but this shift is worth it. The investments in your domain model pay off greatly over time. It also means that in a typical enterprise application, an infrastructure code is less important than the core domain. Make sure you keep your business logic adherent and don't allow it to dissolve in the infrastructure code.
Domain-Driven Design and Unit Testing
When it comes to unit testing, it's important to keep a balance between the test coverage and the amount of effort you put into tests. 100% coverage is an expensive mark to reach, and it doesn't necessarily provide proportional value to the quality of your software. In most enterprise-level applications, the value distribution corresponds to the number of unit tests in this way. You can see that the closer we get 100%, the less value the additional tests provide us with. At the same time, the amount of effort we put in their creation grows linearly. It means that at some point, the value we get from the additional tests doesn't justify the resources we invest in them. This is the point where we are better off to stop building up the coverage, as it wouldn't give us much besides a good-looking coverage number. The exact location of this point would differ from project to project, but the general trend remains. You should employ the same pragmatic principles in an application built with the domain-design principles in mind. In practice, it means you should cover with unit tests only those parts of your code base that are the most significant to the application, and this is the innermost layer in your onion architecture, entities, value objects, aggregates, and domain events, the elements which contain most of the domain knowledge of your application. It's a good idea to get 100% or close to 100% as coverage of them. That is another reason why we should keep the core layer of the domain model isolated from other parts of the application, such as database, email service, and so on. A good separation of concerns help create testable code, which doesn't require any mocks or other test doubles to be tested. If you find your unit test hidden in the database or some other external dependencies, it's a strong sign your domain model is not properly isolated. But what about the other parts of your code base? Shouldn't they be covered with tests as well? They definitely should, but it doesn't have to be unit tests. Moreover, we are better off not employing unit testing for those types of functionality, but rather use integration testing instead. That is, automated tests which cover several pieces of the application at once. Throughout this course, you will see this pragmatic approach to unit testing in practice. You will also see how easy it is to implement them due to great isolation we will achieve for our domain model. The topic of integration testing is out of scope for this course, though. If you want to learn more about integration testing in the context of DDD, check out my blog post here.
The Problem Domain Introduction
Now, let's talk about the problem domain we'll be building our software for. We will need to model the work of automated machines. There will be two of them a snack machine and an automated teller machine, ATM for short. This will be a desktop software, but the principles I will be describing are applicable to any kind of enterprise-level applications. The only reason why I chose a desktop application is that it is easy to model the user interface of those machines with it. We will start with snack machine. This is how its interface will look like. I'm showing you the end result, but keep in mind that we will come to this result interactively. Also, our primary focus in this course is not the user interface itself or desktop technologies, so don't pay too much attention to the visual presentation. I'm definitely not a great user interface designer. All right, so having that said, let's see what problems we'll be solving. Our task is to model the snack machine so that it can sell snacks in exchange for cash. We won't pay attention to such details as how it recognized notes and coins or how the products are dispensed physically. What we are going to focus on is the actual business logic behind the device, what rules it should follow when dealing with taking money in, returning change, selling products, and so on. Snack machines will have three slots for snacks. You can see here there are 10 chocolates, 15 cans of soda, and 20 gums. In a real snack machine, there would be more different products of course, but we'll stick to only three in this course, just for the sake of brevity. The amount of money a snack machine contains is displayed in the bottom part of the screen. The machine operates pennies, 10 cents, quarters, $1, $5, and $20 dollar bills. In this application, we assume there are no other coins or notes, just to make our task less verbose. In order to buy something, you need to put a coin or a note and select which product you want to buy by pressing on one of these three buttons, so the overall process looks like this. I will insert in three $1 bills and press on this button. After that, you can see that the number of chocolates is decreased by one. We will reveal the details of the requirements as we start implementing the snack machine functionality. For now, I hope you get the overall idea behind it. Another task we will have is modeling the work of an ATM. Here a user can withdraw some cash from the bank account with a small fee. For example, I can take a dollar and you can see that the number of $1 notes inside the ATM is decreased. And finally, we will need to move the accumulated cash from ATMs to snack machines, and also keep a record of the balance we have of all charges made by our ATMs. So this is the project we'll be working on in this course. I'll explain the design decisions we'll be making as we progress with our code, as well as common pitfalls developers usually run into when they start applying domain-driven design principles in practice. As we evolve our application, we will come up with different DDD notions gradually. I'll show how they fit the problem and how to choose between them, given particular circumstances.
In this module, you learned in what types of projects we should use the DDD principles. You also saw a quick overview of the course software design principles, such as YAGNI, which stands for implementing only the functionality you need right now, and KISS, which proposes the use of the simplest solution possible. These two principles can help greatly as you go along with your project. The beauty of domain-driven design is that its principles perfectly align with this tool. DDD helps you break a problem into consumable chunks and reduce its complexity to a level where it's no longer hard to understand and implement. We'll look to the main DDD concepts: ubiquitous language, bounded context, and core domain. We talked about the importance of communication with domain experts, which needs to be two-sided, and the importance of the domain knowledge itself. We discussed the notion of onion architecture and why we should keep the domain model isolated. We also looked at the pragmatic approach to unit testing, which gives us the best return of investments. Alright, the development is about to start now. The best way to learn how to solve problems with DDD is to actually solve one, so be prepared to dive into the problem domain in this course and walk through it with me. I also recommend to re-watch this module after we are done with the others, as you will be able to link the knowledge of the actual implementation with the groundwork we have set here.
Starting with the First Bounded Context
Hello, my name is Vladimir Khorikov, and this is the Domain-Driven Design in Practice course, starting with the first bounded context. In this module, we will begin the actual development process. We'll start off with the basic functionality for our model of a snack machine, inserting coins and notes to the machine and returning the money. You will see how entities differ from value objects and learn the best practices for working with them.
Before we start, let's take a minute to define the terms we'll be using in this course. You might have seen many of them in the past, but it's not always clear what they mean in different circumstances. First of all, I use the terms problem domain and domain interchangeably. They mean the actual problem our software is going to solve, the purpose it is built for. The term core domain is a subset of this, too. It is the essential part of the problem domain, the part which cannot be delegated to an external solution and must be resolved by us developers. Also, the terms business logic, business rules, domain logic, domain knowledge, and domain model are synonymous. You can think of them this way, the business logic you enclose in your code represents the knowledge that you as a software developer have about the problem domain. You express this knowledge by codifying a model of the domain you are working in, hence the term domain model. The business logic of an application typically resides in the two innermost layers of the onion architecture. I refer to the notions from these two layers as domain classes, so a domain class might be a repository, an entity, and so on, but not an application service. We will talk about the application services in the next module.
The first bounded context tool I will start with is snack machine. We will talk about the ways to represent bounded contexts later in this course. For now, we will focus on the machine model implementation. Our first task will be to model the way it works with coins and notes. So what are the operations we should be able to perform with money? Obviously we need a way to insert them into the machine. Also, if we change our mind, then we should be able to take the inserted money back. And finally, we may want to buy something. In this case, the amount of money we inserted should go to the machine permanently, and we should get back the change. We will take a shortcut in this module and implement a simplified version of the purchase functionality, and we will extend it in the future modules.
Starting with Snack Machine
We'll start the development with the core domain, the model of the snack machine. Here's the solution for our application. I named it DddInPractice. You can see there is only one project currently named Logic. In it, we will store our business logic, utilities, and data access code. This is the class for the SnackMachine, and note the use of the sealed keyword. It's a good practice to give your code as few privileges as possible by default. So, to insert money to the snack machine, we need to have a place where the coins and notes will be stored. The easiest way to do this is to introduce separate properties for each type of coin and note, so that we know exactly how many of them are in the machine right now. You can see here a separate property for the number of one cent coins, quarters, and so on. The actual method, then, could look like this. We accept an amount of money, and just add them to the money we have in the machine. So far, so good. The second function our snack machine should have is the ability to return the money we inserted in case the user changed their mind. This raises a problem, as we don't distinguish the money the user inserted from the money that was in the machine beforehand, so it can't say how much money we should return to the user. The solution here is to introduce a separate set of properties, which would define the amount of money that is currently in the transaction. This is the money the user will be able to get back or use to make a purchase. When the user inserts a coin or a note, this money is added to the money in transaction instead of the money that belongs to the machine, so we need to change the InsertMoney method, like this. The return method then just returns all the money that the user inserted. We can model this behavior by nullifying the corresponding properties. And finally, we should be able to buy a snack. For now, we can depict it like this. Just move all the money in transaction to the money inside the machine and nullify the transaction money after that. All right, let's overlook this implementation again. There is a concept here, which can be extracted out of the snack machine clause. I bet you already guessed that it's the notion of money. Indeed, we have two identical sets of properties. They represent the money the machine has, the amount of money inside, and the amount of money inserted by a user. We can introduce an abstraction, a new class called Money, and now we are able to move all the money-related members to this class, and also add an addition operator to it. What this operator does is it introduces a plus function. It takes two money instances and creates a new one, which consists of all coins and notes from the original two. Now we are able to add just a single property, MoneyInside, and remove all these properties from here. And another one for the money in trasaction. The InsertMoney method can also accept a money instance instead of the separate count parameters. Here we make use of the plus operator we introduced in the Money class. For the ReturnMoney method, we will also need to introduce some operation for clearing up the money instance to show that there is no longer any money in the transaction. We will talk about it later. For now, I'll just comment this line out. For the BuySnack method, we are moving all the MoneyInTransaction to the MoneyInside, and also nullifying the instance. You can see here after we brought a new concept, money, the code of the snack machine, became much simpler. This is no coincidence. You will see that introducing a new abstraction often makes the overall solution easier to understand and maintain.
Recap: Starting with Snack Machine
Let's recap what we came through in the previous demo. First of all, you saw we started with the core domain right away without modeling the UI or the database first. It's a good idea to always do this. You should always begin the project by experimenting with your domain model, as it's the most important part of your application. Secondly, always start with a single bounded context for all business logic in your application. Don't try to divide it into several pieces up front. While bounded contexts help reduce complexity of your code, the application is justified only when your code base is quite big already; otherwise, they won't reduce the complexity, but rather increase it instead. And thirdly, constantly evaluate your code and look for hidden abstractions. You saw that after we introduced the Money class, the code of the snack machine was simplified greatly. In our case, the presence of this abstraction was pretty adverse, of course, and it's not always possible to identify one in the most sophisticated situation. Nevertheless, if you see your code becomes awkward, you should test different approaches and examine if they fit your problem domain. It might be there is a hidden abstraction in your domain model that you didn't express just yet.
Entities vs. Value Objects
At this point, we approached an important topic, the difference between entities and value objects. In our code base, we currently have two classes, Snack Machine and Money. They seem pretty similar to each other, but their semantics differ significantly. The Snack Machine class here is an entity, whereas money is a value object. Let's elaborate on that. The main difference between entities and value objects is in the way we identify them. There are three types of equality when it comes to comparing objects with each other. Reference equality, identifier equality, and structural equality. Reference equality means that two objects are deemed to be equal if they reference the same address in the memory. Identifier equality implies a class has an ID field. Two instances of such a class would be equal if they have the same identifiers. And finally, with structural equality, we can see there are two objects equal if all of their members match. So how does the way we identify entities and value objects differ? The difference here is that identifier equality refers to entities exclusively, whereas value objects possess structural equality. In practice it means that value objects don't have an identifier field, and if two value objects have the same set of attributes, we can treat them interchangeably. It makes a lot of sense if you look at the notion of money in our domain model. If we have two $1 bills, it doesn't really matter which of them we work with. We don't care about their identity. We can easily replace one set of coins and notes with another one, as long as these two sets have the same composition. At the same time, even if two snack machines have the same amount of money inside, we don't treat them interchangeably. We do care about which of them we work with. You can think of it in a similar way you would think of two people bearing the same name. We don't treat them as the same person because of that. They have their own inherent identity. Another distinction between the two notions is immutability. Value objects should be immutable in a sense that if we need to change such an object, we construct a new instance based on the existing object rather than changing it. On the contrary, entities are almost always mutable. The next difference is that value objects cannot live by their own. They should always belong to one or several entities. In our application, money doesn't make sense without a snack machine. If it does, the Money class would be an entity itself. We can put it another way. There always should be at least one entity that owns a value object. Again, in our case, it is the snack machine class, which contains two properties of the money type. An important implication from this point is that value objects don't have their own tables in the database. We will talk about it in more detail in the next module when we'll be discussion persistence for our domain model.
How to Recognize a Value Object in Your Domain Model?
It's not always clear if a concept in your domain model is an entity or a value object, and unfortunately, there are no objective attributes you could use to get to know it. Whether or not a notion is a value object fully depends on the problem domain. A concept can be an entity in one domain model and a value object in another. For example, in our system, the Money class is certainly a value object. At the same time, if you build a software for tracking the flow of the cash in the whole country, you do need to treat every single bill separately, as you need to gather statistics for each of them. In this case, the notion of money would be an entity, although you would probably name it note or a bill. Despite the lack of objective traits, you can still employ some techniques in order to attribute a concept to either entities or value objects. First of all, you should take into account the notion of identity we discussed previously. If you can safely replace an instance of a class with another one, which has the same set of attributes, that's a good sign this concept is a value object. A good way of thinking of value objects is comparing them to integers. Do you really care if the integer 5 is the same 5 that you used in the previous function? Definitely not. All 5s in the application are the same, regardless of how they were instantiated. That makes an integer essentially a value object. Now, ask yourself, is this notion in your domain looks like integer? If the answer is yes, then it's a value object. In our application, we don't really care where the instances of the Money class come from, and they are really similar to integers because of that. All said above has an important application. Always prefer value objects to entities. Value objects are immutable and more lightweight than entities. Because of that, they are extremely easy to work with. Ideally you should always try to put most of the business logic into value objects. Entities in this situation would act as wrappers upon them and provide more high level instructions. Also, it might be that a concept you saw as an entity at first essentially is a value object. For example, an address class in your system could be introduced as an entity initially. It may have its own ID field and a separate table in the database, but after visiting it, you might notice that in your domain, addresses don't actually have their own identity and can be used interchangeably. In this case, don't hesitate to re-factor your domain model and convert the entity into a value object.
Entity Base Class
A typical application contains a lot of entities, so there should be a base class where you can gather common behaviors for each of them. Let's discuss different approaches for creating such a base class. The first option we have here is to introduce an interface, like this. That way we will be sure all domain entities in our model have some minimal functionality, the ID property. Although it might seem a good decision, in most cases, having an interface as a base entity is a bad idea. First of all, interfaces don't allow you to gather any logic in them. You need to implement this logic yourself, which leads to massive code duplication. Even in this example, you need to create the ID property in every single entity, which is a heavy violation of the don't repeat yourself principle. Secondly, the use of an interface doesn't show the appropriate relationship between domain entities. Implementing an interface means that your class makes a promise to have some functionality defined in the interface. Two classes implementing the same interface don't tell us anything about their relationship. They can be related, but it also can be that they belong to entirely unconnected hierarchies. In other words, the IEntity interface introduces a can-do relationship, whereas domain entities should be connected to the base class by an is-a relation. Not only such classes should have common functionality, but they themselves are entities. The only way for us to avoid these drawbacks is to introduce an abstract base class.
Demo: Entity Base Class
Here is the actual code for the entity base class. You can see it contains an identifier and equality operators. Let's walk through it together. As we discussed earlier, entities have their own inherent identity. The best way to represent this identity is to introduce a separate member, the ID property. It's a good idea to use a long type for this purpose by default, as such IDs are easier to work with than, say, GUIDs. The equality members consist of four methods. The Equals method is defined in the base object class and is often used by the .NET internally. For example, when we call the Contains method on the list of entities, a .NET base class library compares the objects inside the list with the target one by calling the Equals method. The default equals implementation gives us only reference equality. As we discussed earlier, entities also possess the identifier equality, so we need to override this method in order to introduce it. Here's how it looks like inside. We try to cast the other object to Entity, compare the two references to each other, that gives us the reference equality. If the type of the entities is not the same, they cannot be equal no matter what IDs they have. Also, if any of the identifiers are 0, it means that the ID was not yet set with the entity, and we cannot compare it to other entities, because its identity is not yet established. And finally, if all previous checks are passed, we compare the identifiers themselves, which gives us identifier equality. Aside from the Equals method, it's also a good idea to define the equality operator, so that we can use it in our code, like this. The only difference between the equality operator and the Equals method is that the former can accept nulls for both operants, hence these checks here. You can see if both of the parameters are null, we will return true. After that, we can call the Equals method we implemented earlier. The inequality operator is very simple. It just calls the equality operator and reverts the result. Finally, we also need to implement the GetHashCode method. It's important for two objects which are equal to each other to always generate the same hash code. Here the hash code depends on the object's type and identifier, which are the parts of the object's identity. If you want to learn more about this method, check out this great Stack Overflow answer. Some developers also implement the IEquatable interface on the Entity class, like this. You can see that the Equals method just converts the object parameter to the entity type and calls the new method. I tend not to do that, because this interface was designed for the use in value types, structs in other words, and doesn't provide an additional value in classes. Therefore, its usage here violates the YAGNI principle. You might have noticed that we don't set the ID property anywhere in this class. We will talk about it when we'll be discussing persistence for our domain model in the context of object-relational mappers. Now, as we have the Entity base class, we can inherit our snack machine from it. This is our first fully-fledged entity.
Recap: Entity Base Class
In this demo, you saw the Entity base class we'll be using in our application. It's important to remember that unlike value objects, each entity has their own identity. The best way to introduce this identity is to put it into the base class. The additional benefit here is that we are able to implement all required equality members in a single place. All entities in our domain model get them for free just by inheriting from the base class.
Value Object Base Class
Value objects don't have their own identity, and thus they shouldn't have the ID property like entities. It also means that we cannot place all code required for equality in the base class. In order to implement structural equality, we need to know the internals of each value object class. Nevertheless, we can still gather some logic common to such classes. Here's the code of the value object base class. You can see that we do override the Equals method, but delegate the actual work to the abstract EqualsCore method, and the same is for GetHashCode. We could just leave the Equals and GetHashCode methods without overriding them here, but this practice has two advantages. First, the new two methods are abstract, meaning that we won't forget to implement them in a derived value object class. The compiler will notify us about that. Second, we are making sure that the object common to the EqualsCore method is of the same type as the current valueObject and it is not null. Thus, we don't need to duplicate these checks in the derived classes, we can just gather them here. The equality and inequality operators are the same as in the entities. Now let's look at how we can use this class. We can derive money from it, and you can see as soon as we introduced the inheritance, the compiler tells us that we need to implement the two abstract methods. So let's do that. For the EqualsCore method, we need to check the equality for each of the properties. And the same goes for the GetHashCode method. All members of the value object should take part in the HashCode generation.
Recap: Value Object Base Class
Alright, let's reiterate it once again. The most important distinction between entities and value objects is the difference in the way we identify them. There are three types of equality. Reference equality belongs to both entities and value objects. Identifier equality to entities, structural equality to value objects. This means that each entity should have their own identity, which is best expressed using a separate ID property. It also means that we can create a single equality method that would feed every entity. Value objects at the same time don't have an identity, so they cannot have a separate ID field. The structural equality means that we need to implement the comparison logic in each value object class apart, but this task can be alleviated by factoring some common logic out to the base class.
Value Objects vs. .NET Value Types
.NET has a notion with a similar name, value type. The value type class is a base class for enums and primitive types like int, byte, and so on. Also, all structs inherit from it. Strictly speaking, there is no relation between DDD vale objects and .NET value types, because the former is a design concept and the latter is a technical implementation detail. However, there are some similarities in them. Value types in .NET are easy to make immutable and they implement structural equality instead of reference equality. In theory, you could use .NET value types to represent value objects in your domain model. In practice, however, this is not the best solution. Structs don't support inheritance, and because of that, you will have to implement equality operators in every struct separately, which would lead to code duplication. Also, structs don't interact particularly well with object-relational mappers. Therefore, despite the semantics likeliness between these two notions, we are better off using classes to implement value objects in our domain model.
When to Write Unit Tests
In the previous module, we talked about unit testing in the context of DDD. We discussed that we should cover with unit tests only the innermost layer of the onion architecture, entities, value objects, aggregates, and domain events. Now let's talk about when we should write unit tests. The process of test-driven development proposes that we employ the test-first approach. While it's true that DDD has a lot of benefits, in my opinion both test-first and code-first approaches are applicable in different circumstances. You can read about it in more detail in my blog post here. In short, there are basically two modes in which we write code. With the first one, we are pretty sure what we want the code to do, so we can create unit tests up front before we actually start implementing the required functionality. On the other hand, we might be exploring new areas in our domain model. When experimenting with different ideas in code, we are not exactly sure how the implementation should look like. In this case, we are better off not trying to come up with unit tests up front and just try the code without them. The reason is that while experimenting, we often rewrite our code and even throw it away completely, and unit tests would only slow us down with that. If so, that we wrote the first draft of our domain model without any tests. We were experimenting with code and that resulted in a new concept, the Money class emerged. And now as we are finished with the experiments, we are pretty sure about how the code should look like. It is time now to enter the test-first mode and continue the development adherent to the DDD process.
Implementing the Money Class
Let's start off by adding a new Visual Studio project for unit tests. Good. I'm going to use xunit as the test framework for our application, but other test frameworks would fit as well, of course. Also, we'll be using FluentAssertions. It's a library that helps write assertions in a more fluent way. That's pretty handy, as it increases test code readability. Alright, you can see here our two NuGet packages with their dependencies installed. I also use NCrunch as a test runner. It runs unit tests constantly on the background and gives a nice visual feedback in case something breaks. It's a paid plug-in and it's absolutely fine if you don't have it. You can achieve the same result by running your tests manually. You will see how NCrunch works in a minute. We will begin with the Money class. Let me rename this class created by Visual Studio to MoneySpecs. Perfect. I'm going to start by covering with unit tests the existing functionality. So the first test would verify the summing function. Let's call it Sum_of_two_moneys_produce_correct_result. Okay, so what we're going to do here is we're going to create two money instances, sum them up, and verify the resulting money instance contains the correct amount of coins and notes. Here's how we could write an assertion using a standard Assert.Equal syntax, but we'll use FluentAssertions for that. You can see this version of the same assertion mimics the English language in a sense that it reads from left to right, just as a regular sentence. That's basically it. This is the only reason why we use this library for our tests. It makes our tests a little bit more readable. All right, here are the other five assertions. As you can see, we are checking that all properties in the money instance were summed up correctly. These green circles here produced by NCrunch, they represent the test coverage. If we go here and replace one of the parameters with 0, you can see NCrunch tells us the implementation now doesn't comply with the unit test. And here's the exact line that checks that. This plug-in is pretty useful, as it helps eliminate the manual work required for running unit tests after each change. But again, it's perfectly fine if you just run them manually. Just don't forget to do that often enough so that you get very good feedback. I'm going to employ the classic three A test structure for this course. You can see this section here is the Arrange section. The following two are Act and Assert. I usually don't write those comments and just separate these sections with a blank line. The other already existing functionality we need to test is the one with regards to equality. It's a good practice to always check that two value object instances with the same data of the same type are considered to be equal, because as we know, .NET employs the reference equality for all classes by default. Let's make sure the Money class has this structural equality. Let's name the test Two_money_instances_equal_if_contain_the_same_money_amount. So basically what we want to do here is we want to create two instances like this and verify the first one is the same as the second one. Again, we use FluentAssertions library for this, which calls the Equals method behind the scene. And we'll also check they have the same hash code, too. Good. With another test, we will check that Two_money_instances_do_not_equal_if_contain_different_money_amounts. So, let's create a dollar first and then a hundred cents. The dollar, although it has the same value, shouldn't be equal to hundredCents, because in our domain model, we do need to distinguish such things. The hash code should differ as well. All right, now as we are done with the existing functionality, let's think about what other methods we need from the Money class. The first thing to ponder should always be the edge cases. What are they in this particular case? For example, can we create a money instance with no value or with a negative one? The empty money instance does make sense in our domain model. After all, the snack machine we are modeling can have no money inside at some point. On the other hand, a negative value doesn't make sense and should be prohibited. So let's create a test for that. I'll name it Cannot_create_money_with_negative_value. This would be a parameterized test. Basically what we are doing here is we are trying to pass a negative count to each of the parameters the constructor has. Hence, we need to exercise all parameters one by one. To verify they all fail, I'm going to define an action delegate and check it throws an exception. You can see the test fails. That's because right now nothing prevents us from creating legal money objects. Let's fix that. If oneCentCount is less than 0, then we should throw an invalid exception. And we need to replicate this validation for all parameters. The test is passing now; very good.
Building up the Money Class
At this point, it is pretty clear we will need at least two more methods from our Money class. Obviously, we will need to get the value a money instance represents so that we are not forced to calculate it manually all the time. Also, it is logical to have a subtraction operation along with the addition one, so we need to implement it as well. I need to make an important warning here. Always follow the YAGNI principle and refrain from adding any functionality up front. Remember, this principle is one of the most important, and you should always adhere to it when developing software projects. It might seem that by adding these two features to the Money class, we are violating this principle, because they are not used anywhere in our domain model yet. But I'm taking a shortcut here. I know that we will need them in the near future, because I already went through the development of this application. In a real world situation, you should always try not to introduce any code until you see that you cannot solve the task we are working on without it. All right, so let's add another test here. We can name it Amount_is_calculated_correctly. This also would be a parameterized test so that we would be able to run it against a set of different use cases. Here they are. You can see that, for example, when we create an empty money instance, we expect its value to be 0. For a single cent, it would be 1 cent, and so on. Basically I'm just throwing a pile of cases I calculated manually, creating an instance, and verifying the amount it represents is equivalent to the expected amount. At this point, we don't have the amount property yet, so let's add it. Following the test-first approach, we need to make sure our test fails before implementing the required functionality, so I'm writing the simplest implementation possible, just to make the application compile. The letter m here means the literal value is of the decimal type. The test fails as expected. NCrunch shows that one of the use cases expects the value to be 500, but received 0 instead. The actual implementation is pretty simple. We just need to sum up all the coins and notes the money instance has, taking into account their delimitation. And you can see all tests are passing now. It's a good time to make some refactoring for our code at this point. You can see the properties of the Money class have public setters, which means that they can be easily changed by the external code. Remember, one of the attributes of value objects is that they should be immutable. To implement immutability, we could just hide the properties from the outside world by making them private, like this. But I'm using C# 6 here, so I can use one of the latest features it introduces, read-only properties. To implement a read-only property, I just need to remove the setter, and here we are, our class is now completely immutable. Another feature C# 6 adds is expression-bodied members. It's a good idea to use it for the Amount property. Perfect. The last method we need to implement is the subtraction operator. Let's add a test, subtraction_of_two_moneys_produces_correct_result. So basically what we need to do here is we need to take two instances and subtract one from the other. The resulting money instance should have the correct counts for each property. An important edge situation in this case is that we can try to subtract more than we have, so we need to make sure we prohibit such separations as well. I name the test Cannot_subtract_more_than_exists. Declare on two instances, and note that the first one has only one 10 cent coin and the second one contains a single cent. We cannot subtract one from the other, because there are not enough coins to perform such subtraction, so we need to make sure such separation throws and exception. All right, so just as before, we are implementing the test in the simplest and obviously incorrect way, just to make sure the tests fail. And you can see they do fail at this point. This unit test, for example, expects 9, but gets 0 instead. What we need to do here is we need to subtract every single property from each other. This makes the test pass. Perfect.
Implementing the Snack Machine Class
Let's start implementing the snack machine with the simplest method, ReturnMoney, creating another file, SnackMachineTests. We can call the test Return_money_empties_money_in_transaction. I create a SnackMachine here and insert a dollar inside. And you can see, the test doesn't like what we've done here so far. The problem is that we are trying to use the MoneyInTransaction instance without initializing it. That's one of the benefits continues test running provides us. We get the feedback very quickly. All right, so let's add a constructor, assign an empty instance to the MoneyInside, and the same for the MoneyInTransaction. Good. The tests went green again. Calling the ReturnMoney method, and verifying the amount of MoneyInTransaction is 0. The test fails, and now we need to implement the method itself. Here we need to somehow clear the money that is currently in transaction. The first option that could come to mind is to introduce a clearer method and call it here, like this. This solution is not the best one, as it violates the immutability of the Money class. In order to implement such a method, we would need to change the state of the existing money instance. A better way of dealing with it is to just overwrite the instance with a new one, like this. This way we are keeping the money value object immutable. You can see the tests are passing, so now we can do a bit of refactoring. First of all, creating a money instance and passing all six parameters to it all the time is a bit annoying. As our Money class is a value object, it's a good idea to create a static read-only field for the empty money instance. Here it is. Now we can replace the constructor with this field. And it turns out we can create a field for each coin and note as well. Cent, 10 cent, quarter, and so on. This enables us to simplify our test like this. Another thing we can do here is we can employ the new features of C# 6 and simplify our code even further. Static using statements allow us to import a separate class and use its static members without specifying the class name. So, Money.Dollar here turns to just Dollar. And we can do the same in the snackMachine class. Another C# 6 feature is property initializers. I can just assign the money instances the initial values and remove the constructor. All right, another method we are going to cover with tests is the InsertMoney method. We can call the test Inserted_money_goes_to_money_in_transaction. Create a new SnackMachine, insert in a Cent and a Dollar, and checking the amount of money is correct. The test passes, because we already implemented this functionality. But let's make sure the test doesn't give us false positives by commenting this line here. Very good. I can uncomment it back again. At this point, we need to once again think of possible edge cases. Is it possible to insert not a single coin or note, but several of them all at once? That is probably a question we need to ask the domain expert, because at this point it's not obvious if the model we are building should support such a use case. And remember, communication between the developers and domain experts is vital and the domain during design practices put a great emphasis on this part of the development process. Let's say that we together with domain experts decided our domain should only accept a single coin or bill at a time. That makes sense, because that's how a real snack machine's receiver works. So we need to add another test to verify that. Cannot_insert_more_than_one_coin_or_note_at_a_time. In the test, we need to create a twoCent money instance and try to insert it to the machine. The test would pass if the machine throws an exception. All right, to implement this, we need to specify all possible values we accept in the Insert method and check if the incoming instance is one of them. Good. And finally, we need to finish the BuySnack method. We can add a test named Money_in_transaction_go_to_money_inside_after_purchase. Here we are inserting two dollars to our machine, call in the BuySnack method, and check the MoneyInTransaction is empty, and the MoneyInside contains two dollars after that. The implementation is pretty simple. We just need to assign the MoneyInside instance an empty value here. We are done with the first draft of the snack machine. It obviously lacks some significant parts. For example, the Buy method should definitely deal with snacks somehow and not just take all inserted money. We will address this in the later modules when we'll be discussing aggregates and repositories. Also in the real world application, you would probably want to write more unit tests. For example, it's a good idea to check that a newly created snack machine doesn't contain any money in it. But I'll leave it aside just to keep our development process less verbose.
Recap: Implementing Money and Snack Machine
In the previous three demos, you saw how we implemented our first version of the Money and SnackMachine classes. A note that we kept the money value object immutable. Whenever we needed to perform such operations as addition or subtraction, we created a new instance of the class without changing the existing ones. The SnackMachine entity, on the other hand, is mutable. Mutabilty is an inherent trait for all entities. They change their state throughout the lifetime while maintaining immutable and unique identity. Also note that the SnackMachine entity contains not as much logic comparing to the money value object. All it does at this point is delegates most of its operations to the Money class. This is another good practice I mentioned earlier. Always try to delegate as much logic as possible from entities to value objects. Value objects are easy to maintain because of their immutability. During this phase of development, we stuck to the TDD process, meaning that we wrote the tests first, and then implemented the required functionality according to those tests. I intentionally demonstrated the whole process to show you how nicely domain-driven design can be combined with test-driven development. In the next modules, however, I will depart from adhering to the test-first approach just to speed our development up. But keep in mind that it's a good practice to follow this simple rule: write the code first when you're experimenting with your model and switch to the test-first mode when you have a good picture of how the code should look like. And, of course, while the test-driven development practices can be beneficial, you don't have to follow them in order to do domain-driven design, but remember to always cover your domain model with unit tests regardless of when you prefer to write them.
In this module, we started the development with the first bounded context, which is snack machines. We focused on entities and value objects. You learned the differences and best practices behind these two notions. Let's reiterate through the most important moments of this module. First of all, try to begin the development by working with the core domain first. Your core domain, along with the unit tests covering it, is the most important part of the application you are building. Secondly, always start the development with a single bounded context. There is little value in dividing your application into several pieces up front, as you usually don't have enough information to make a competent decision about the proper boundaries at this point. Constantly evaluate your code and look for hidden abstractions in it. If you see your code becomes unclear or convey its meaning vaguely, it's a strong sign you missed an important abstraction. There are three main distinctions between value objects and entities. The first one refers to the way we identify them. There are three types of equality when it comes to comparing objects to each other. Entities possess identifier equality, meaning that we treat two entities as being the same only when they have the same identifier. Value objects, on the other hand, have structural equality. We can see there are two value objects to be equal if all their fields are the same. Value objects cannot have an identifier. The second distinction is that value objects are immutable data structures, whereas entities change during their lifetime. And finally, value objects cannot live by their own. They should always belong to an entity. In our application, for example, the money value object doesn't make sense without a snack machine. A snack machine composes too many instances. It also means that value objects shouldn't have their own tables in the database. We will elaborate on that topic in the next module. We discussed the techniques for recognizing a value object in the domain model. A good approach here is to compare one to an integer. If they have essentially the same semantics, you can be sure the class you are looking at is a value object. We also talked about an important practice of moving as much logic to value objects as possible. Value objects are lightweight and therefore are extremely easy to maintain and reason about. We looked at the code of the base entity and value object classes, and also saw why we cannot use .NET value types to create value objects. We discussed test-driven development in the context of domain-driven design and two approaches to write in unit tests. We will talk more about unit testing in the following modules. All right, we've made good progress here. In the next module, we will see how to introduce user interface and a database in the way that helps keep our domain model clean and isolated.
Introducing UI and Persistence Layers
Hello. My name is Vladimir Khorikov and this is the Domain-Driven Design in Practice course, Introducing UI and Persistence Layers. In this module, we will talk about the user interface and the database for our domain model. We will see how they feed the application built with the domain-driven design principles in mind, and how to work with the object-relational mapper in a way that allows us to keep our domain model simple and clean.
Application Services and UI in the Onion Architecture
Adding UI for the Snack Machine
I added a new project for our client, DddInPractice.UI. I've made some preparatory work to make the WPF application up and running. You can see Common, Images, and Utils folder and a few files here. Don't worry about them. It's just some orchestration, and is not essential for the UI we are going to focus on. I also added a View for the snack machine with all UI elements and bindings needed. The details of how those bindings work are not essential for this course either, so we won't dive into them. This is the ViewModel. Currently it contains only a caption string, which is used by the View to display the title of the window, and this is the UI itself. It doesn't do anything at this point, because we didn't define the required properties in the ViewModel yet, and that's what we're going to do here. We are going to build the ViewModel. Remember, we discussed that the ViewModel acts as a wrapper on top of an entity and provides the functionality required for the View. To implement this, we need to get the entity somehow, so I'm going to introduce it as a constructor parameter, and then save it to a private field. You can see the project doesn't compile anymore. That's because we need to pass a SnackMachine to the ViewModel in the place where we instantiated this ViewModel. I'm just creating a new SnackMachine object here, so anytime we run the application, it will work with a new instance. We will change that when we introduce persistence for our domain model. All right, so let's start off by displaying the amount of money we inserted into the machine. It will be shown here near the money inserted label. This label is bound to the MoneyInTransaction property, so we need to create one. We can implement one by calling the corresponding property of the SnackMachine instance and converting the money amount to string. Now if we run the application, you can see the money inserted is displayed and is currently 0. The problem is that it doesn't change if we try to put money in, so let's fix that. The button that puts a single cent into the machine is bound to InsertCent command, creating a property for it, and defining it in the constructor. The command will call a private InsertCent method, which in turn will insert a single cent to the snack machine, like this. We also need to notify the View so that it refreshes the MoneyInTransaction label on the screen. This is the technique that lies in the foundation of MVVM I described earlier. A View calls a command, InsertCentCommand in our case, form the ViewModel. The ViewModel processes the call by changing the state of the entity and raises an event so that the View can update itself to display the latest data from the ViewModel. All right, let's add commands for the other buttons as well. We could implement them like this, introducing separate methods for each of the commands, but it's easy to create a single one and just pass a required money instance to it. So here I'm passing one cents, 10 cents in this method, and here are the other four commands. So now if we try to put money into the machine, you can see the money actually goes there. The money inserted label displays that. One thing we can adjust in the way the money amount is displayed is we can put a dollar or a cent sign in front of the number. It's a good idea to implement this functionality in the Money class itself by overriding the toString method. So if the amount of money is less than a dollar, we are returning the amount of cents, like this. Otherwise, the method returns the number of dollars. And it's a good idea to also add a test for this method. You can see if we create a money instance with a single cent, we expect the toString method to return a one cent string. For a single dollar, it should be one dollar, and so on. Starting from this module, I depart from that test first approach we discussed previously, just to speed the development process up. But keep in mind that it's a good idea to adhere to this approach in this particular case, because we know exactly what we need the toString method to do. Now I can remove the amount property from here and just call the ToString method on the money instance itself. And here we go, the nice symbols are now shown in front of the inserted amount of money. The next thing we can implement here is some information message to inform the user about the last action they performed. So let's add a message property in our ViewModel. It's a simple property with a back field. I put the Notify method call in its setter, so that it automatically notifies the View whenever we change its value. We won't need to do it ourselves, like we did with MoneyInTransaction. Also note that we don't specify the name of the property here. If we don't indicate a name explicitly, the Notify method just gets the name of the method or property it is being called from, message in this case, and that's exactly what we need here. We can use this property in the Insert method by assigning it You have inserted whatever amount of money is passed to the method. And we don't actually need to call ToString here, the compiler will do it for us. So now when we insert a coin or a note to the machine, we get a nice message here. The next thing to consider is the Return money button. Let's add a command for it. The method itself calls the appropriate method on the SnackMachine class, notifies the View to update the MoneyInserted label, and updates the message. If I put 11 cents into the machine and return them back, you can see the money inserted turns back to 0. All right, we still have this whole section inactive here. It requires a MoneyInside property, so let's implement it as well. A nice thing about bindings is that they can work not only with string properties, but also with complex data structures. In our case, the MoneyInside section on the screen expects a property of the money type. To implement it, we can just sum up the money inside the machine and the money that is currently in transaction. It makes sense, because we don't need to differentiate the money that belongs to the machine from the money inserted by a user, and can just show both of them as a whole. Now as we have another property the View is bound to, we need to notify the UI when we change anything that can affect this property. In our case, both ReturnMoney and InsertMoney methods can affect the money inside, so I add a notification regarding this property to both of them. You can see as we put coins or notes to the machine, the View reflects the actual number of them, and the overall value they represent. Very well, the last thing we need to do here is we need to implement the buying functionality. So, I add one more command to the ViewModel and define a private method for it. In it, we need to delegate the execution to the SnackMachine entity, tell the user they have both a snack, and once again notify the View about both of the properties. Now if I insert, for example, three dollars, and buy a snack, you can see the money inserted is discarded, which means all inserted money are now the property of the machine, and we cannot retrieve them back. In contrast, if I insert some money and return it, both the money inserted and the money inside revert to the initial values. Very good. The first version of the user interface is ready. But before we finish with it, let's do some refactoring. First of all, in the InsertMoney method, the name of the input parameter doesn't actually reflect the meaning of it. We are not inserting just some arbitrary money inside the machine. We can only insert a single coin or a single note. So let's rename it to coinOrNote. Perfect. Secondly, you can see some duplication in the way we notify the View about the changes the commands make. All three methods do essentially the same here. The only difference is in the messages they use to show on the screen. We can extract the notification portion to a separate method, notify client. Now we can just call it in all three methods with different messages. Finally, note that we are passing literal strings to the Notify method to specify the names of the properties. That might lead to problems if we decide to rename those properties and forget to change the strings. In this case, notification would no longer work as intended. To avoid such situations, we can use another feature C# 6 introduced, the nameof operator. So instead of the literal string here, I use nameof MoneyInTransaction, and the same for the MoneyInside. Now if we decide to rename one of these properties, the compiler would notify us in case we forget to also change these lines here. Internally the compiler just replaces all nameof invocations with strings, so there are no performance drawbacks in this solution.
Recap: Adding UI for the Snack Machine
In the previous demo, we saw we built a UI and Application Services layer. In our case, the UI layer was represented by XAML Views, Application Services layer by ViewModels. In ASP.NET MVC solution, for example, controllers would act as Application Services and HTML views would be the UI layer. The Application Services layer usually acts as a mediator between the outside world, which is user interface, and the domain layer. This layer itself shouldn't contain any business logic, only coordinate the communication between different elements of the domain and validate the inputs from the UI. In our case, the ViewModel we created acts as a wrapper on top of an entity. It augments it with the functionality required for the view. It didn't contain any business logic, but rather delegated it to the entity. Application Services can work with other domain classes as well, such as repositories. We will see it later in this module. It's important to find a proper place for the logic we are adding to our code base. Some of it makes sense in the domain layer. For example, you saw we implemented the ToString method as part of the Money class. Other logic looks better as part of the Application Services layer. For instance, when we were working on the MoneyInside property, we added it as a part of the ViewModel. You can use the following rule of thumb when you want to decide between the two. If the piece of logic you are looking at makes sense only in the context of UI, then it's probably better to place it into an Application Service, ViewModel in our case, otherwise, it should be part of the domain layer.
Designing the Database for the Snack Machine
It's a good time now to discuss the database design for our domain model. We currently have only two classes in it, SnackMachine and Money. We need to decide which of their properties we want to persist in the database. That is another subject for discussion with a domain expert, because such decisions would affect the overall model behavior. For example, if we decide to store both money properties, that would mean we want to save the model's state not only between the transactions, but also in the middle of it. Let's assume that we, together with a domain expert, decided that it's okay for the machine model to be persisted only when a transaction is completed. In other words, when the purchase is done. So we're not really interested in saving the MoneyInTransaction property in this case, we assume that if the application crashes before a user completes a purchase, it's fine for the model to lose track of the money that was inserted before this point. All right, that leaves us with two classes, the SnackMachine Entity with a single field, and the Money ValueObject with six fields. How would the database look in this case? One of the options that might come to mind is to create separate tables for each of them, like this. You can see the SnackMachine table in this diagram contains a reference to the Money table. While these might seem a good idea, such a design has two major drawbacks. First of all, the Money table contains an identifier. It means that we will have to introduce a separate ID field in our domain model in order to be able to work with such a table correctly. This in turn means that we need to give the Money ValueObject some identity, and that violates the definition of value object. The other drawback is that with this solution, we can potentially detach value objects from entities. The Money ValueObject can live by its own, because we are able to delete a SnackMachine role from the database without deleting a corresponding Money role. That would be a violation of another rule, which states that the lifetime of value object should fully depend on the lifetime of the apparent entities. It turns out that the best solution in this case is to inline the fields from the Money table to the SnackMachine table, like this. This would solve all the problems I stated earlier. We don't give an identity to the Money ValueObject, and its lifetime now fully depends on the lifetime of the SnackMachine Entity. And it makes sense if you mentally replace all the fields that regard to Money with a single integer, as I suggested in the previous module. Do you create a separate table for an integer? Of course not. You just inline that integer to the table you want it to be in. The same applies to the ValueObjects. Don't introduce separate tables for them, just inline them into the parent entities table. So now as we've come up with a database structure for our domain model, let's see how to link them together.
Introducing an ORM
In this course, we'll be using NHibernate as an object-relational mapper, so let's install corresponding NuGet packages. The first one would be the NHibernate library itself, and the second one is FluentNHibernate, which enables fluent mapping for our domain classes. We will talk about different mapping structures in a few minutes. To make NHibernate work, we need to introduce some auxiliary code, namely SessionFactory. I'm copying its implementation here. Let's walk through it together. One of the key elements in most ORMs is the notion of session. You might have heard of it as context if you use such mappers as Entity Framework or LINQ to SQL. It keeps track of all objects loaded from the database into memory, and automatically updates the corresponding roles in the database according to the changes made to those objects. Session implements the unit of work design pattern, meaning that it pushes all accumulated changes at once, usually at the end of its lifetime. Also, it is lightweight and designed to be created and disposed of frequently. A common guideline for working with sessions is to create a separate session for each interaction, which takes place between the user and the application. SessionFactory, on the other hand, is a heavyweight class. There should be only a single instance of it per each database. Here we introduce the initialization method that takes a connection string, creates an instance of the SessionFactory, and saves it in the static field for the later use. The BuildSessionFactory method uses FluentNHibernate library to fluently configure the ORM. You can see here we specify that our application uses a SQL Server database. NHibernate supports most of the popular relational databases, so you can use it with any other database, for example, Oracle or MySQL. With this line, we tell NHibernate to get the mappings from the current assembly, and we also specify some useful conventions. For example, here we are saying that all referenced entities should have a corresponding column name that ends with ID. All fields in the database should be treated as not nullable by default, and all tables should have the same names as the corresponding entities classes. With such conventions, we will reduce the amount of repeatable work needed to set up the mappings between our domain classes and the database tables. A more interesting convention here is the one that regards to identifier generation. But before explaining what this convention means, let's discuss different ID generation strategies.
ID Generation Strategies
There are three popular methods to implement ID generation for domain entities with SQL Server. One of them is to rely on the SQL Server's identity feature. We can define an ID column in such a way that every time we insert a new role, SQL Server will attach a new identifier to it. So when we create a new entity in the memory and then save it, the ORM pushes a new insert to the database. The database generates a new identifier, which the ORM then retrieves and assigns to the entity in the memory. While this solution allows us to leverage the built-in SQL Server functionality and generate good-looking integer values, it has a major drawback. In order to actually sign an identifier to an entity, NHibernate has to perform a roundtrip to the database and that doesn't play well with the concept of unit of work. It means that in some cases, we are no longer able to execute all changes made during a session at once. Another way to generate an identifier is to use GUIDs. It doesn't contain the drawback I described, because GUIDs can be easily generated on the client side, so there is no need to make a roundtrip to the server. The only shortcoming with this approach is GUIDs themselves. They may be cumbersome to work with, and you might prefer to have integer identifiers instead. If this is the case, you can use the third option, Hi/Lo algorithm. The idea behind this algorithm is that you can devote the client not just a single ID, but a whole batch of identifiers at once, which then can be distributed among new entities. This is usually done by having two numbers, high and low. High is the number of the batch, whereas low is the number of identifier in that batch. High numbers are stored in the database, so for example, let's say that the batch size is 10 and the current high number is 5. When a client application is about to set a new ID to an entity, it increments the high number stored in the database and thus gets the batch number 5 with the identifiers ranging from 50-59. The client then can safely use these identifiers to assign to new entities, knowing that they are reserved for it exclusively. But this approach also means that we need to have a separate table to keep track of the batches that have been issued so far, like this. You can see there is a row for the SnackMachine entity. Basically all entities in our model will have separate rows here. You can check out the Stack Overflow answer if you want to learn more about the Hi/Lo algorithm. Generally speaking, it is mostly a matter of taste whether you choose GUIDs or Hi/Lo. For this course, I use the Hi/Lo algorithm, but it's perfectly fine if you prefer GUIDs instead. So, returning to the convention for our session factory, you can see now what all of these parameters mean. This is the name of the table where all batch numbers are stored. This is the column name for the batch number itself. This is the size of the batches, and this is the filter statement. It is used to determine which of the rows NHibernate should look into to get the next ID for a given entity.
There are three approaches when it comes to mapping domain entities to tables in the database, XML files, attributes, and fluent mapping. XML files are quite verbose and are prone to break when refactoring, because they operate literal strings to define the names of the properties being mapped. Attributes is a better option. They don't have the shortcomings of XML files, but they do have another one. Remember, we talked about the importance of isolation. We discussed that the innermost layer of the onion architecture, entities, value objects, aggregates, and domain events, should have a single responsibility, which is representing the domain knowledge of your system. Add in mapping attributes directly to entities, means they start holding the knowledge about how they are being stored in the database. Therefore, this mapping strategy violates this single responsibility principle. Because of that, we are better off choosing the fluent mapping approach. With this approach, we can create separate map files for each entity. So, let's create one for the SnackMachine. I call it SnackMachineMap. The map file itself would look like this. You can see the ID field is mapped using the Id method, which allows us to specify the Identifier property for the entity. Also note that we map only the MoneyInside property. We don't store the MoneyInTransaction field. The Component method is the method that allows us to inline the value objects fields into the parent entities table. You can see we list all six properties here. That means each of them has a separate column in the SnackMachine table.
Adjusting the Domain Layer for the Use with ORM
At this point, we have the database and the mappings in place, so we are almost done with the persistence for our domain model. If I add a temporary test and write code like this, I should be able to get a SnackMachine from the database. What I'm doing here is I initialize the SessionFactory with the connection string, create a session, and try to retrieve an entity with id 1, which I added to the database manually beforehand. As you can tell, this doesn't work. The problem here is that we need to adjust our domain model for the use with the ORM. When we discussed isolation in the first module, I mentioned that although we should try to isolate our domain model from the persistence logic as much as possible, it's not always possible. In many cases, especially when using an ORM, we still need to adapt the domain layer and there still is some persistence logic leaking into the domain model. Luckily, the degree of the leak is not too big, and the tradeoff we are making here is worth it. So, how exactly we need to change our domain model in order to use it with the ORM? First of all, NHibernate requires all entities to be unsealed, so we cannot use the sealed keyword here in our SnackMachine. Secondly, we need to mark all non-private members in the entities as virtual. Also, the setters for the properties cannot be private, so I mark them as protected. And finally, all entities and value objects should have a parameter-less constructor. The good news here is that it doesn't have to be public. The SnackMachine class already has such a constructor. The compiler adds it automatically if we don't define it ourselves, but we need to add one to the Money value object, like this. And I'm calling it here with the this keyword, just to make resharper stop indicating it as redundant. You don't have to do this, of course. And we need to make the ID property in the base entity class virtual as well. The reason why these changes are required is because NHibernate creates proxy classes on top of entities and overrides all un-private members in them. So it needs those entities to be unsealed and also have all members marked as virtual. The parameter-less constructor is required because NHibernate creates those entities' reflection. The last thing we need to modify is the way we get the type of an entity. Here we check if the two entities we compare of the same type by calling the GetType method. This might cause problems in scenarios with lazy loading, as NHibernate creates proxy classes that inherit from the classes that are being wrapped. The GetType method will return the type of the proxy, not the type of the underlying entity. To fix this, we can introduce a GetRealType method, which would retrieve the real type of the entity regardless of whether there is a proxy on top of it. It makes use of one of the utility methods in the NHibernate library. Now we just need to replace the GetType method invocations. Good. We've solved the problem. That's basically it. The test is passing now, and if we try to debug it and look at the SnackMachine it returns, you can see there is a single SnackMachine currently in the database with some money inside, and the test successfully fetches it into memory. As I said, the leak of the persistence logic into the domain logic isn't too big, and we still preserve a lot of isolation for our domain model. For example, we didn't have to change any of the existing tests that validate our domain classes. It is possible to maintain the same high degree of isolation even in larger projects, and you will see how to do it later in this course.
Putting It All Together
Now as we've set up persistence for our domain model, let's incorporate it into our UI layer. First of all, we need to initialize the SessionFactory. It's a good idea to perform all initialization as close to the startup of your application as possible. This starting point is usually called composition root. In our case, the composition root resides in the App class. It is the class that is instantiated first when the application launches. So, let's add a constructor. What we could do here is we could call the SessionFactory Init method, like this. But I personally prefer to gather all initialization logic in a special class, and then use it in the composition root. That way we can enable decoupling between the UI and the utility classes, like SessionFactory. Let's add a new class, Initer. There would be only one static method which would call Init of SessionFactory. Good. And I can make the class itself static as well. We will add other initialization logic here later in this course. Now we can use this class in the app constructor. I am passing a literal string as the connection string here, but in a real world application, it should probably be taken from a configuration file. Currently when MainViewModel creates a SnackMachine ViewModel it passes it a new SnackMachine on each creation. We can replace it with a SnackMachine taken from the database, fetching the machine from the DB, and passing it to the View. Don't worry about the explicit session creation at this point, we will refactor this code in the next module when we'll be talking about repositories. For now, I just want to demonstrate the persistence code in action. If I run the application, you can see the SnackMachine contains one piece of each coin and note. It means they are successfully taken from the database. The last thing we need to implement is saving the SnackMachine after purchase is completed. To do this, we need to go to the ViewModel and persist the SnackMachine in the BuySnack method, creating a session, starting a new transaction, saving the SnackMachine, and committing the transaction. And note the use of a transaction here. We don't have to use it in this particular case, because there is only a single operation away, but it's a good habit to use transactions when we take data in the database we ensure automacy of the updates. And again, we will refactor this code in the next module. Our ViewModels definitely shouldn't work with the persistence concerns directly. Let's see if it works. If I insert, for example, 3 cents and click on the Buy button, the new amount of money should be persisted in the database, and it is. You can see there are four 1 cent coins now attached to the SnackMachine. Perfect. Our first draft of the SnackMachine is ready, and we can move on and build up new functionality on top of it.
In this module, we introduced UI and persistence layers for our application. We discussed the MVVM design pattern and where the parts model Model View ViewModel belong in the onion architecture. We talked about the importance of finding a proper place for the logic we are adding to our code base. We've discussed the database for the snack machine. We talked about best practices for persistence value objects. They shouldn't have their own tables, but rather be inlined in the parent entities table. You saw how to use an ORM to persist the domain model, and also what changes must be made in order to comply with the NHibernate requirements. For example, we had to make the SnackMachine entity unsealed, and mark its known private methods as virtual. I believe it is still a good tradeoff. ORMs free us from a lot of manual work on one hand and allow us to maintain a good degree of isolation for our domain model on the other. We discussed pros and cons of different Id generation strategies. We also looked at mapping strategies and saw what is the best feed for us and why. Finally, we put all pieces together and saw how our domain model is displayed and being persisted in practice. In the next module, we will talk about other two fundamental DDD concepts, aggregates and repositories, and we'll see how to apply them to our project.
Extending the Bounded Context with Aggregates
Hello, my name is Vladimir Khorikov and this is the Domain-Driven Design in Practice course, Extending the Bounded Context with Aggregates. In this module, we will extend our SnackMachine model with the actual purchase functionality. Along the way, we will discuss best practices for working with aggregates, and we'll see how they are applicable to our project.
Currently our SnackMachine model doesn't actually make any purchases, it just appropriates whatever money a user inserted without giving anything back. Obviously that's not how real snack machines work, and we need to extend the model's functionality. So, what are the requirements for the functionality we need to implement? First of all, the snack machine should have three slots, each of which may contain several snacks of the same type and of the same price. The snack machine should allow us to buy any item from those slots. There are several business rules that should be followed here. First of all, if a user inserts more money than needed, the machine must return back the change. If the money inserted is not enough, or there are no products left in the slot, the user should get an error message. And finally, it might happen that there is not enough change in the machine. We should handle such situations as well. As usual, we will start with the simplest implementation possible and iteratively move towards a better design. We will see how to handle one 1-to-many relationships between domain classes, how to combine several entities into aggregates, and how to find proper boundaries for them.
Starting with the Implementation
Let's see how we can implement the new requirements in code. This is how our domain model looks like currently. To handle the new purchase functionality, we need to add two new entities, snack and slot. The Snack class will contain only Name field. The Slot entity should have links to both a Snack and a SnackMachine, and also contain the information about the slot, its position in the machine, and the price and the number of the snacks residing in the slot. All right, let's add a new class, Snack. This would be an entity, so I'm inheriting it from the Entity base class. And it will contain only a single name property. Add in a parameter-less constructor to make it work with NHibnerate, and another one accepting a name. And I need to make the property virtual. Perfect. Another entity we need to introduce is Slot. Here are its properties. And I have to add two constructors here as well, a default one, and the one which accepts all properties required to initialize a slot. This is the SnackMachine class. We'll start by modifying the existing test that covers the BuySnack method. Let's rename it to BuySnack_trades_inserted_money_for_a_snack. First of all, before we buy anything, we need to put snacks into it somehow, so we'll need to implement a corresponding method. And two, we'll need to validate that the number of snacks decreased after the purchase. I am placing some row text here, which obviously wouldn't compile. You can view it as a part of the red, green refactor practice. I don't have an API for this kind of validation just yet, so I cannot use the actual code here, but I can put some placeholder to not forget to do this in the future. This text acts as such a placeholder. All right, creating the LoadSnacks method, the Slot number where we need to put the snacks, a snack instance, quantity, and price. In order to implement this method, we need to introduce a storage for the snacks, so I'm adding a corresponding property, which would hold the slots the machine has. Good. It is of type IList and not just List, because NHibernate requires collections that are part of the mapping to be either of ICollection or IList types. We will talk about the collection mapping later in this course. Now we can find a slot base position and assign a new snack, quantity, and price to it. And we need to make setters for these three properties public here. All right, we can load some snacks to the first slot of the machine with quantity 10 and price 1 dollar. We'll need only a single dollar to buy this snack, so I'm removing the second InsertMoney method call. Note that we currently call the BuySnack method without any parameters. We need to somehow specify what snack we are buying. The best way to do this is to pass the position of the slot, because that's exactly how the end user would approach the task. He or she just needs to indicate in what slot they are interested in is located. We've loaded the snack into the first slot, so I'm passing one here as well. And now we can verify that the number of the snacks has changed. It should be nine after the purchase. All right, let's make our code compile by adding a position parameter to this method. You can see the test fails on this line. That's because we didn't initialize our collection of slots. To do this, I need to define a constructor and assign a new list to the property. Every snack machine in our domain model will have exactly three slots, so we can initialize all of them manually here, passing these as a SnackMachine instance to the slot, the position is 1, no snacks, and 0 for both the quantity and the price. The other two slots are the same, except that they are located at different positions. And just to be consistent with the way we initialize the SnackMachine instances, I'll move these two lines to the constructor as well. You can see the test still fails, but this time for a good reason. The quantity of the snacks in the slot didn't decrease as we expected. To fix this, we need to go to the BuySnack method and reduce the snack number by one. The test is passing now. Very good. As you can tell, the Buy method still lacks some functionality. For example, we didn't return change and we don't have any validations in place. We will address this issue soon. For now, let's take a minute and discuss the design we've come up with.
At this point, we approached an important topic, the notion of aggregate. In our domain model, there are two of them, snack machine and snack. Let's elaborate on that. Aggregate is a design pattern that helps us simplify the domain model by gathering multiple entities under a single abstraction. This concept includes several implications. First of all, an aggregate is a conceptual whole, meaning that it represents a cohesive notion of the domain model. Every aggregate has a set of invariants, which it maintains during its lifetime. It means that in any given time, an aggregate should reside in a valid state. For example, let's say snacks have an additional attribute, weight, and we have an invariant stating that our snack machine cannot hold more than 10 pounds of snacks. This kind of validation should be performed in the aggregate so that it's not possible for the client code to add more snacks if the overall weight exceeds the limit. Every aggregate should have a root. That is, the entity which is the domain for the aggregate, so to speak. An important rule regarding this notion is that classes outside of the aggregate can only reference the root of that aggregate. They cannot hold a permanent reference to other entities of the aggregate. In our case, the root of the snack machine aggregate is the SnackMachine class. It means that entities outside this aggregate cannot keep a link to the slot entity. They can hold it temporarily, though. For example, in a local variable during some method, but they should get access to it via the root entity only. So, in our case, if a client code is about to get information about some slot from the database, it should get the corresponding snack machine instance first, and only after that retrieve the required slot using the root entity. However, try to avoid exposing the internal entities at all if possible. You will see this practice in a minute. We will hide the slot entity from the outside world completely. This point strongly correlates with the previous one. Restricting access to the entities that are internal to an aggregate helps protect the aggregate's invariants. Ideally there should be no way for the client code to break the aggregate's invariants and thus corrupt its internal state. Aggregates also act as a single operational unit for the code in your application layer. Application Services should retrieve them from the database, perform actions, and store them back as a single object. In other words, they should consider an aggregate a conceptual whole and refrain from working with separate entities in it. Another function aggregates hold is maintaining consistency boundaries. It means that in any given time the data in the database that belong to a single aggregate should be consistent. To achieve this, we need to persist an aggregate in a transactional manner. In our case, there shouldn't be a situation where we save to the database only the snack machine itself without saving its slots with it. The database should contain all information about the aggregate and this information must be consistent, meaning that it shouldn't break the invariants of this aggregate. At the same time, the invariants that span across several aggregates shouldn't be expected to be up-to-date all the time. They can be eventually consistent. Note that while an entity can belong to only a single aggregate, a value object can reside in several aggregates. For example, there might be another aggregate and an entity in it that holds a money property. That's perfectly fine. Again, it makes sense if you replace the money value object with an integer, and an entity in your domain model can hold one. The same rules are applicable to value objects.
How to Find Boundaries for Aggregates
One of the biggest questions that arrives when it comes to working with aggregates is how to choose boundaries for them. Why are there exactly two aggregates in our domain model and not, say, one or three? We could, for example, make all three entities belong to a single aggregate, so why is it that we have such boundaries in our domain model? Just like with entities and value objects, there are no objective traits that make particular boundaries for an aggregate. They fully depend on the domain model you are working in. The way we group entities into aggregates in different domain models can vary a lot, even if those models have the same set of entities. However, there are some guidelines you can follow in order to identify the boundaries. Domain attributes that makes an aggregate is cohesion of the entities in it. Entities inside the same aggregate should be highly cohesive, whereas entities in different aggregates should maintain loose coupling among each other. It's a good practice to ask yourself the following question, does an entity make sense without some other entity? If it does, then it should probably be the root of its own aggregate. Otherwise, it should be a part of some other existing aggregate. In our case, the slot entity cannot exist without a snack machine, so it has to be a part of its aggregate. These two entities, SnackMachine and Slot, comprise a cohesive group of classes, an aggregate. At the same time, the Snack class can probably live by its own, so it should be the root of an aggregate, which consists of a single entity. As you go along with the development process, you will receive more information about the domain. If you think the boundaries you selected initially don't play well with the problem you are solving, don't hesitate to change them. Domain modeling is an iterative process, so don't expect to find proper boundaries for all your aggregates right away. If you want to learn more about cohesion and coupling, as well as the differences between these two notions, you can read my blog post here. Be aware of creating aggregates that are too large. It might be tempting to include into aggregates more entities. For example, one could decide to include the Snack class into the SnackMachine aggregate. While it might seem a good idea, because it leaves us with less number of aggregates and thus with a simple domain model, in most cases, it doesn't work out well. The reason here is that the bigger your aggregates are, the harder it is to maintain their consistency and handle conflicts when several transactions try to update parts of a single aggregate at once. Finding proper boundaries is a tricky question, and basically is a tradeoff between the simplicity of the model and its performance characteristics. Just to give you some heuristic in the projects I was a member of, most of the aggregates contained one or two entities, and I don't remember an aggregate with more than three entities in it. It's totally fine to have a lot of single entity aggregates, so don't try to gather entities into aggregates artificially. Just follow the guideline and make sure entities in your aggregates comprise a conceptual whole, meaning that they don't make a lot of sense without each other. Note that this heuristic doesn't include value objects. You can have as many value objects in your aggregates as you want. I'd like to make an important note here about 1-to-many relationships between entities. In our domain model, the relation between the SnackMachine and the Slot classes is 1-to-many, in a sense that a single machine can theoretically hold as many slots as possible. Despite its name, in most cases, the 1-to-many relation should be viewed as 1-to-some, meaning that there shouldn't be a lot of entities on the many side. If you find a class in your domain model holding a collection of entities and that collection contains more than, say, 30 members, it's a strong sign you should revisit the model and probably remove the collection and extract the entity on the many side to its own aggregate. For example, we could hold a lot of all purchases made in a snack machine as a collection in the machine itself. This design might seem compelling at first, but the problem here is that the bigger the log gets, the harder it is to maintain proper performance when working with the aggregate. In this case, it would be a good idea to extract this class in a separate aggregate and work with it apart from the snack machine. To make this separation work, we could employ domain events. We will talk about domain events in the future modules.
Aggregate Root Base Class
As we've discussed earlier, every aggregate should have a root entity, the entity which is the main for this aggregate, and which can be referenced by entities and value objects in other aggregates. It's a good idea to create an aggregate root base class, just as we did with entities and value objects. The aggregate root base class usually has three goals. The first one is to explicitly show the boundaries of the aggregates in your domain model. By inheriting an entity from the AggregateRoot base class, you make it easier to read your code base and see which entities are roots of their own aggregates and which are just part of existing ones. Secondly, if you employ optimistic locking, you need to somehow version the entire aggregate. The best way to implement such versioning is to put a version property to the AggregateRoot, like this. And finally, the AggregateRoot base class is a perfect place to hold domain events that happen to an aggregate during its lifetime. We will dive into this topic in much more detail in the future modules. For now, I leave the base class empty. We will extend it later on. Note that if you don't need to implement optimistic locking, and you don't use domain events in your code base, it might be just fine to omit the use of this base class, as it doesn't provide much value in such situations. If this is the case for your project, try to weigh the pros and cons and decide whether you want to introduce the additional base class to a domain model or you can get away without it. In our project, we will need it for holding domain events, but it might be another case in your situation.
Refactoring the Snack Machine Aggregate
Let's look at our code base in light of what we know about aggregates. First of all, we know that the SnackMachine and Snack entities are the roots of their own aggregates, so let's mark them appropriately. I changed the class they inherit from to AggregateRoot. Good. There are two other flows in our code, namely in this particular property. Do you see them? Try to pause the video and think a minute. Unpause it back when you are ready to compare our answers. All right, so the first drawback here is that we expose the collection to the outside world. The client code can easily add new elements to the collection and thus corrupt the internal state of the aggregate, namely the addition of a new slot here would violate the invariant stating that the SnackMachine can have only three slots in it. The second shortcoming is more subtle. The problem here is that we expose the Slot entity itself. As I mentioned earlier, it is a good idea to keep the entities that are not aggregates roots inside the boundaries of their aggregates, and not show them to other aggregates. It's not always possible to avoid such exposure of course, but it is in our case, as you will see in a minute. A solution here is to just make the collection protected and hide it from the client code completely. You can see our test doesn't compile anymore, because it cannot access the slot collection. To overcome the problem, we could create a method that returns a number of snacks in a particular slot. For example, GetQuantityOfSnacksInSlot and compare it to the expected value. This design decision entails other problems, however. If we need to get not only the quantity of the snacks inside a slot, but also the snack itself and its price, we would need to create two other methods, GetSnackInSlot and GetPriceInSlot, and use them to fetch different pieces of information about the slots. The issue here is that if we need to get a list of all snacks in all slots, those methods would be pretty cumbersome to use. So, how can we overcome this problem? As we discussed before, if our code is hard to use or looks awkward, that is a strong sign we missed some important abstraction. Let's look at the Slot class again. Do you see an abstraction here that we can extract out of this class? It turns out there is one. These three properties, Snack, Quantity, and Price, are always used together, so we can extract them into a separate value object. It makes a lot of sense if you consider the usage scenarios for them in our domain model. In most cases, we want to work with these three elements together. For example, when we display a snack on the interface, we need to know not only what the snack that is, but also its price and the remaining quantity. So let's create a separate class for this new abstraction. We can call it SnackPile. It will represent a pile of snacks a particular slot contains. This would be a value object, so we can make it sealed, because the requirements NHibernate imposes regarding virtual members apply to entities only. I am creating a placeholder for the two equality members. We'll write the actual implementation for them in a second. All right, so now we can move these three properties here, make them known virtual, and also remove the setters, because our value object will be immutable. Create a parameter-less constructor, and also a constructor that accepts all three members. Now we can implement the EqualsCore and GetHashCodeCore methods. Perfect. We need to add the newly created value object to the Slot entity, and also update its constructor. Here we can just initialize the SnackPile with empty values and remove the snack quantity and price parameters altogether. The SnackMachine class has stopped compiling at this point. Let's see how we can fix it. First of all, the LoadSnacks method doesn't have to accept three separate parameters anymore. We can replace them with a single SnackPile instance and assign that instance to the appropriate slot inside the entity. I need to make the setter for the property public. Good. The constructor of the SnackMachine class can also be simplified. We no longer need to pass any parameters other than SnackMachine itself and the position of the slot. And finally, we have an error here when trying to decrease the number of remaining snacks by one. The first decision that might come to mind is to write such code and just make the quantity field mutable. However, it is not the best solution. Remember, it's a good idea to adhere to the immutability principle, and don't mutate value objects. A better option in this situation is to create a separate method in the ValueObject class, SubtractOne. It would create a new ValueObject with the same parameters as in the existing one, except the quantity value. So let's implement it. We just need to return a new SnackPile with the same snack. The quantity would be decreased by one, and the price is also the same as in the existing ValueObject instance. Perfect. The last thing we need to update is the unit test. Creating a SnackPile to load into the machine, and instead of these three methods, we can create a method that returns a SnackPile from a particular slot. Searching for a slot, and returning its property. Good. Now we can retrieve a SnackPile and check that the quantity in it equals the expected value, and I remove this code. Very good. The code compiles and the test is passing now. Let's implement a couple of additional refactorings for our domain classes. First of all, we need to make this public method virtual, so that NHibernate wouldn't complain about it. Secondly, we use this line of code for several times in different methods, so it's a good idea to extract it into a separate method. I call this method GetSlot and copy the line to it. Now we can use it here, and also here. Perfect. The last thing you might have noticed is that we don't actually make any validations in the SnackPile ValueObject. It's possible for us to create a pile with a negative quantity or a negative price, which are invalid values for our domain. Let's fix that. If the quantity or price is less than 0, then we throw an exception. Also note that as we use the decimal type for the prices in our domain model, we can potentially set the price to a value that is less than one cent, but is still positive. We need to handle such situations as well, and don't allow prices that are more precise than one cent. We just cannot handle such prices with physical money our snack machines operate. So, if the remainder of the price after dividing it by 100th is more than 0, then we also throw an exception. Note that it's a good idea to cover these three invalid cases with unit tests. I'll leave it as an exercise.
Recap: Refactoring the Snack Machine Aggregate
Let's recap what we've done in the previous demo. First of all, we created a fully encapsulated aggregate, SnackMachine, that doesn't expose its internals to other aggregates. Not only do we hide the collection of the slots, but we also keep the internal entity, the Slot entity, inside the aggregate and don't expose it to the outside world. It's a good practice to try to achieve such a degree of isolation. In our case, we were able to implement it by introducing a new value object, SnackPile. Unlike entities, value objects are perfectly fine to pass between aggregates. Note how we resolved an awkwardness in the code by adding a new abstraction. Always search for hidden abstractions in your code base. It might be that your domain model can be simplified greatly if you introduce one. And again, unlike entities, value objects are lightweight, and it's always a good idea to move as much domain logic to them as possible. In our case, we transferred almost all responsibilities the slot entity had to the SnackPile value object. The Slot class now acts just as a host for that value object, nothing more. Also note that we adhere to the immutability rule for value objects. Instead of making the quantity property in the SnackPile ValueObject mutable, we introduced a separate method that creates a new instance of that value object.
Implementing Missing Requirements
Despite our progress with the domain model, we still have some missing requirements left. First, we need to check if the price of the snack the user is buying is equal to or less than the money they inserted. We shouldn't allow the purchase if it's not the case. Similarly, we must check if there are any snacks left in the slot. Next, we need to return the change back if the user inserts more money than needed, and finally, we shouldn't allow the purchase if there is not enough change in the machine to give the user back. Let's start with the first two requirements, as they are pretty simple to implement. I am adding a test that verifies we cannot make a purchase if there are no snacks in the slot. You can see it just creates an empty snack machine and tries to buy a snack from the first slot. It should fail, because at this point, there are no snacks inside the machine yet. Note that the test is passing. That's because in this method, we try to create a snack pile by decrementing the quantity of the already existing pile, which essentially gives us a negative number. This number, then, doesn't pass this check. The constructor throws an exception, so we already have the behavior we need. Let's comment these lines out just to make sure the test works. The unit test does indicate a failure. Very good. The second test we need to add is the one that verifies that we cannot make a purchase if there is not enough money inserted. So, basically what the test does is it loads the snack with the price two dollars, inserts a dollar, and tries to buy that snack. The test fails, because right now nothing prevents us from buying a snack, even if we don't have enough money in transaction. Let's fix that. So, if the price of the snack in the slot is more than the amount of money in transaction, then we throw an exception. The test passes. Perfect.
Revealing a Hidden Requirement
The next requirement is not as simple as it might seem. We need to make sure the machine returns change, so basically what we need to do is we need to add to the money inside, only the money that is worth the snack price, not more. Right now we just move all inserted money to the money inside, regardless of the snack price. This seemingly simple task raises an important question. What exact set of coins should we transfer? For example, what if the user inserts four quarters and one dollar bill and tries to buy a snack with the price one dollar? Should we transfer quarters, or should it be one dollar bill instead, or maybe it doesn't matter? As always, when we encounter an edge case we didn't think of, we need to go to the domain expert and ask her about the expected behavior. It turns out that this question is more complex than we initially thought, and we have to another business rule to our requirements. That is, the snack machine must try to retain coins and notes of as little denomination as possible. So, in the example above, the machine needs to appropriate four quarters and return the user one dollar bill. That makes sense, because otherwise the machine will run out of change very quickly and won't be able to serve clients who don't have the exact amount of money in hand to make a purchase. It also means that when the user inserts, for example, four quarters, and asks to return the money back, the machine must try to return one dollar bill instead and keep the quarters. All right, let's start with a new test for the ReturnMoney method. The SnackMachine should return the money of the highest denomination first. We need a separate method for this test, LoadMoney. We will use it to load some initial money into the machine, just as we do with LoadSnacks. So here we load a dollar, try to insert four quarters, and return money. The Return method should be implemented in such a way that allows the machine to keep the quarters and give the user one dollar instead. So the number of quarters inside the machine should be 4, and the number of 1 dollar bills should be 0. Implementing the LoadMoney method, it just adds the money to the money inside. Let's look at the current implementation of the SnackMachine class. Note that we have two separate value objects to represent the money inside the machine, and the money inserted by a user. We know now that we don't have to distinguish them, because it may be that the machine returns to the user not the exact same money they inserted, but the equivalent amount from the money inside. Do we need two separate value objects in this case? We don't. We can just keep all money, be it money inside or money in transaction in a single property, and track the amount the user inserted using a simple numeric variable. So, I can change the type of this property from money to decimal, that will be that variable. The MoneyInside property will hold all money in the machine regardless of how they got there. I need to fix the code here. Instead of placing the inserted coin or note to the MoneyInTransaction property, we need to just increase the amount and put the money directly to the MoneyInside property. Fixing other occurrences. In the BuySnack method, we don't need to transfer the money from one property to the other. They are already there. So all we need to do here is nullify the MoneyInTransaction. This method still works incorrectly, of course, but we'll get back to it later. Changing the tests so that we use the decimal type instead of money. In the ViewModel, there is also no need to sum the two instances. We can just use the first one. Perfect. The code compiles now. The test still fails, however, so we need to change the ReturnMoney method. To make it work, we need to somehow allocate a required sum from the money inside and subtract it from the overall amount. So, basically what we need here is something like this. Allocate the MoneyInTransaction amount, which would be the money to return, and subtract it from the money inside. This Allocate method will handle all the logic while deducting the coins and notes of the highest denomination. I'll just copy the implementation here, but keep in mind that this is definitely something that needs to be covered by unit tests, which I'm leaving as an exercise. You can see we start from the most valuable notes and try to fill the required sum with them first, and then fall down to less valuable bills and coins. After that, we'll return the resulting money object. Good. The test is passing. The second piece of functionality we need to implement is returning change after a purchase is completed. So, here I load a snack with a half dollar price, and also load 1 dollar using 10 cent coins. After I insert a dollar and buy a snack, the amount of money inside should be 1 and a half dollars, which means that the machine should take the dollar and give 50 cents back. The test is failing. You can see the snack machine takes all the inserted money currently. One little thing to make our code more readable, we can introduce an additional multiplication method in the Money class and implement it like this. Basically multiply all members by the multiplier. And now instead of using the money constructor, we can write 10 cent multiplied by 10. Much better. To make the unit test pass, we need to allocate the difference between the inserted money and the price of the snack and subtract it from the money inside. The last use case we have to take into account is when the machine doesn't have enough change. You can see the snack costs half a dollar, but we insert a whole dollar here. That should result in an exception. To make it work, we need to check if the change we allocate in is indeed sufficient. If it's not, the method has to fail. Very good. We implemented all required functionality.
Recap: Revealing a Hidden Requirement
In the previous demo, you saw how a new requirement we didn't think of emerged. There almost always will be requirements which you miss initially and which you discover as you progress with your software. It is typical in any more or less complex project. Don't hesitate to refactor a domain model in such situations to make it better convey new knowledge you unfold. It is important to closely collaborate with the domain experts on the new information and try and refine the domain model to the other. In our case, the new requirement let to the situation where we don't have to distinguish the money inside the machine from the money in transaction. It is easy to treat them as a single value object and just drag the amount of money inserted by the user with a decimal property.
In this module, we talked about aggregates. That is, a design pattern that stands for gathering multiple entities under a single abstraction. There are several attributes that belong to aggregates. First, an aggregate is a conceptual whole, meaning that they represent a cohesive notion of the domain model. Every aggregate has a set of invariants which it maintains during its lifetime. Second, every aggregate should have a root entity, the root entry, which can be used by other aggregates. All entities and value objects outside a given aggregate should work with it only via its root entity. Third, aggregates act as a single operational unit for the application layer. It means that the application code should work with all aggregates, not with separate entities in it. And finally, aggregates hold consistency boundaries. They should be stored to the database within a single transaction. We talked about how to find proper boundaries for aggregates in your domain model. The best way to do this is to answer question whether or not an entity makes sense as a separate concept. If so, it should be the root of its own aggregate. It's a good idea not to expose internal entities outside of the aggregate boundaries, because it helps maintain proper encapsulation. It's not always possible, but in many cases, it is. And finally, you saw an example of refactoring the domain model after revealing a hidden requirement. Remember there always will be things that you cannot foresee up front. It's important to iteratively involve your domain model after the knowledge you get about the problem you are working on. All right, that's it for this module. In the next one, we will talk about repositories and their connection with aggregates.
Hello, my name is Vladimir Khorikov and this is the Domain-Driven Design in Practice course. In this module, we will talk about repositories. We will see how they are applicable to our project and what the best practices for working with them are.
Adjusting the Database for the New Entities
In the previous module, we defined two aggregates and added two new entities, snacks and slots. Now let's adjust our database structure by adding new tables for them. This is our domain model. And this is the database for it. You can see that just as we have the SnackMachine table, the SnackPile value object is inline into the Slot table, so that we don't have a separate table for it. I added new rows to the Ids table so that the Hi/Lo algorithm can work with these entities as well. And I also filled out the snack table with three different snacks, and added three slots with these snacks for the existing snack machine.
That brings us to another important DDD notion, repository. Repository is a pattern for encapsulating all communications with the database. The idea is that the client code should get the required domain object as if they reside in the memory, just by calling a single method, and without any additional effort. So in our case, a code that retrieves a snack machine could look like this. An important question here is how many repositories should one create for a domain model? The general rule is that there should be a repository per each aggregate. So, in our case, there should be two of them, a SnackMachineRepository and the SnackRepository. The SnackMachineRepository handles all the work for retrieving the internal slot instances, so when we call the code above, not only should it get the snack machine itself, but also all its slots. This is usually achieved either by eagerly the loading the sub-entities with the aggregate root, or by employing lazy loading. Similarly, when we save an updated snack machine, its sub-entities should be saved with it without additional effort from outside. Another rule of thumb for working with repositories is that the public methods should work with aggregate roots only. The SnackMachineRepository should accept and return the SnackMachine instances. All the work with internal entities has to be done behind the scenes, either manually in the repository, or using ORM mapping capabilities. At this point, you might ask, what if you need to get a sub-entity instance? What if you don't know to each parent the sub-entity instance belongs? A common practice in this case is retrieving an aggregate root instance first, and working with it to get the required sub-entity. For example, in our case, if the client code would like to retrieve a slot by its identifier, we could create a method like this one. You can see this method resides in the SnackMachineRepository, and although it accepts a slotId, it returns an instance of SnackMachine, not Slot. Aside from utilizing an ORM, repositories can work with the database directly, either by using SQL or by calling stored procedures. So, it's a good idea to commission all communications with the database to repositories, even if such communications bypass the ORM. An example here would be reading a list of Snackmachines and sending it to the client. If such query is performance sensitive, you can write a separate SQL script for it and use it from the repository directly without involving the ORM and domain objects. We will talk about this approach in more detail in the future modules.
Repository Base Class
Here's how the repository base class looks like. You can see there are two methods, GetById and Save, which would be used by all repositories in our domain model, so it makes sense to gather them here in the base class. You can add here other methods, of course, for example, a Delete method, but we won't be using the deletion functionality in our application, so there is no need in such a method in our code base. And note that the class is generic, and we have a restriction saying that the type parameter must be an AggregateRoot. That way we enforce the rule we discussed previously, one repository per each aggregate. Also note that every method in the repository creates its own session instance. That's because this is a desktop application, and will work in a detached mode, so to speak. What I mean by that is our ViewModels keep references to the domain entities. Therefore, we have to detach them from the session they were created in. Because of that, we don't need to keep the session alive after we get an entity from the database. In the web application, you would probably want to keep the session during the whole lifetime of web request. In this case, you would need to inject it in the repository constructor and not just instantiate in each method apart. A rule of thumb for working with repositories is that every aggregate root should have their own repository. This is how the SnackMachineRepository could look like. It's a good place for gathering logic specific to a concrete aggregate. For example, in this class, we have a method that searches for all machines containing a particular snack, and another one that looks for machines with a specific amount of money inside. We won't need these two methods in our application. I just wanted to give you an idea of what code a typical repository can contain. Keep an eye on how you name your repositories, and don't give them names that expose their internal implementation details. For example, the name SnackMachineSqlRepository is not the best one, because the word SQL in it points to the specific detail of how domain entities are being stored. This name doesn't comply with the principles of ubiquitous language, which state that we should name the domain classes after their meaning. Think about it this way. Your application code doesn't care how exactly your repositories get domain entities from the persistent storage. They don't even care what the storage that is. In fact, a repository can gather required data from multiple sources if needed. All these details are irrelevant to the clients of the repository, so don't name it after the underlying storage it uses. SnackMachineRepository would be just fine in our case.
Setting up Mappings for the Aggregates
Now, as we have repositories in place, let's discuss the mapping for the new domain entities. This is a mapping for the Snack class. This class is very simple, so is the mapping for it. All it contains is the ID and the Name properties. The slot mapping is a little bit more complex. In addition to the ID and position properties, here we have the value object mapped using the Component method and a reference to the SnackMachine instance. Note that the value object contains a reference to the Snack entity and we disable lazy loading for that entity here. That's because our domain objects work in the detach mode, and we have to fetch all required elements at once. We cannot use lazy loading when an object is detached from its session. In the web application, you would probably want to keep lazy loading behavior, because you usually don't have domain objects detached. Aside from the mapping for the two new entities, we need to define the 1-to-many relationship between the SnackMachine and Slot classes. It can be done with this line. You can see we use a special Reveal class here. That's because the Slot collection is marked as protected, the Map class cannot access it directly. And note that the collection is marked as not lazy as well. All right, here is a temporary test I'll use to test the repository, along with the newly created mappings. And this is the data in the database for the SnackMachine. You can see all slots are successfully loaded into memory. And now let's try to change something. Let's try to buy a snack and save the SnackMachine after that. Running the test, and the test has completed. Let's look at the database. Our SnackMachine should get plus three dollars at this point, and the number of one of the snacks should decrease by one. And you can see that indeed the machine now contains four 1 dollar bills instead of 1, but if we look at the slot we bought the snack from, it still has a quantity of 10, whereas it should be 9. It means that while the repository successfully updated the SnackMachine itself, it left untouched at the slot sub-entity. We know that repositories should take care of all updates inside an aggregate. The client code doesn't have to do it itself, so how can we fix that? One way is to manually update each slot in the slot collection in the SnackMachineRepository when we save a machine. A more simple way, however, is to update the SnackMachineMap. NHibernate allows us to set up cascade updates so that whenever a parent entity gets updated, the collection of sub-entities is updated as well in case there are any changes in it. I just need to write this code here. Let's run the test once again. The number of dollars was increased, just as before, and the snack quantity is decreased. Very good. We have our aggregates and repositories up and running.
Refactoring the Snack Entity
Let's look at the snack table again. It turns out that in our domain model, the list of snacks is reference data. Reference data is data that is pre-defined. The change of such data is relatively rare and we can treat it the same way we treat the database structure. Another example of reference data is the Id table. We add new rows to it only when the database structure is changed. The users don't minify those rows directly. Reference data enables some interest in approach we can leverage in our domain model. Our code can rely on the existence of those snacks in the database, and we can define them explicitly. Let's see what I mean by that. Here's the Snack class. You can see that whenever we need a new instance of it, we'll create one via the constructor, like this. What we can do instead is we can add an Id parameter, make the constructor private, and add a static readonly field, Chocolate, by creating a snack with the same data as in the database. And the same for the other two snacks. And note that the constructor is made private, so these read-only fields is the only way for the client code to work with the instances of the Snack class. You can see they fully represent the data in the database. It's a good practice to also cover them with integration tests to make sure the ID and the name you set up in the domain model are the same as in the database. Now as we've defined the existing snacks in the declarative manner, we can replace this explicit snack instantiation with a read-only field. Even more, we can also define it as a static import just as we did with the Money class. That would allow us to shorten the invocation. There are several more places where I need to update it. Perfect. And let's also look at the Slot class. You can see the constructor instantiates the SnackPile, passing it null as the snack instance. A better way to deal with such situations is to employ the null value design pattern. This pattern stands for the use of some special value instead of null. We can define it here along with the other snacks we've declared. The benefit of this pattern is that it decreases the chance of incorrect behavior that can appear because of nulls. And it's a good idea to cover this field with an integration test as well, but at this time, the test should check that this field does not exist in the database. So, after we created a null object, we can use it here instead of null. But we can do even better. Just as we've defined a null object for the snack entity, we can define one for the SnackPile value object and use it for initializing newly created slots. This is a quite powerful technique, and it can significantly simplify your code base. An important note here is that you shouldn't forget to change those values every time you change the reference data. Because of that, it is vital to cover the static fields with integration tests to verify they match the data in the database.
Adjusting the User Interface
It is time now to adjust the user interface for the work within your version of the domain model, as well as the repositories we introduced earlier. I've updated the markup for the UI. Here is how it looks like now. This empty space here is devoted for the snacks. To display them, the view requires an additional property with the name Piles, which returns a list of SnackPile ViewModels. Note the use of another ViewModel inside of the SnackMachineViewModel. It's a common practice in the world of MVVM applications, and is usually referred to as hierarchical ViewModels. This is the SnackPileViewModel itself. You can see just as the SnackMachineViewModel wraps the SnackMachine entity, it works on top of the SnackPile value object. It introduces several properties, which basically transform those of the value object and present them in a form that is consumable for the view. All right, to actually form a collection of view models, we need to get a list of SnackPiles first. So let's introduce another method in the SnackMachine entity. I'll name it GetAllSnackPiles. Note that we return a ReadOnly list here. It's a good practice to always do that to show the clients of the method that the collection is not mutable. In our case, the collection of the SnackPiles is indeed mutable. It should always contain three and only three elements. Also note that just as with the GetSnackPile method, here we expose a collection of value objects, not the collection of internal entities. That way we maintain the encapsulation of the aggregate. To get all SnackPiles, we need to sort the slots by position, select the SnackPile in each of them, and form a new list. And now we can use this method in the Piles property. Good. You can see the view now displays the snacks in the machine, 10 chocolates, 15 cans of soda, and 20 gums. Let's now refactor our ViewModel for the use of the repository we created. Instantiating it in the constructor, and calling the Save method instead of working with the session manually. And the same in the main ViewModel, where we we'll retrieve the SnackMachine from the database. Perfect. And note that we now have three buy buttons instead of one. Those buttons pass the number of a slot to the ViewModel, so let's adjust the command to accept that number. The parameter's type is string, so I change the type of the BuySnack command to the command of String. Parse the position here, and pass it to the Buy method. And we also need to update the NotifyClient method so that it notifies the client about the change in the Piles property as well. You can see when I insert a dollar inside and buy a gum, all works fine. The money goes to the machine and the number of gums decreases. But if I insert an insufficient amount of money, for example, $1, and try to buy a snack whose price is $3, I get an exception. That's because the client doesn't check if all required prerequisites are met before calling the Buy method. One way to fix this is to do all those checks here in the ViewModel. In other words, do all the validations. Check if the pile is not empty, if the user inserted a sufficient amount of money, and so on. It is not the best design decision, because those checks operate the data that resides in the SnackMachine, and thus should belong to the SnackMachine class itself. Another option is to change the returning type of the BuySnack method to some value and use it to signalize an error. For example, we could return some string describing an error instead of throw an exception, and that string could then be examined by the client. It is a better option, but still is not the best one, because it violates the command-query separation principle. That is the principle which states that if possible, we should separate methods that mutate the internal object state from the methods that just query some data from it. In other words, we just should use a void as the returning type for commands and make all other methods side effect free. In general, this principle helps us increase readability of the code. The BuySnack method here is a command, because it mutates the internal state of the snack machine, and thus it should not return anything. So, what should we do in this situation? The third option here is to introduce a separate method which gathers all checks for the BuySnack method, and which doesn't mutate the object state. We can call it CanBuySnack. It's a common and yet quite powerful pattern. Basically for each separation that has some preconditions, which should be met, you can add a separate method that validates those preconditions. The client code then can use that method to do all required validations. So here we are getting a pile that resides in the position, and basically define all checks that should be made. If the quantity is 0, it means the pile is empty. If the inserted money is less than the price, it means the user should insert more money. And finally, if we cannot allocate the required amount of money to return back the change, that should be in there too. You can see we use a CanAllocate method of the money value object, which doesn't yet exist. Let's add it. So basically in the Allocate method, we adhere to the command-query separation principle as well. We will have one method checking that the allocation can be done, and the other one performing the actual allocation. So, to validate that the allocation is legal, we need to execute it first and then compare the resulting amount of money with the requested one. If we don't have enough change, the amount should simply differ. And I'm making this change here to add the validation before proceeding. All right, these are all checks to be done before buying a snack, returning an empty string in case all validations are passed. Now we can call this method in the BuySnack method, like this, and remove all these checks. Very good. As you can see, extracting all the checks in a separate method makes the code much cleaner. Finally, we need to use the BuySnack method in the ViewModel. So, basically if there is any error, we'll just show that error to the client and don't proceed further. You can see if I insert a single dollar and try to buy a chocolate, which costs $3, it shows that there is not enough money to make the purchase. And if I insert $20 and try to buy a $1 snack, it displays that there is not enough change. Very good. We now have a fully working model of the snack machine.
In this module, we talked about repositories. Repository is a pattern the purpose of which is to encapsulate all communication with external storage. It's important to keep in mind a general rule of thumb there should be a single repository per each aggregate. Another guideline here is that public methods of repositories work with aggregate roots only. If you need to get a sub-entity, retrieve its aggregate root first, and only after that look among its sub-entities. But keep in mind the guideline we discussed in the previous module. Try not to expose sub-entities outside of aggregates wherever possible. Repositories work with whole aggregates, meaning that they should perform saving and retrieval of all sub-entities without additional effort from the client side. It is best achieved by configuring proper mappings in the ORM. You also saw an example of working with reference data. It's a good practice to declaratively define such data in the domain model. It helps simplify the code and increase its readability. In the next module, we will talk about bounded contexts. We will introduce a second bounded context and we'll discuss different ways to express them in code.
Introducing the Second Bounded Context
Hello, my name is Vladimir Khorikov and this is the Domain-Driven Design in Practice course. In this module, we'll introduce the second bounded context in our application. Along the way, we'll discuss the differences between bounded contexts and sub-domains, and how they relate to each other. We will also talk about how to choose boundaries for them and how to perform context mapping. We will look at different types of physical isolation for bounded contexts, how they communicate with each other, and finally, we'll discuss the guidelines for using code between them.
New Task: an ATM Model
In the previous modules, we ended up with a snack machine model. Now we have given a new task. Create another model, and this time for an automated teller machine, ATM. This model will allow the users to withdraw cash using their bank cards. Just as with the snack machine, here we'll leave aside such details as how exactly card operations are performed or how the ATM dispenses cash. Our focus area will be the business rules behind this model. So, what are the requirements for this new task? First of all, the ATM must give the users cash they requested. In return, their bank cards should be charged the worth of money dispensed, plus a 1% fee. Also, the model must keep track of all money that was charged from clients. To solve this task, we'll introduce a new bounded context. We'll discuss the reasons for that shortly. For now, let's take a closer look at the notion of bounded context.
Bounded context is a central pattern in domain-driven design. It stands for separating the model and explicitly drawing the boundaries between its pieces. The reason for such separation is that as your application grows, it becomes harder to maintain a single unified model as it becomes larger and more people get involved into the development process. Big models bring significant communication and integration overhead with them. Bounded contexts help reduce that overhead. There are several attributes that belong to the notion of bounded contexts. First of all, they act as a boundary for the ubiquitous language. It means that the language we use for communicating with domain experts and naming classes in our domain model should be consistent and unified only within a bounded context. At the same time, the naming doesn't have to be consistent across different models. Two bounded contexts can hold entities or value objects with the same name, and they can be completely unrelated to each other. That's perfectly fine. You can think of bounded contexts as if they were C# namespaces for the classes in your code base. Two namespaces can hold any set of classes, be they intersecting or not. For example, the SnackMachine bounded context could have a class name CompositeElement, which represents a replaceable item in a machine. The ATM bounded context at the same time could have its own version of that class with its own set of attributes and business rules. These classes, despite the same name, should be viewed differently, as they reflect different concepts. Another important attribute of bounded contexts is that they span across all layers in the onion architecture. It means a bounded context is not something you can point out on this diagram. Each of them is represented with its own onion, so to speak. So, if you decide to introduce a new bounded context, it should have its own set of entities, repositories, factories, Application Services, and all other layers from the onion architecture. And finally, it's important to explicitly state the relations between bounded contexts. That's where context maps come into play. A context map is a map that renders the bounded contexts in your system and the connections between them. If you want to see an example of a context map, I recommend you to watch this talk given by Eric Evans. We will draw our own context map later in this module.
Bounded Contexts and Sub-domains
There is another concept in domain-driven design, sub-domain. It's important to understand the relation between sub-domains and bounded contexts, as they are often mistaken by programmers. There are two central elements when it comes to building a software project, a problem and a solution. The problem is the reason why we are creating the project, the thing we aim to solve with it. The solution is the actual artifact of our efforts of trying to solve the problem. So, the differences between the two concepts is that sub-domain belongs to the problem space, whereas bounded context to the solution space. In other words, a sub-domain is a part of the whole problem, a part of the problem domain, and bounded contexts at the same time is a part of the solution for that problem. Sub-domains and bounded contexts are best related to each other as 1-to-1, meaning that ideally every sub-domain should be covered by exactly one bounded context. It's not always possible, though. Let's say, for example, that you've come to legacy ERP project, which you need to enhance to address new requirements for the sales sub-domain. If the existing code base isn't covered by automated tests, it might be scary to change it, so you might decide to add a new bounded context and separate it from the existing code base by an anti-corruption layer. In this case, you end up with two bounded contexts that cover a single sale sub-domain. It's a good practice to avoid such situations, though, because the code base is overall easy to maintain and understand where there is a strict 1-to-1 relation between sub-domains and bounded contexts. So, in the example above, there should be ideally a single sales bounded context. In our case, we have two sub-domains, snack machine and ATM. As we are working on a greenfield project, meaning that we don't have any legacy code here, it's easy for us to adhere to the guideline and create a single bounded context for each sub-domain.
Choosing Boundaries for Bounded Contexts
It's important to keep explicit boundaries between bounded contexts, but how to define them in the first place? How to draw a line between one bounded context and another? As I mentioned earlier, the best way to do that is to adhere to the 1-to-1 guideline. In other words, create a separate bounded context for each sub-domain. So, the question boils down to how to define a sub-domain? A sub-domain is usually not something that is defined by us developers. As the notion of sub-domain refers to the problem space, it is often defined by customers and domain experts. The boundaries for sub-domains usually come up naturally during the talks with them. For example, they might mention that they want to build a sales prediction subsystem to help the company stabilize its revenue. Or they want a support subsystem to decrease the expenses related to the customer support activities. All these are signs of separate sub-domains, so in most situations, you just need to carefully listen to the domain experts. However, this approach might not work in some cases, as you need to take into account other factors as well. The first one is the size of the team. If your sub-domain is too big and that causes the team working on it to grow more than, say, 6-8 developers, it's a strong sign you need to separate the bounded context in 2 and form an independent team for each of them. The second factor is the size of the code. Even if your team was small enough, it might be that the code in a bounded context grows enormously so that it becomes hard to manage its complexity. In this case, you might also want to depart from the 1-to-1 guideline and create 2 bounded contexts for a single sub-domain. The general rule of thumb here is that the code of single bounded context should fit your head, meaning that you shouldn't have a lot of trouble understanding it. In my experience, though, it rarely happens, and the 1-to-1 guideline works just fine in most situations. All right, we discussed situations where there might be several bounded contexts for a single sub-domain, but what about the opposite? Can a single bounded context cover several sub-domains? For example, in our application of the snack machine and ATM sub-domains are pretty small, doesn't it make sense to solve those problems with a single bounded context? It's true that in our sample application, there is little code in bounded contexts. For example, the snack machine model consists of only two aggregates. Nevertheless, even if you are able to cover two sub-domains with a single bounded context and still keep it succinct, I recommend you to adhere to the guideline and still create two separate bounded contexts for them. Just make sure the sub-domains they cover are indeed distinct ones and not part of a single sub-domain. The reason here is that bounded context segregation is a logical one. Whether or not to keep them separated physically is a different question. So, even if you do create two bounded contexts, you don't have to create separate sets of Visual Studio projects for them right away. You can carry the code together while it's small and thus keep the maintenance overhead low. We will talk about the degrees of physical isolation for bounded contexts later in this module. The last guideline regarding boundaries for bounded contexts is how they relate to the development teams working on them. It's a good practice to keep the team's boundaries aligned with those of bounded contexts. It means that while one team may work on several bounded contexts, there shouldn't be a situation where two teams work on a single bounded context. It would lead to communication issues and increased maintenance costs.
Drawing a Context Map
Let's draw a map for the context we have in our application. But before that, let's see how the structure of the ATM bounded context looks like. It will consist of a single aggregate with an ATM entity inside. As the ATM needs to dispense cash, we need to represent that cash somehow, and we already have a suitable class for that, MoneyValueObject, which we can use the ATM entity. Another field here is the MoneyCharged field, which will indicate the amount of money charged from the client's bank cards. So, now we have a situation where the same value object, MoneyValueObject, is used in both bounded contexts. That means there is a shared kernel between them. So, our map goes like this. You can see the two bounded contexts use a shared kernel and don't interact with each other. So, we can say there is a separate ways relation between them. That kind of relationship actually indicates no relation, whatsoever. It's important to remember that such maps should reflect the actual state of affairs, not the desired one. If the bounded contexts in the application interfere poorly, for example, one of them has become a big ball of mud and starts affecting other contexts, show it with such diagram. It will be a good starting point on the way to fix the situation. If you see that one of the bounded contexts decays, meaning that the code in it is a mess, you might want to protect other bounded contexts from its impact by introducing an anti-corruption layer.
Types of Physical Isolation
As I mentioned earlier, logical separation of bounded context is orthogonal to the physical isolation. There are several degrees of such isolation you may employ, and each of them has its own pros and cons. The first one is keeping the bounded context in the same assemblies, but in different namespaces, so basically just creating separate folders for them. Note that although with this type of isolation you keep the code of the bounded context physically close, you still need to maintain proper separation, and don't allow them to infiltrate to each other. Boundaries should be preserved regardless of what type of isolation is chosen. Such degree of separation also means you share the same database instance, but again, entities in different bounded contexts should be stored in different tables. If you use SQL Server, it's a good idea to define separate database schemas for each bounded context to make this distinction more apparent. The second type of isolation is extracting the bounded contexts into separate assemblies under the same solution. Here, for example, you can see two solution folders for bounded contexts, and a separate project for the SharedKernel. Note that each folder contains its own UI and logic assemblies that regard to certain bounded context. The third type of isolation is separate deployment. While the first two types imply the bounded context work in a single physical process, this type of isolation means you deploy and maintain them as separate applications. The source code is stored separately and there are separate database instances for each of the bounded contexts. This type of isolation is often referred to as microservices. The topic of microservices is out of scope for this course. If you want to learn more about it, I recommend you to read this article. It's important to understand the benefits and drawbacks that each of the isolation types entail. The benefit here is that the greater the physical isolation is, the easier it is to maintain proper boundaries between bounded contexts. You are less likely to violate them if the bounded contexts reside in separate assemblies, or even separate solutions. At the same time, the more you isolate your bounded contexts, the more maintenance overhead this isolation introduces. It's harder to deal with separate assemblies in your code base than it is with only one, and it's even more difficult to handle separate deployment of them in case the third type of isolation is chosen. The guideline here is that you should be pragmatic about the degree of isolation you choose, and introduce greater physical separation only when the benefits of it is justified. If your bounded contexts are small enough, it's just fine to start with the first type and move forward only when you feel the code gets bigger and it becomes harder to keep it clean without introducing additional physical boundaries. In our application, we will adhere to the first type, because we don't have large amounts of code in our bounded contexts. This is how it looks like in practice. You can see there is a separate folder for the snack machine context with all classes related to it. The Atms folder is empty, because we don't have any code for the new bounded context yet. And the SharedKernel contains a single Money value object. Note that we also have a folder for Common base classes, and they are the one for utilities. They are shared by all bounded contexts. We will talk about sharing code between bounded contexts later in this course. Also note that we have the same kind of separation in the UI project, two separate folders for each of the bounded contexts.
Communication Between Bounded Contexts
The way entities in different bounded contexts communicate with each other depends on two elements, the type of isolation chosen and the relationship between them. Let's take the first two isolation types, the types that imply you host bounded contexts in a single process. In this case, the communication pattern depends on whether or not there is an anti-corruption layer between the bounded contexts. If there is none, entities in these bounded contexts can just call other entities' methods directly. Also, the communication can be performed via domain events, which we'll discuss in the next module. In case there is an anti-corruption layer, things get more complicated, and anti-corruption layer usually means that developers working on one bounded context don't want to interfere with the concepts from another bounded context. It's often the case if that other bounded context has become a big ball of mud, or in other words, a mess. Another use case for introducing a such a layer is working on the legacy project, where you want to keep the new code isolated from the concepts in the existing code base. Regardless of the actual reason, the presence of an anti-corruption layer means that you cannot just allow entities in your model, call entities from the other bounded context. You need to introduce the proxy between the two models, which would handle all translations for them. So, whenever you need to perform a call, your model calls the proxy instead and gets a response, which is formed in a way that is familiar to it. In case the third type of isolation is chosen, meaning that two bounded contexts are hosted in separate processes, the communication goes through the network. For a direct call, it is usually an HTTP call using REST or SOAP protocol. For an event, some sort of a message queue is used. In this situation, you don't have to create an anti-corruption layer between the two bounded contexts, because this messaging mechanism essentially acts as such.
Code Reuse Between Bounded Contexts
An important topic which often gets misunderstood is code reuse between bounded contexts. It might turn out that there is some code which is common for two bounded contexts, and which can be factored out and reused in both of them. The first temptation in such situation is to perform the extraction in order to comply with the don't repeat yourself principle. On the other hand, we have a guideline which says there should be strict boundaries between bounded contexts and developers should not allow concepts from one context to infiltrate to another. So, how to deal with this conflict? To answer this question, we need to define different types of code, which can be a potential subject for use. The first type is code that carries business logic. Such code should never be reused unless it has the exact same meaning for all bounded contexts involved. For example, you might have two bounded contexts, sales and support, which both contain a product entity. It may even be that these entities have some behavior in common. In this case, you still need to create separate entities for them in both bounded contexts and store them in distinct database tables. The reason here is that despite the fact these entities have some commonalities, they still represent different concepts. The sales perspective on what a product is is not the same as the support perspective on it, even if that is the same physical product. You shouldn't try to merge the two concepts into a single entity. At the same time, two domain classes might have the same meaning for different models. For example, both snack machine and ATM bounded contexts have identical perspective on the MoneyValueObject. It represents the same concept for both of them. If this is the case, you do need to reuse by it by extracting this class into a shared kernel like we did previously. It's a good idea to treat that kernel the same way you treat a bounded context, and strictly maintain its boundaries. The second type of code is domain base classes, such as entity value object repository, and so on. They don't contain any business logic themselves, but are still used by the actual domain classes in your bounded contexts. The answer to the question whether or not to use such code depends on the team configuration in your project. If you have the same team working on two bounded contexts, then it's a good idea to reuse domain base classes. On the contrary, if there are separate teams working on those models, they are better off to have their own implementations of those base classes, even if those implementations are identical. The reasoning behind this guideline is that despite the fact the base classes don't contain any business logic, they still are an additional point of coupling between the models. It is fine to have that point if you have a full control over the bounded contexts that use these base classes. Otherwise, it becomes a burden to maintain consistency in changes that are made by different teams. So, one set of base classes per each team, regardless of how many bounded contexts they are used in. In our application, we use the base classes, because we are the only team working on this project. The third type of code is utility code, code that doesn't contain any domain logic, but rather represents some useful helper methods. The guideline here is roughly the same as with the domain base classes. If a utility is small, such as Session Factory in our application, just duplicate it so that each team has its own set of helper methods. At the same time, if such code gets bigger and you see it can potentially provide a lot of failure to all teams in your company, create an internal open source project out of it. You can extract it to a separate project and make it publicly available to all teams in your company, but that should be something really useful, not just a set of random auxiliary classes. Overall, try to avoid reusing code between bounded contexts as much as possible, especially reusing the code from the domain layer. Extract a domain class to a shared kernel only when all bounded contexts involved have the same perspective on what it represents.
Implementing ATM Domain Logic
Alright, we are now ready to start implementing the second bounded context, the model of ATM. It will be simpler than the snack machine model. The only functionality it should contain is the ability to give the users cash in exchange for their bank cards balance. The ATM also needs to keep track of all charges made. This is how the implementation will look like. You can see it's a single entity with two fields, one for the MoneyInside and the other one for the MoneyCharged. We'll start off by working on the ATM entity. Note that for this project, I've chosen the first type of bounded context isolation, because the bounded context themselves are pretty small at this point. It means that we will gather all classes for the new bounded contexts in a separate folder. We won't create new Visual Studio projects for it. All right. Creating a new class, Atm. This will be an aggregate root, so I inherit it from the AggregateRoot base class. The entity will contain two properties, MoneyInside and MoneyCharged, and I'm adding a method for taking money from the ATM. Now let's add some unit tests. Create an AtmSpecs. Good. With the first test, we'll verify that the money inside the machine decreases and that it accounts the amount of money charged from a user. Creating an entity. In order to check the money inside the machine changes, we need to load it there first. So I'm calling a LoadMoney method here. This method doesn't yet exist. We'll add it shortly. Assuming there is a dollar inside the ATM, I as a user should be able to take it. After that, the amount of money inside must turn to 0, and the amount of money charged should be $1.01, because the commission for withdrawal is 1%. Creating the LoadMoney method, this method will just add whatever money will pass it to the MoneyInside property. We can see it fails. That's because we need to initialize the MoneyInside property with an empty instance. Okay, now the test fails for a good reason. It expects the MoneyInside to turn to 0, but sees there is still $1 inside. Let's implement the TakeMoney method. Just as with the snack machine model, here we allocate the required sum and extract it from the MoneyInside property. To calculate the charge amount, we need to compute the amount with commission and add it to the MoneyCharge property. Perfect. The test is passing. Let's implement a little bit of refactoring. Let's extract the commission rate to a constant, and also introduce a using statement to avoid specifying the Money class here. And let's do the same in the test. Good. There still are some requirements we need to take into account. For example, what if we try to take a single cent from the ATM? Should the commission be still charged in this case, and if so, how much should it be? Also, are there any rules for rounding the commission? For example, what if a user takes $1.10? Should the commission be one cent or two cents? These are the questions we should ask the domain expert. Let's say the expert replied that there should always be at least one cent commission for any withdrawal operation, and the commission itself should be rounded up to the next cent. Now as we have this knowledge, we can reflect it in our unit tests. With this test, we ensure that the commission is at least one cent. You can see we load a cent into the Atm and take it back from it. The money charged should be two cents in this case. This test will make sure the commission is rounded properly. When we try to take $1.10, the machine should charge 2 cents as a commission, $1.12 overall. Both tests are failing currently. Let's implement the required functionality. So, in order to comply with these two requirements, we need a more sophisticated calculation algorithm for the chart sum. We cannot just multiply the amount by commission rate. Here is the function we will use for that purpose. As you can see, it calculates the row commission amount first, and then determines if the amount contains a fraction which is less than a single cent. If there is such a fraction, the method replaces it with a cent, and returns the overall amount with the commission. We can replace this code here with a call to this method. Good. The test has passed. The last thing we need to add is validations. We will use the same pattern we used previously. We will create a separate CanTake method, which will return an error string in case any of the preconditions are violated. So, what are they? First of all, we shouldn't be able to request 0 or a negative sum, it just doesn't make any sense in the context of ATM. Secondly, we need to check that the money inside the machine is sufficient to address the user's request. And finally, we need to check that the machine has enough change to pick the required sum. An empty string here would signalize no errors. After we enumerated all preconditions, we can use them in the TakeMoney method. And of course, it's a good idea to check these edge cases with unit tests. Very good. The Atm domain entity is ready.
Adjusting the database
Now as we have the domain class for the new bounded context ready, let's adjust our persistence layer. Here's the new ATM table. Note that once again, I inlined the data for the MoneyValueObject inside the Atm entity. Besides the MoneyValueObject, we also have the MoneyCharged decimal column. I created a new ATM row for the test purposes, and I also added a new row to the IDs table. All right. We are ready to define the mapping between the entity and the database table, creating a new class, AtmMap, inheriting it from ClassMap. The mapping here is pretty simple. The Id property, the MoneyCharged property, and the MoneyInside value object. Good. The last thing is the repository. Add in a class, AtmRepository, and inherit it from Repository base class. This class will be empty, because all required functionality resides in the base class. That's pretty much it. The last remaining thing here is the user interface.
At this point, we can define the user interface for our model of Atm. I added a XAML view beforehand, and this is the ViewModel for it. You can see it contains only a caption and reference to an ATM entity for now. We need to add the remaining functionality here. This is how the UI looks like currently. As you can tell, it is not functional, because the ViewModel is not yet defined. So, in order for the user interface to display the actual values behind the ATM, I need to add two properties here, MoneyInside and MoneyCharged. And now if I run the application, you can see all the values are filled out. We have the coins and notes inside the machine, as well as the overall amount of money inside and the amount of money charged. The idea is that we simulate the process of withdrawal by sorting the required sum in this text box and clicking on the Take money button. You can see although the values are displayed correctly, the interface doesn't react on the button clicks. To perform the actual withdrawal, we need to define a command, like this, and instantiate it in the constructor. It will execute the TakeMoney method, passing in the required amount dispensed. Note this predicate here. It's a quite handy feature of WPF commands, which allows us to define in what circumstances the command can be executed. In this particular case, we indicate that the command can be executed only when the requested amount of money is more than 0. In this method, we will follow the same steps as in the SnackMachineViewModel. We are asking if an ATM can dispense the sum for the user. If not, notify in the view and returning from the method. Otherwise, taking the money, saving the ATM using the repository, and notifying the view about the success of the operation. To display a message for the user, I need this property. And now I can define the NotifyClient method. You can see it sends the message and notifies the view about two properties the view depends upon. We also need a repository. Instantiating it in the constructor. Good. Let's try to withdraw the money now. I am taking a dollar. As you can see, the number of $1 bills inside the machine decreases, and the money charged is set to $1.01. If I restart the application, all parameters are still in place, which means that the ATM was successfully saved to the database. The last thing remaining here is actual charging the value of cash dispensed, plus 1% commission from the user's bank card. We won't be writing the actual code for this here, but I wanted to show you the overall idea of how to do that. The operation we are about to perform resides in an external service, and the best way to work with external services is create a proxy class that wraps it with an API, which is consumable for our Application Services. Here's such a proxy class, PaymentGateway. It's just a stop for a real gateway, but a real world implementation would probably have similar interface. It's important to understand that in properly isolated model, domain classes shouldn't work with such gateways. The layer which is responsible for that is Application Services, ViewModels in our case. So, let's define a PaymentGateway here, and instantiate it in the constructor. To use the gateway, we need to know the amount to charge. In other words, we need to sum up the amount of money dispensed and the commission. We already have such methods in the ATM entity. All we need to do is make it public. And now we can calculate the amount, and charge the payment. And again, although the PaymentGateway doesn't do anything at this point, this code shows how you can approach this task. Alright, we have the ATM model implemented and working as requested.
In this module, we discussed bounded contexts. Bounded contexts indicate boundaries between different models and ubiquitous languages used in them. We talked about the relation between bounded contexts and sub-domains. The distinction between them is that sub-domains belong to the problem space, whereas bounded contexts is the solution for that problem. We discussed the 1-to-1 guideline. You should try to create a single bounded context for each sub-domain in your system. But keep in mind that it's not always possible. There are three reasons why you might want to depart from this practice. Legacy project, large code base, and large team. We talked about the importance of drawing a context map between bounded contexts. The main guideline here is that it should reflect the actual state of affairs, not the desired one. You learned three types of physical isolational bounded contexts. Keep in mind that while your code base is small, it makes sense not to separate bounded contexts physically and just keep them in a single assembly. As the project grows, however, consider extracting different bounded contexts out of it to separate visuals to your projects, or even to separate marker services. At the same time, make sure you maintain proper boundaries regardless of what type of physical isolation is chosen. We discussed communication between bounded contexts. It depends on the type of isolation you've chosen, and whether or not there is an anti-corruption layer between the contexts. We also talked about code reuse. The main rule here is that you should avoid reusing code that represents domain logic. In the next module, we will talk about domain events and two different ways of working with them.
Working with Domain Events
Hello, my name is Vladimir Khorikov and this is the Domain-Driven Design in Practice course. In this module, we will talk about domain events. We will see when they are applicable and how to actually represent them in code. We'll discuss two different ways to work with domain events, and we'll see which of them is better and why.
Now as we have two models, one for snack machine and the other for ATM, our stakeholders decided we need a new subsystem, which would be responsible for managing the devices. In the real world, a subsystem like this would probably do such tasks as setting new devices across the country, monitoring cash levels inside them, and so on. In our case, let's say it has only two requirements. Keeping track of all payments made by all users of our ATMs, and moving cash from snack machines to ATMs. So, whenever a user withdraws cash, the system should account the sum charged from the user's bank card and show us the total balance we have so far. The second requirement is about moving the cash the snack machines accumulated to ATMs. Such functionality makes sense, because snack machines produce cash so to speak, whereas ATMs consume it.
Introducing a New Bounded Context
Let's see how we can implement the new requirements in our domain model. From the previous module, we know that whenever we hear such words as subsystem, it's a strong sign there is a new sub-domain in the application, and there is. The new set of requirements this subsystem contains is completely different from the requirements we had previously. Let's call the new sub-domain Management. Following the 1-to-1 guideline we discussed in the previous module, we need to create a separate bounded context for it. We need a new abstraction to help us keep track of all wireless payments and transfer cash from snack machines to ATMs. The best way to find a proper abstraction is to talk to the domain expert and ask the following questions. How the payments will be accounted? Where will they go? Also, how exactly the money will flow from snack machines to ATMs? Directly, or maybe there would be a transitional point? Let's say the domain expert replies that the money charged from the user's bank cards goes to our head office's bank account, and when we need to transfer cash from a snack machine to an ATM, we don't do it directly, but rather move it to the head office first and only after that send it to the ATM. Having this information in hand, we can start implementing the new functionality. This is how the new version of the context map looks like. You can see it uses the short kernel with the Money value object, but it also relates to the other two bounded contexts. The relation between them is conformist, meaning that the Management bounded context conforms to whatever model the other two bounded contexts introduce. The letter u here means the bounded context is upstream, or master, in other words. The letter d means downstream or slave. The reason why the Management bounded context is conformist is because it will actively use the snack machine and ATM entities for transferring cash between them, so it makes sense to make it the new bounded context dependent on the existing two. With these answers, we can see there is a separate concept the domain expert used twice, the notion of head office. It is involved in both operations, so it is a good idea to introduce an entity with the same name to our domain model. Let's implement it. I am adding a folder for new bounded context, Management, and a new class HeadOffice. It would be a single entity aggregate. Good. There would be two properties in it. The Balance property will keep track of all payments made from the user's bank cards, and the cash property will contain the cash transferred from the snack machines. Let's now look at different ways of handling our new requirements.
Implementation: the First Attempt
So far so good. We have a new bounded context defined. Let's see how we can implement the first requirement, how we can track all the charges made from the user's bank cards. The first option here is to place this logic to the Application Service AtmViewModel. So basically get an instance of the HeadOffice class somehow, for example, via its repository, increase the balance, and save the instance after that. Although it would work in our particular case to some extent, this approach has several drawbacks. First of all, such implementation means that we are coupling the AtmViewModel to the HeadOffice entity. It means that we are introducing an additional dependency between the ATM and Management bounded contexts. With this solution, not only Management knows about ATMs, but ATM's bounded context also becomes aware of Management. It's a good idea to introduce as little coupling between bounded contexts as possible and creating bi-directional dependency here violates this guideline. The second drawback is that this approach works only until there is only a single place where the TakeMoney method is called. If, for example, we add another ViewModel to the ATM bounded context, which would also dispense cash from an ATM entity, we will need not to forget to repeat the whole process with increasing the balance again. So basically we will need to copy this code to that new ViewModel. Obviously such implementation would be error prone due to code duplication and possible human factor. The second option is to implement this functionality in the TakeMoney method itself. Add a new parameter, HeadOffice parameter, and increase the balance by the amount with commission. While this allows us to eliminate the problem with code duplication, other problems come into play. First, we still have bi-directional coupling between the two bounded contexts, which should be avoided when possible. And second, and more important, the ATM entity now gets a responsibility which is not related to the ATM itself. It is not the job of an ATM to increase the balance of the HeadOffice. Such duty just doesn't make sense in the context of our domain. It clearly violates the single responsibility principle. So how to solve this problem? The solution here is domain events. The ATM entity should raise an event about the charges that take place when the user withdraws cash, and the Management context should subscribe to those events and change the HeadOffice instance accordingly. This way we avoid introducing a bi-directional relationship between bounded contexts and stay away from the other problems we discussed here. We will see how to implement domain events later in this module. For now, let's take a closer look at this concept.
So, what is a domain event? A domain event represents an event that is significant for your domain model. It's important to distinguish usual events or system events from domain events. The former refer to such things as a button click, timer tick, window closed events, and so on. In other words, they represent notions that are related to the infrastructure. Domain events, on the other hand, describe occasions with are important for our domain. For example, when we click on the TakeMoney button on the user interface, the click itself is a system event. Our domain doesn't care about it, just as it doesn't care about anything else on the user interface. At the same time, the domain operation, which happens after the button click, namely the withdrawal operation, has a meaning for our domain. In our case, we need to account that operation and change the balance of the HeadOffice. Domain events are often used to decouple bounded contexts completely or replace bi-directional relationship with a uni-directional one. You saw an example of it in our application. Domain events will help us to avoid making the ATM bounded context aware of the Management bounded context. Along with direct calls, domain events is a technique for establishing communication between bounded contexts. Although it's the most common use case for domain events, it's not the only one. They can also be used for collaborating between entities within a single bounded context. It might be that an entity inside the bounded context should perform an action, which doesn't belong to the list of its responsibilities. In this case, it would be a good idea to introduce a domain event instead and perform that action elsewhere. You saw an example previously. The HeadOffice entity should react on every withdrawal in our domain model, and instead of adding the responsibility to change the balance to the ATM entity directly, we decided to create a domain event and thus free the ATM class from that duty.
Introducing a Domain Event
This is how the domain event will look, and this is where it will be used. You can see instead of directly changing the balance of the HeadOffice here in the TakeMoney method, we create an instance of the event and raise it. We will talk about the actual techniques for raising events later in this module. For now, let's discuss best practices for defining domain events themselves. The first guideline regards to the naming conventions. As a domain event represents something that happened in the past, we should name them accordingly in the past tense. So, in our case, it is BalanceChangedEvent, not BalanceChange or ChangeBalance event. Also, try to be specific about what happened. Don't give generic names to your domain events. The next guideline here is that you should try to include as little data in the domain event as possible. Ideally it should contain only the information that is needed for the external code to react on this event, nothing more. In our case, you can see we include only the Delta, the sum that was charged from the user's bank card. A question that often arises when it comes to defining an event is what data structure should one use to represent that information? Can we use entities and value objects for that? For example, if we had a person entity with this structure and we need to track changes in their first, last, and middle names, can we just create an event like this and include the person as a property in it? The answer is no. Doing so is generally a bad practice. The reason here is twofold. First of all, this way we almost always include more information to the domain event than needed, and that contradicts the first guideline. Secondly, with this implementation, we introduce an additional point of coupling between bounded contexts. It's basically fine if the bounded context consuming the event is connected to the bounded context which produces the event, and resides on the downstream side. In such a situation, the consuming bounded context has to conform to the producing bounded context anyway, meaning that it already knows about its internal structure, but it might not always be the case. It may be that two bounded contexts don't know of each other and still one of them is subscribed to the events from the other. In this case, adding a domain class, be it an entity or a value object to a domain event, adds a necessary coupling between the bounded contexts. So, the guideline here is to always represent data in domain events with primitive types only. Another frequent question is whether to include an ID of the changed entity or enclose full information about it in the domain event? The answer to this question depends on the relationship between producing and consuming bounded contexts. If the bounded contexts that are subscribed to the event already collaborate with the context which originates the event, it's fine to use Ids. The downstream bounded contexts will be able to query information about the entity on receiving the event. We don't introduce any additional coupling here. At the same time, if they don't know about the bounded contexts producing the event, we have to enclose full data with regard to the changed fields of the entity to the event. But again, we must use primitive types to represent this data and not use the entity itself.
When working with domain events, another question that inevitably arises is how to deliver them physically to the subscribers? The answer depends on what type of physical isolation is used for bounded contexts. If the bounded contexts reside in a single process, then the delivery isn't actually a matter because the code in those bounded contexts share the computer memory. In the case of separate processes, the delivery goes via the network using some sort of a service bus. We will look at some of the techniques later in this module, but we will not dive deep into them in this course. However, I want to emphasize that just as physical isolation of bounded contexts is orthogonal to the logical isolation, physical delivery of domain events is not related to the logical counterpart. We can use whatever delivery technique we want. We can even persist the events and use them later in an event-sourcing architecture. All such techniques use the same logical concept of a domain event behind the scene.
Building up Management Bounded Context
Before we move forward with domain events, let's build up the Management bounded context and introduce persistence for it. This is how the table for the HeadOffice entity will look like. Once again, note that we are inlining the Money value object here. Along with Money, the table also contains the balance column. And here I added a row to that table, so that we have some test data to work with. The HeadOffice entity will need a functionality to change the balance, so we can add a ChangeBalance method to it, and initialize the cash property. And now we need to map the domain class with the database table. Creating HeadOfficeMap, inheriting it from the ClassMap base class. The mapping is quite straightforward, the Id field and the two properties, Balance and Cash. Next we need to add a repository for that aggregate, HeadOfficeRepository. It would also be empty, because all required functionality resides in the base class. Good. And lastly, we need to think about how we will access the HeadOffice. Our stakeholders currently have only one HeadOffice, so it's a good idea to reflect it in our domain model. It doesn't make a lot of sense to always retrieve that single instance from the database. What we can do instead is we can employ the singleton design pattern and keep a reference to the HeadOffice instance during the entire application lifetime. We can implement the singleton pattern by adding a new class, HeadOfficeInstance. This class will hold the reference and our code will access it when needed instead of fetching it from the repository. All right, we can make the class static and introduce a static property, Instance. We know that the only HeadOffice in our domain model has the ID of one, so we can specify it here as a constant. And finally, we need to add an Init method, which would initialize the singleton by fetching the proper object from the database. We need to add a call to this method here in the Init class so that it will be worked on the application startup. You might wonder where in the onion architecture this class resides. It plays the same role as a repository, so it resides in the second innermost layer on this diagram. It means that classes from this layer cannot access it. Only classes from the same layer or upper can work with this singleton. We didn't implement this functionality in the HeadOfficeRepository itself, because it would violate the single responsibility principle. Repositories should fetch objects from the database and save it back. They shouldn't hold instances of anything. That's a task for another domain class. So now if we run this code, you can see the instance of the HeadOffice class was successfully loaded into memory. Alright, the HeadOffice entity is ready, and now we need to bind these two classes. So basically when a user takes money, we need to change the balance of our HeadOffice by passing in the amount of money the ATM charged from the user. That's where domain events will help us.
Handling Domain Events with the Classic Approach
To handle domain events, we need to define an interface, IDomainEvent. This is a marker interface in a sense that its only purpose is to mark classes that represent domain events in our model. Another interface we need to introduce is a IHandler. It has a type parameter which has a restriction. It has to be derived from IDomainEvent. This is an interface for all domain event handlers in our code base. The type parameter T specifies what type of domain events it handles. The only method it must implement is Handle. All right, now we are ready to create a class for the event we discussed previously, BalanceChangedEvent. It should inherit from the IDomainEvent interface, and it should contain the data about the balance change. To handle this event, we need to add a Handler class, BalanceChangedEventHandler. It should implement the IHandler interface of type BalanceChangedEvent, which means that it should contain the Handle method accepting the BalanceChangedEvent. To implement it, we need to create a repository, get the HeadOfficeInstance, change the balance using the data passed with the event, and save the Office. This will be the handler implementation. Handlers are usually quite simple, because what they often do is just delegate the actual work to other domain classes. You can think of event handlers as of domain services. Their roles are very similar. We'll talk about domain services in more detail in the next module. Note that the event itself is defined in the ATM bounded context, whereas the handler for it in the Management context. It is no coincidence, because it reflects the actual relation between the bounded contexts. The ATM bounded context generates the event and the management bounded context consumes it and reacts accordingly. It also allows us to preserve the uni-directional relationship between them. So how can we glue the event and the event handler together? We will first implement the classic approach for this. After that, we will look at its drawbacks and then introduce a new one. Finally, we'll compare the two approaches to each other. All right, to actually raise the events, we need to introduce a new class, DomainEvents. It will be a static class. This is its content. Let's walk through it together. The idea behind this class is that it maintains two lists of handlers, dynamicHandlers and staticHandlers. StaticHandlers are handlers that we define as classes, just as we did with the BalanceChangedEvent handler. This code here scans the current assembly for all such handlers and gathers them into a single list. It determines whether or not a class is a handler by looking for the IHandler interface we defined previously. DynamicHandlers, on the other hand, are the handlers we add during the runtime with this register method. They are basically just delegating which class adds to the internal collection of delegates for a particular event type. You can see the class pre-creates a dictionary for those delegates. The key of the dictionary is the type of an event and the value is the list of handlers we add in the runtime. DynamicHandlers are usually used in unit tests to check that the events are raised correctly. This method processes the actual events. It first goes through the dynamicHandlers and checks all that suit the type of the event passed in. It invokes each handler in the list. After that, it goes through the staticHandlers and sees if there are any that implement the handler interface. If there is, the method instantiates a handler object and calls the Handle method on it. So, basically whenever we need to raise an event, we call the static raise method of the domainEvent class, and it dispatches the event to all dynamic and static handlers registered. We need to add the Init method to the Init class, so that it is also called on the startup. All right, let's see how we can actually use this class. Let's add a new test to the ATM specs named Take_money_raises_an_event. Here we will create an ATM, load some money into it, and take that money. After that, we need to somehow check that the event was raised. How can we do that? This is where the Register method will help us. We can register a new delegate, which would save the raised event in the local variable, like this, and then verify that the event was actually raised by checking that it's not null and that it contains the correct amount of money in the Delta property. The Register method call throws an exception, because the dynamicHandlers collection is not initialized. I need to add a call to the Init method to fix that. Good. Now you can see the test fails on this line, which means the BalanceChangedEvent wasn't actually raised. To implement the required functionality, I should call the DomainEventsRaise method in the TakeMoney method and pass it a new event, BalanceChangedEvent, with the amount of money with commission. The test is passing now, which means the event was successfully raised.
Recap: Classic Approach
The approach we employed in the previous demo works, but it has two major drawbacks. The first one is that it violates the isolation principle we discussed in the first module. Layers in the onion architecture should know only of themselves and the ones residing lower. They shouldn't depend on classes from outer layers. The ATM entity works with the DomainEvents static class, which doesn't belong to the innermost layer of the onion architecture. Therefore, this implementation damages the isolation of the domain model. The lack of proper isolation results in several smells we can see in our code base. One of them is this awkward delegate we have to define in the unit test in order to check that the event is raised. Another one is the list of dynamic handlers in the DomainEvents class, and the Register method which is used only in unit tests. Introducing code in the domain layer that is used in unit tests only is generally a bad practice. The second drawback is that such implementation doesn't fit into the notion of unit of work. This is best expressed with an example. Let's say that we start a business operation. It first executes a method which results in a domain event, employs some validations next, and finally saves everything into the database. The happy path in this case works fine, an event is produced, the subscriber successfully consumed, all validations pass, and all data is persisted. But we'll get a problem in case the validation fails, and we need to terminate the operation. The problem is that the domain event is already raised and processed by the time the validation fails, and there is no easy way for us to rollback the changes made. This is why this implementation doesn't fit the notion of unit of work. The act of processing an event occurs before we commit on producing that event. Another example of this problem is multiple mutations. We could change the balance of an ATM for several times during the single business transaction, and each time we do that, a new event is raised and processed. A better option would be to create only one final event with the overall change and raise it instead. And it's really hard to implement this option using the code from the previous demo. These shortcomings make the solution we introduced unreliable for any more or less complex situation. I brought it here just to make you aware of it, because many programmers still employ this approach when working with domain events.
A Better Approach to Handling Domain Events
So how can we improve the solution? Is there a way to implement domain events without introducing the drawbacks we discussed previously? Fortunately, yes. The idea is that instead of raising the event right away, we should distinguish two separate notions, creating an event and dispatching it. Entities should be responsible for the creation of domain events only, whereas the dispatching functionality should be attributed to infrastructure. This way we will be able to maintain the concept of unit of work in our code base. Let's implement that in practice. This is where the AggregrateRoot base class will help us. It will store all domain events created by an aggregate so that they can be dispatched later on. Here is how it looks like. We define a list of domain events and two methods working with them. Add a new domain event and remove all events. Note that the AddDomainEvent method is protected. That's because the responsibility to create an event should belong to the entity itself. We shouldn't commission it to external code. So now as we've defined the collection, we can replace this code here with adding a new event. Our domain entity is no longer responsible for raising it. All it does it saves it to the internal list. All right, we have entities with domain events attached to them. How can we process those events, and not only just process, but do that in a way that allows us to preserve the unit of work semantics? In other words, take an action only when the business transaction is committed. To do that, we can rely on our ORM. NHibernate provides several extension points that allow us to inject our code into its internal pipeline so that it would be executed only when some persistent event occurs. In our case, we need to process domain events only when the aggregate was successfully persisted into the database. To do that, we need a new class, EventListener. This class will implement four interfaces, like this. Let me put them on a single line so that you can see them. All four methods we define in the class call a private DispatchEvents method that performs the actual dispatching. Let's take a minute to discuss how this technique works. These three methods, PostUpdate, PostDelete, and PostInsert, are used by NHibernate after an entity is updated, deleted, or inserted. There also are interfaces for listening for their counterparts, PreUpdate, PreDelete, and PreInsert, but they don't fit our needs, because until the operation is finished completely, there still is a possibility for it to fail, for example, because of a database constrained violation. So, the use of post-persistent events allows us to dispatch domain events only after the persistence operation is completed, and thus preserve the unit of work semantics. Note the PostUpdateCollection method here. It is triggered after a collection update. We don't need it in our application, but I include it here so that you can use this code as is in your own project. This method is required in some sophisticated update scenarios. For example, if a snack machine in our domain model could change the number of slots, and we for some reason wanted to raise a domain event every time it happens, we wouldn't be able to just use the PostUpdate method. The snack machine entity itself in this case wouldn't change, and so NHibernate wouldn't trigger the PostUpdate event for it. What it would do instead is it would fire a PostUpdateCollection event. That's why we are listening to it here. That way, we are sure the domain events get processed regardless of whether or not the entity's fields are changed. The DispatchEvents method, as you can see, just goes through the DomainEventsCollection and dispatches them one by one. After that, it raises it. To make NHibernate aware of the DomainEventListener class, we need to specify it in the configuration, like this. And let me put each array on a separate line. All right, as you can see, this Dispatch method here doesn't yet exist. We need to modify the DomainEvents class first. Here's the current implementation. I'll copy it to a separate file so that you can review it and compare with the new implementation later on. The new version doesn't need the dynamicHandlers anymore, so I remove the Dictionary and the Register method. We also won't need it in the Raise static method. And I will name the Raise method into Dispatch, because it better represents its functionality. We don't raise an event with the DomainEvents class, we only dispatch the events that were created by domain entities. When we dispatch an event in the EventListener class, we no longer know the concrete types. They all are of the IDomainEvent type for us now. So we don't need the type parameter in the Dispatch method anymore. And we can rename the staticHandlers to just handlers, because it's the only type of handlers we have anyway. Let me also rename handler to handlerType. Okay, to actually check that the handler is capable for handling the event we are dispatching, we need to employ a reflection. And to handle the event, we can use dynamics. We also could use reflection here, but the code would look quite cumbersome. Dynamics work much better in this particular case. All right, now as we've changed the way we work with domain events, we can change the unit test. No need to declare the event up front and use the Register method to capture it. What we can do instead is we can grasp the balanceChangedEvent from the ATM entity and check that it's not null and that its Delta property is indeed the right one. All tests are passing. Very good. And you can see the test working with the domain event has become much more succinct. That's because we removed the smells we introduced with the previous implementation. To raise the readability of the test even more, we can create an Extension method for the ATM entity, which would allow us to check the event using a one-liner. So instead of these three lines here, we can write ShouldContainBalanceChangedEvent, and specify the actual change. Perfect. Let's see how it works in practice. I'm taking a $1 from the ATM. You can see the number of $1 bills in the database has changed, and so has the balance in the HeadOffice instance. Very good. We have implemented the new approach to working with domain events.
Recap: a Better Approach
Let's recap what we've done in the previous demo. First of all, we split the two responsibilities that were bound together before that, creating a domain event and dispatching it. Our entities are now responsible for the creation of events only. The actual dispatching is given away to the infrastructure. This implementation allows us to preserve the isolation for our domain model and also adhere to the notion of unit of work. In terms of processing the events, we rely on the internal NHibernate mechanics, which gives us a great opportunity to extend its behavior with our own code. We listened to the events NHibernate raises when it finishes persisting domain classes. We dispatch the domain events only after the persistence is completed. This allows us to maintain consistency between the changes made to our aggregates and the domain events we raise. Also, note that we defined the collection of domain events in the AggregateRoot base class. That's no coincidence. Just as aggregate roots are responsible for maintaining aggregate invariants and consistency boundaries, they are also responsible for all domain events occurred in the aggregate.
Using Domain Events to Communicate Between Microservices
As you can see now, domain events is a powerful tool which facilitates communication between bounded contexts. I mentioned earlier that you can use the concept of domain events regardless of what type of physical delivery is chosen for them. Here I want to show you an example of how you can implement communication between bounded contexts, which reside in separate processes. In other words, between microservices. This is how we could send domain events to other microservices. You can see this is the same event handler as before, but this time instead of executing the actual code for handling the event, it sends an ESB message via a message bus. This message is then received and processed by whatever microservice is listening to these particular kind of events coming from the message bus. Also note that this version of the handler resides in the ATM bounded context. That's because the responsibility to send the event to the message bus lies on the bounded context producing it.
Adding Interface for the Management Bounded Context
Alright, we are done with the first requirement, keeping track of all charges made from the user's bank cards. The second requirement we have is moving cash from snack machines to ATMs. We won't dive deep into it too much here, because the solution is pretty straightforward, but I wanted to show you the end result and point out some key elements of the implementation. This is our dashboard. It displays a list of all snack machines in our system, as well as a list of all ATMs. You can see it also shows the balance of the head office itself and the amount of cash it stores. I've set all values to 0 to show you the actual workflow of transmitting cash from a snack machine to an ATM. To use a snack machine, we can display it by clicking on the Show button. Here let's say, for example, that I want to buy a chocolate. I've inserted $3 and buying it. After I close the window, you can see the amount of money in the list changes. I can now unload the money from the snack machine to the head office, and load it to the ATM. Now I as a user of the ATM can take $3 from the ATM. You can see the balance of our head office has changed to $3.03, meaning that our head office just earned 3 cents. To implement the actual functionality, I added two methods to the head office class. One for unloading cash from a snack machine and another one for loading it to an ATM. Note that in the dashboard view model, we use data transfer objects, DTOs, to display lists of snack machines and ATMs on the user interface. There is an important guideline when it comes to displaying data on the UI. Unless your domain classes are really simple, using them to display lists of data is a bad idea, because it almost inevitably leads to poor performance. You should perform using special classes, DTOs, by default. The sole purpose of such classes is to just transfer data, nothing more. Here's the DTO for the snack machine entity. You can see it contains only an ID and money amount fields. In other words, only the fields that are required for the user interface. They also don't contain any business logic, because they are not part of our domain layer. This is the DTO for the ATM class. It looks very similar. Here's how we get a list of ATMs. I commission this responsibility to the corresponding repository. In this method, I use NHibernate to transform domain entities into DTOs. Note that this is a shortcut, because despite the use of DTOs, we still load full domain entities into memory in order to generate the DTOs. It's basically fine if your application doesn't have a lot of performance requirements. If it does, you can fall down to plain ADO.NET or some lightweight ORM, like Dapper, and create DTOs directly without involving domain entities. This technique is referred to as CQRS and the use of DTOs is one of its types. If you want to learn more about CQRS and different types of it, you can read my blog post here. This is how the GetSnackMachineList and GetAtmList methods are used in practice. Whenever we need to refresh the lists, we use the repositories to fetch new data from the database and notify the user interface, so that it can re-render it on the screen.
In this module, we talked about domain events. Domain events is a great tool that helps us keep bounded contexts decoupled from each other. You saw how awkward code might be without them. Although communication between bounded contexts is the most common use case for domain events, they can also be employed for decoupling classes inside a single bounded context. We discussed four best practices for defining domain events. First, keep the name in the past tense. Second, try to include as little data in a domain event as possible. Ideally it should contain only the information that is needed for the external code to react on this event, nothing more. Third, don't include domain classes such as entities and value objects into domain events. It would introduce a necessary coupling between bounded contexts. And finally, include full information about changed objects in the case consuming bounded contexts don't know about the bounded contexts producing the event. In case they know about it, you can include just an ID of the changed entity. We touched upon physical delivery of domain events. The main point here is that how exactly domain events are delivered to consuming bounded contexts is orthogonal to the notion of domain event itself. The techniques for working with them in the domain model remain the same. You saw two ways of handling domain events. The first one, the classic one, relies on domain entities raising events directly via a static class. This approach has two drawbacks. The first one is that we damage isolation of our domain model, because the innermost layer of the onion architecture in this situation depends on the infrastructural static class. The second drawback is that we no longer adhere to the unit of work concept. The domain events in this situation get processed before we commit the transaction, and that can lead to inconsistencies in case the transaction cannot be committed for some reason. The second approach doesn't have these shortcomings and I advocate to use it instead. With the second approach, we separate two responsibilities, one for creating an event, and the other one for dispatching it. That way we keep our domain model isolated and also adhere to the notion of unit of work. You saw how to work with lists of objects on the user interface. The guideline here is to avoid using domain entities to display data on the screen. Instead, use DTO classes by default. You can find the source code for all the demos in the course in the exercise files. I also created a GitHub repository with the final version of it. Here it is. Alright, that's it for this module. In the next one, we'll look at some DDD concepts we didn't discuss yet, such as Domain Services and factories. We will see what further enhancements our application can take in the future. We'll also talk about some common anti-patterns programmers employ when they start applying DDD principles.
Looking Forward to Further Enhancements
Hello, my name is Vladimir Khorikov and this is the Domain-Driven Design in Practice course. In this module, we will discuss DDD concepts that we didn't cover in our sample project, and some further possible ways in which our application can evolve in the future. We will also look at some common anti-patterns programmers employ when they start applying DDD principles in practice.
Always Valid vs. Not Always Valid
One of the debatable topics in DDD is whether to always keep entities and value objects in a valid state or allow them to reside in an invalid state and check that state later on; for example, before saving them to the database. This topic is best expressed with an example. Let's say we have a domain entity cargo with a Max Weight property. Let's also say it contains several items, each of which has its own weight, and there is an invariant saying that the cargo cannot contain a number of items the total of weight which exceeds the maximum weight the cargo can handle. This is how we could express the domain adherent to the Always Valid approach. You can see we first create a cargo with some maxWeight value, and then check that this maximum weight would not be exceeded in the AddItem method call. If it is, we throw an exception signalizing that the invariant of the entity is violated. That way we don't allow the class to enter an invalid state. This is, on the other hand, how we could solve the problem adherent to the Not Always Valid approach. You can see we allow any number of products to be added to the cargo. To validate whether or not the invariants are broken, we introduce a separate IsValid method. Both techniques have their own pros and cons. The main benefit of the always valid approach is that we as programmers can be sure that objects we are working with always reside in a valid state whenever we accept them as int parameters, or get them as a result of some operation. On the other hand, the Not Always Valid approach allows us to gather most of the validations for an entity in a single place, and thus simplify the relational logic. So, what approach to choose and why? Despite the benefits of the Not Always Valid approach provides, I strongly recommend you adhere to the Always Valid approach. There are two reasons for that. First of all, it removes temporal coupling. With the opposite technique, you must always remember to call the IsValid method before persisting an entity to the database, or before executing some business critical operation. This often ends up to be an error prone way to build a domain model. Secondly, the Always Valid approach helps with the don't repeat yourself principle. In other words, it helps eliminate duplications which inevitably take place with the Not Always valid approach, because of the necessity to validate domain entities multiple times during a single business operation. Overall, it is better to adhere to the guideline stating that all domain entities and value objects should always reside in a valid state and maintain their invariants during the full length of their lifetime. A violation of any invariant in the domain model should signalize a bug and lead to a failure in order to protect the persistent state. This principle is often referred to as fail-fast and is one of the most important principles of software development. You can read more about it here. So, if all domain classes reside in the valid state, where exactly should one perform validations? In the example above, where should we check that an item can be added to a cargo? The best place to perform such validations is the boundary of the domain layer in the Application Services. You saw this technique in our project. Whenever we needed to validate whether a snack could be bought from a snack machine or a sum of money could be taken from an ATM, we performed validations in the view models first, and only after that performed the corresponding operation upon the domain entities. This way we ensured all classes in our domain model always reside in a valid state. Otherwise, our domain classes throw exceptions, which lead to application crash.
One of the DDD concepts we didn't use in our sample application is factory. Factory is not a DDD notion per say, it was first described by the Gang of Four in the Design Patterns book, but it's still worth discussing. A factory is a class, which is responsible for creation of domain entities. Sometimes creating an entity requires a lot of work in a sense that you need to retrieve some information from different places and combine different pieces of it together in a certain way. In this case, it would be unwise to commission this responsibility to the entity itself, because construction of an entity has nothing to do with exploiting it after that. Think about it this way. A car engine is a complex device, but it's not responsible for self-construction. That is what engine factories are for. Designing an engine which can build itself is in theory a feasible task, but such a device would most likely be too expensive and unstable compared to a classic car engine. The same reasoning is applicable to domain entities. If the logic for creating them is complex, it is better to extract this responsibility to a separate factory class. That would help keep the entity simple and thus more maintainable. Just as repositories, factories create whole aggregates, not just separate entities in it. At the same time, don't add a separate factory in case the initialization logic is simple enough. Class constructors would be a better option in such a situation. You saw that in our domain model, we didn't introduce any factories. That's because there wasn't any complex business logic associated to the creation of our entities.
Domain Services vs. Application Services
Another important DDD concept we didn't discuss yet is Domain Services. A Domain Service is a class which doesn't have any state associated with it and which contains some domain logic. The best way to think about Domain Services is to view them as containers for the knowledge which doesn't belong to any entity or value object, but is still essential for your domain. It might happen that an operation is related to some entity, but representing it as a method in that entity wouldn't make much sense. In this case, it is probably a good idea to delegate this operation to a domain service. An example here would be a car service. We don't entrust a responsibility to maintain a vehicle to the vehicle itself. It's just not the way it works in the real world. A better decision would be to introduce a separate domain service responsible for that operation. A question which often arises when people start thinking of Domain Services is how they differ from Application Services. The difference here is that an Application Service resides outside of the domain layer, whereas a Domain Service is inside of it. It means that Application Services are in charge of communicating with the outside world and shouldn't contain any domain logic. What they should do instead is delegate the execution to the domain classes, such as entities, repositories, and Domain Services. Domain Services, on the other hand, do contain domain knowledge and shouldn't communicate with the classes outside of the domain layer, the two innermost layers in the onion architecture. We didn't have any Domain Services in our project, but at some point, we could. For example, if we had a requirement to distribute a given amount of products evenly among several snack machines, that would be a task for a Domain Service, because it is a domain-related operation and it doesn't fit the responsibilities of any existing entity in our domain.
Anemic Domain Model Anti-pattern
There are several pitfalls programmers starting with DDD may run into, and it's important to know about them. One of such pitfalls is anemic domain model. If you try to follow DDD principles, then you are probably not subjected to this anti-pattern, but it's still worthwhile to know about it. Anemic domain model stands for separating data and methods working on that data to separate classes. It usually means that entities in such models contain only data and all domain logic is extracted to Domain Services. In our application, for example, anemic domain model would look like this. You can see the snack machine entity contains only data, MoneyInside, MoneyInTransaction, and the collection of slots. All operations up on the snack machine are delegated to the snack machine domain service. In object-oriented programming languages, introducing an anemic domain model is generally a bad idea, because it hinders application of many OPD design patterns and best practices. The most important drawback here is that it often leads to poor encapsulation. Note that the properties in the snack machine entities are all public. That's no coincidence. The only way to enable separation of data and logic into different classes is to expose the internal structure of domain entities and thus break their encapsulation. Such code is much harder to maintain, because its invariants can be easily broken. You can read more about anemic domain model in this article written by Martin Fowler.
Fat Entities Anti-pattern
Another anti-pattern I'd like to mention is fat entities. This anti-pattern resides on the other part of the spectrum of bad design decisions comparing to anemic domain model, and basically stands for putting too much logic to entities. There is a fine balance between anemic domain model and fat entities, and it's sometimes hard to find it. So, how do you know that you put too much logic to your entities? One of the clues is that your entities start having responsibilities that seem unnatural to them. For example, in our sample application, we could commission the duty to update the balance of the head office to the ATM entity. Such responsibility doesn't make a lot of sense in our domain, because ATMs don't actually update the balance of our office. Nevertheless, we could do that and that would be a sign of the ATM entity getting too fat. Another symptom of this anti-pattern is when domain entities start looking up data in the database or communicating with outer layers of the onion architecture. In other words, when we break isolation and allow the innermost layer depends on the classes from outer layers. To avoid falling into the trap of fat entities, you need to make sure your domain model is properly isolated, and all entities' responsibilities make sense from the domain point of view.
There also is a quite common anti-pattern involving the repositories. It regards to the way a repository initializes domain entities that are returned in different methods. Let's elaborate on that. Let's say, for example, that we have three scenarios, each of which requires a different set of data from snack machines. The first one needs all data associated with snack machines in the database. So, we create a method in our repository, like this. Another scenario doesn't need slots from the database, so we decide to create a separate method, which also returns all snack machines from the database, but doesn't fetch slots attached to them. This would allow us to improve the performance of fetch operation, because we reduce the amount of data we retrieve from the database. And finally, the third scenario requires only the machine's identifiers, so we add a third method which also returns a list of snack machines, but fills only the ID property of them. All other fields remain empty. This allows us to improve the database select query even further. This technique leaves us with three methods. Each of them is perfectly justified from the performance point of view, because each of them returns only the data that is required in one particular case. The problem with this approach, however, is that these two methods return partially initialized entities. And with partially initialized properties, we cannot ensure validity of the entities and thus cannot adhere to the Always Valid approach we discussed previously. In our case, snack machines should always contain exactly three slots, and by not returning them along with the machines, we violate this business rule. Partial initialization leads to inability to maintain invariants of the entities and thus should be avoided. If your repository returns a domain entity, make sure it is fully initialized, meaning that all its properties are filled out. But what should we do in case we really need the performance benefits partial initialization provides? The solution here is not to use domain entities as returning objects in such situations. So in this case, we can keep the three methods, but use other data structures instead of snack machine entities. For the method that returns all machine properties except slots, we can use data transfer objects and put this data there. For the third method, we can employ the long type and return IDs as is without wrapping them into a separate class. Not only does this approach solve the problem with the validity of snack machines, but it also makes explicit what data the repository methods return. This Dto class doesn't contain a slot collection, so we won't be able to accidentally access it. When we'll be processing the DTO on the client side, the compiler would notify us about that.
Mechanical Approach to DDD
The last anti-pattern I'd like to talk about is mechanical approach to domain-driven design. I've seen several times how programmers start treating their domain mechanically. Whenever they defined a new concept in the domain model, they automatically created several classes for it, a class for the concept itself, a repository, a factory, and a domain service. They did that even if there was no need for those classes. Don't do that. Not only does such approach violate the YAGNI principle, it also greatly diminishes the benefits of domain-driven design. Domain modeling is not something we can automate or commission to external tools. The act of modeling is closely related to learning. When we build our domain model, we first and foremost learn the domain we are working in. The code is an artifact of that learning process. Mechanical approach to building the model, as well as mechanical approach to learning doesn't do any good. The only way to perform that is to do it thoughtfully. It also means that such tools as code generation and scaffolding don't help with this process, either. If you find yourself relying on a code generation tool for building your domain model, you are most likely falling into the trap of mechanical approach to domain-driven design.
Let's overview our application and see what further enhancements it can potentially take in the future. This is the map of the bounded contexts we ended up with in the previous module. Note that they are rather small in a sense that they contain few entities each. In the real world application, they would probably be bigger, because a typical application usually has much more requirements than we covered during this course. One of such requirements could be constructing a snack machine with different parameters, such as various numbers of slots, products, specialization, and so on. And that can be potentially be the direction in which the development will move further on. This additional functionality would belong to the snack machine bounded context. Also, we would probably need some kind of product supply for the snack machines our company owns in order to be able to refill them in a timely manner. This would belong to another bounded context, product supply, and it would contain its own version of the product entity, because the perspective from which the two bounded contexts view this entity differs, despite the fact these classes will represent the same physical product. For the ATM bounded context, we will probably need a collection service, which would reveal cash in them. That would also be an additional bounded context with its own representation of ATMs. Note that as the application evolves, we can change the boundaries of the bounded contexts. That's perfectly fine as long as we state those changes explicitly in the context map. We can also change the type of physical isolation we employ for them. As the bounded contexts we work on grow, we can extract them into separate microservices and draw them as independent applications.
In this module, we discussed several DDD concepts we didn't touch upon in the previous modules. We talked about factories, classes that should be used to remove responsibility to properly initialize domain entities from the entities themselves. It's a good practice to employ that pattern in case the initialization logic gets too complex. We looked at the notion of Domain Services and how it differs from Application Services. The main difference here is that Domain Services reside inside the main layer, and thus contain domain knowledge, whereas Application Services coordinate the work between the domain layer and the outer world. We discussed the dichotomy of Always Valid versus Not Always Valid approaches to entities and value objects. It's a good practice to adhere to the Always Valid way of working with domain objects, and don't allow them to break the invariants. That would help eliminate duplications in code and adhere to the fail-fast principle. We talked about two anti-patterns that reside on the opposite side of the spectrum, anemic domain model and fat entities. Don't run two extremes, and always keep balance between the two. We also talked about a common anti-pattern, which sometimes arises when it comes to working with repositories. Make sure that every repository method return a domain entity fully initializes that entity. If you need some partial data from you database, use data transfer objects instead. We discussed mechanical approach to domain-driven design. Domain modeling is one of the most important parts of software development. Make sure you treat it accordingly and pay close attention to that process. Finally, we talked about further enhancements our sample application can potentially take in the future.
I'd like to provide a full list of resources I referenced during this course. If you have a full Pluralsight subscription, then you have an access to the source code of the project we were working on. If not, here is a link to it on GitHub. The other references regard to the topics we discussed in this course. This is my blog post on integration testing where I give an example of how to employ it in a typical DDD project. A question on Stack Overflow with a good explanation why you should always override the GetHashCode method when you are overriding the Equals method in your class. Here I write about different approaches to unit testing, namely test-first and code-first approaches. This is a nice explanation of what Hi/Lo algorithm is. In this video, Eric Evans gives a talk about bounded contexts with regards to microservices and also provides a good example of what context mapping is. Martin Fowler and James Lewis have written a great article on the topic of microservices, so if you want to learn more about it, it's a go-to resource. Here I write about cohesion and coupling and how these notions relate to each other. In this post, I describe different types of CQRS. The use of DTOs you saw in the previous modules essentially goes under the first type of it. This article depicts the fail-fast principle and here you can read more about anemic domain model.
We've made great progress in this course. We've built a small, but fairly complex application from the very beginning using domain-driven design principles. You saw how DDD helps us focus on the essential parts of the software, its domain model, and how it allows us to simplify this model and thus keep it maintainable in the long run. You learned the differences between entities and value objects, how to keep the model isolated in the face of working with a relational database, and an ORM, how to work with aggregates, repositories, bounded contexts, and domain events. You also saw how DDD plays with unit testing and MVVM design pattern. Feel free to contact me. Here is my email, Twitter handle, and blog. This is Vladimir Khorikov. Thank you for listening.