Domain-Driven Design Fundamentals
-
10 Years of Domain-Driven Design
Eric Evans coined the term Domain-Driven Design in his groundbreaking book with the same title, published about 10 years ago in 2003. Since then other titles have followed including great books expanding on this subject by Jimmy Nilsson in 2006, and Vaughn Vernon in 2013. More titles like Professional Domain Driven Design Patterns are coming soon. There's definitely continued and renewed interest in Domain-Driven Design as both the demand for and complexity of software continues to grow. Domain-Driven Design is commonly referred to as DDD, and even has its own Twitter hashtag #DDDesign. Although DDD has been around for over 10 years now it continues to be a great approach to building software that we both enjoy employing and sharing with others. We hope you'll agree after watching this course that there are some great ideas and practices in DDD that you can use to improve the software you create.
-
What's in This Course?
Domain-Driven Design is a huge topic. Our focus will be on the developer perspective and the technical, and coding aspects of DDD more so than architectural concerns. In this module we'll first talk about why we think you should even be watching this course. Next, we'll jump right in to an existing solution so you can get a concept of what the code and the architecture of an application written using DDD practices looks like. Then we'll start digging into the big DDD concepts like modeling problems with a domain, what the various technical components of DDD are, and how you can use DDD to manage complex projects. Throughout the course we'll use the existing solution so you can see how some of this process works. With this in hand we'll walk through extending the sample based on a new request form the client. Since this is a fundamentals course we certainly don't expect to turn you into an expert by the end of it. However, you should be well on your way to understanding the value behind Domain-Driven Design and how some of the practices can be employed to improve your success with complex software projects. Right now, if you're new to DDD, you don't even know what you don't know yet. However, once you're done with this course you'll know more about DDD but of course, you'll also realize how much more there is to learn. That's one of the great things about our industry, the more you know, the more you realize how much more there is you don't know.
-
Eric Evans' Participation in This Course
We're especially grateful to Eric Evans who generously spent some time talking with us about Domain-Driven Design. Throughout this course you'll have the good fortune to view video of Eric sharing his thoughts on some interesting aspects of Domain-Driven Design that we created just for this course. Eric has a great mind and is very thoughtful about how he presents his ideas. If you've never watched him talk about the art of software before, you're in for a treat.
-
Why Should You Care About Domain-Driven Design?
So why should you watch this course? Why should you care about learning Domain-Driven Design? Steve and I have both been designing and developing software for a very long time. Without giving away our ages we've got over 40 years of experience between the two of us. And we've both been very inspired by Domain-Driven Design. In many ways it aligns very naturally with ideas that we've each come to from our own experience. It also takes these ideas and lays them out in a way that's not only illuminating, but it's repeatable. When Eric Evans wrote his book, his goal was to understand what was behind the successes he had achieved with large-scale, complex software projects, and what were the patterns. That's what he laid out in the book. This is why we care about DDD and we hope that you can gain from our experience, which is why we put together this course. DDD provides principles and patterns to help us tackle difficult software problems and even business problems. These are patterns that are being used successfully to solve very complex problems. The more we've learned about DDD, the more we've found these ideas aligned with the approaches we've learned from our many combined years of experience. DDD provides us with a clean representation of the problem in code that we can readily understand and verify through tests. We developers live to code. When starting on a new project we're eager to jump in and start coding so that we can build some software. But you can't build software unless you truly understand the client's needs. DDD places as much emphasis on not only comprehending what your client wants, but working with them as full partners through a project. The ultimate goal isn't to write code, not even to build software, but to solve problems. You need to realize that nobody really wants your program, they want what it can give them. There's a famous saying in sales, people don't want to buy a quarter-inch drill, they want to buy quarter-inch holes. Your client is not interested in building software, but in being successful at their mission. Software provides a more efficient means to this end.
-
High Level View of DDD 1: Interaction With Domain Experts
Domain-Driven Design is for solving complex problems. Evans put a lot of thought into the subtitle of his DDD book and came up with Tackling Complexity in the Heart of Software. But DDD itself is a complex topic. To start with we think it's helpful to look at it from a very high level. We call it the 10,0000 foot view, here in the US, but that's probably 3,048 meters to the rest of you. One of the critical pieces of DDD is to encourage better interaction with domain experts. These are the people who live and breathe the business, or process, or whatever you are targeting with the software you are planning to write. You may be thinking, but we already talked to them? Perhaps, but probably you're using your terms, not theirs, and maybe talking in the language of tables in the database rather than domain concepts. Or you may presume that after some standard requirements gathering you can infer enough about the problem at hand to design the solution on your own. After our own history in the business of developing software we know that that rarely ends well. DDD guides us to engage with the domain experts at much greater length and through much more of the process than many software teams are used to doing.
-
Eric Evans on the Importance of Communication
You really need to cultivate your ability to communicate with business people to free up people's creative modeling, and so on.
-
High Level View of DDD 2: Focus on a SubDomain
Another core theme in DDD is to focus on a single subdomain at a time. Say you're asked to build software for a spaceship manufacturer. They describe their business tasks such as purchasing materials, engineering, managing employees, advertising their spaceships, and share with you their dreams about mass producing spaceships when the market's ready. Each one of these task are, in themselves, a complex subdomain filled with their own specific tasks, terminology, and challenges. And those subdomains may have only minimal interaction between them. Many applications just try to do too many things at once, then adding additional behavior gets more and more difficult and expensive. With DDD you'll divide and conquer. By separating the problem into separate subdomains, each problem can be tackled independently making the problem much easier to solve. This lets us focus on the problem of employee management separately from the problem of sourcing materials for producing the spaceships. The term modeling is important in DDD and refers to how you decipher and design each subdomain. You'll learn much more about this as you progress through the course.
-
High Level View of DDD 3: Implementing the SubDomain
The final theme in our high-level perspective of DDD is writing the code to implement each subdomain. The principle of Separation of Concerns not only plays a critical role in identifying the subdomains, but within each subdomain we use it as well. Many applications spread the domain logic between the persistence layer and the user interface making it much more difficult to test and to keep all of the business logic consistent. DDD applies separation of concerns to help steer you clear of this problem by focusing the domain and not on details like how to persist data into a database, or how to connect to a service in the cloud. Those become implementation details that you can worry about separately. While implementing these subdomains the focus is on the subdomain, the problems of the subdomain you are trying to solve with your software. You don't get bogged down worrying about infrastructure concerns.
-
Benefits of DDD
Domain-Driven Design is a big commitment. While Steve and I have both chosen to leverage pieces of DDD as we learn more about the wider scope, one thing we're both confident about is that it's providing a lot of benefits to our work. Because DDD guides us to focus on small, individual, nearly autonomous pieces of our domain, our process and the resulting software is more flexible. We can easily move or modify the small parts with little or no side effects. It even lets us be more flexible with our project resources as we're building the software. The resulting software also tends to be more closely mapped to the customer's understanding of the problem. DDD gives you a clear and manageable path through a very complex problem. When you look at the code you can see that it's generally well organized and easily tested, and the business logic all lives in one place. Even if you don't use full DDD for a project, there are many patterns and practices that you can use by themselves to benefit your application. So keep watching even if you don't think you'll need all of it. But DDD is not a path for every project. Its real benefit is for complex domains. Even Eric Evans explicitly states that DDD isn't suitable for problems when there's substantial technical complexity, but little business domain complexity. Using DDD is most beneficial when the complexity of the domain makes it challenging for the domain experts to communicate their needs to the software developers. By investing your time and effort into modeling the domain, and coming up with a set of terminology that's understood for each subdomain the process of understanding and solving the problem becomes much simpler and smoother.
-
Drawbacks of DDD
But all this comes at a cost. You'll spend a lot of time talking about the domain and the problems that need to be solved. And you'll spend plenty of time sorting out what is truly domain logic and what is just infrastructure. The easy example there is just data persistence, or for the sake of our spaceship manufacturer, maybe it's how to communicate with an external service that helps to verify that potential buyers are properly vetted for space travel. You'll have a big learning curve as you learn new principles, patterns, and processes, there's no question about that. DDD is a big topic, and gaining expertise from end-to-end is a big commitment. This course doesn't aim to make you an end-to-end expert in DDD, but to give you a big step forward that will allow you to not only comprehend the concepts, but you'll gain a lot of new tools that you can use right away whether or not you choose to dig further. And it's worth restating that DDD is not always the correct path for your applications. And it's helpful to keep in mind some of the scenarios where DDD is just going to be overkill. For example, if you have an application or a subdomain that's just a data-driven app and doesn't need much more than a lot of CRUD logic, there's really no need to use DDD. It would be a waste of time and effort. And be clear about the difference between complexity in your business domain and technical complexity. DDD is designed to help with complex domains. If your domain is simple, even if you have a lot of technical challenges to overcome, DDD still may not be the right path. For example, if you were writing a Tic-tac-toe game for a touchscreen with a new, complex API, the complexity lies in the touch interactions of the two players on the screen. The domain itself is well-known and just comes down to Xs and Os. Getting others to follow the DDD approach can also be a drawback. There may be some politics involved in this decision, it really depends on your team and your organization. We hope that another take-away from this course will be to help you understand the concrete benefits of DDD, which you can show to your coworkers to help convince them.
-
A Mind Map of DDD's Working Parts
This is an image from the Domain-Driven Design book. It's a mind map of what the process of modeling with DDD looks like. Eric Evans refers to this as a navigation map and it lays out all of the pieces of Domain-Driven Design and how they relate to one another. We want you to see it so that you have a concept of the big picture even though in this course we'll spend most of our time on a subset. We will be defining many of these terms later on in the course, so don't panic. We've mentioned modeling the domain and subdomains a few times. Modeling is an intense examination of the problem space. Key to this is working together with the subject matter experts to identify the core domain and other subdomains that you'll be tackling. Another important aspect of modeling is identifying what's called bounded contexts. And within each of these bounded contexts you focus on modeling a particular subdomain. As a result of modeling a bounded context you'll identify entities, value objects, aggregates, domain events, repositories, and more, and how they interact with each other. In the image there's more than just these subdomains, however. For example, there's a concept of an anti-corruption layer, which allows subdomains to communicate with one another from behind their boundaries. The model also has notes for each element such as avoid overinvesting in generic subdomains. That could be something like a credit card verification service that you could choose to use rather than building yourself. Or free teams to go separate ways, this is something that can be accomplished once you've identified the boundaries of each subdomain. As you begin focusing on specific subdomains, another very important DDD concept surfaces driven by the need for clear, concise communication. It's called the ubiquitous language. A simple definition of a ubiquitous language is to come up with terms that'll be commonly used when discussing a particular subdomain. And they will most likely be terms that come from the problem space not the software world. But they have to be agreed upon so that as discussions move forward there is no confusion or misunderstanding created by the terminology used by various members of the team. We invite you to pause this video to look over this map and read the notes associated with the various elements and contemplate what they might mean. We'll revisit this throughout the course and we hope that the map will make more and more sense as you work through the course.
-
Overview of the App Used in This Course
Now we want to switch over and show you a small DDD-based solution that we'll be working on for the rest of the course. This app represents an appointment scheduling system for a veterinary clinic. Let's take a look at it. So for this sample application we decided that we would use a veterinary clinic management system because it has a decent amount of complexity so we can apply some of the DDD principles, but it also gives us an excuse to show off pictures of our pets. And our friends' pets too! We've got lots of pets form other Pluralsight authors in here and they're all so cute. Yeah, we asked a bunch of our friends if they'd let us use their names and pictures. So we've got Corde, here, the Mastiff. We've got Tinkerbell, the Chihuahua. Little Chihuahua. Of course, Julie's got Sampson. My handsome boy! And I've got Darwin, the silly Poodle. So the idea behind this application is that if you're working at the front desk of the vet clinic and someone walks in, they want to schedule an appointment, or the phone rings and someone's on the phone and they need to schedule an appointment for their pet, the first thing you're going to do is look them up in the system. So you click up here and this opens up a search dialog where you can search for the client's name. Here you can see we've got Julie Lerman and her dog, Sampson. We've got Julie Yack and she's got three pets as well. Let's go ahead and look up someone else, we find Peter Mourfield and his dog, Finley. Let's go ahead and choose this particular client. And so now we've chosen Peter and Finley, and we're going to specify ---we just need to find a time slot. And it looks like we can just put them right here at 8:00 a.m. in Room 2 with Dr. Jones. So we'll just double-click. Oh he is too cute! This will add a new appointment, we can say Finley just needs a nail trim and Save that. And now you can see that this new appointment has been created. Now the complexity in this system comes into play when we have to do some checks for certain things. We want to make sure, for instance, that Finley isn't already scheduled in one of the other rooms at this exact same time. We also, maybe, want to send a notification to Peter to let him know that he has this appointment scheduled and perhaps send a notification the day before to remind him or get his confirmation that this appointment is going to take place so that we cut down on how many times we have a client that doesn't show up for their appointment. In addition we have some other parts of this application, so this is the schedule, this is the main focus. But we also need to be able to manage the clients, you know to add additional clients, and manage their pets, and things like that. As well as doctors, this is a clinic that's set up for a number of different doctors so we need to be able to manage their information as well. And we could even add and remove different exam rooms all through the user interface. But these are mostly CRUD, which means we're just talking about adding and removing records without a whole lot of complexity. So we're going to talk about those in sort of a different compartment of the application than the schedule, which has a lot more complexity. Great, so why don't we take a look a little bit at the structure of our solution also. Sure, so if we pull up Visual Studio, we can see we have a solution with 12 projects in it and we've organized it with solution folders. So, each of these folders here is a solution folder, which has one or more projects inside of it. And also we've got the user interface currently set up here, which is called FrontDesk.Web. It's an ASP.NET MVC application so it has controllers and views, and most of the work is being done through services. So when we look in the Controllers, there's just a single home controller and then everything else is being done through web API controllers. So the Domain-Driven Design work that we're doing is not reflected in the web app, it's reflected among the other projects and the web app interacts with those projects. So looking form the bottom up, in our Solution Explorer here, the next thing we have is this idea of a SharedKernel. And we'll talk more about what that means later on in the course, but it includes some pieces that are shared between different parts of the solution. We've also got some SharedDatabaseManagementTools, which again we'll cover later. And we have a simple section for ResourceScheduling, which is just sort of a mocked-up version of some data that we would use to manage like when technicians and doctors are coming and going to the clinic. We don't actually have that implemented at all so there's nothing really in here. But the idea is that we might have these different sections of the application segmented out in a real solution. The ClientPatientManagement is where we're doing that CRUD work of adding new clients, adding new patients, and editing them. So that's just data in/data out so we didn't need Domain-Driven Design for that work so, we're actually writing it in a very different way. And also notice it looks like there's four projects there, but two of those projects are just for tests. So that's pretty simple and we're not even using DDD to do that. It's just much simpler and more straightforward. The big one is the appointment scheduling. And the appointment scheduling is where we have a project called AppointmentScheduling.Core and this is organized following ports and adapters, or onion architecture. Meaning that it doesn't reference anything else in the system except, in this case, our SharedKernel. I describe the onion architecture and compare it to mare traditional layered architectures in my course, Creating N-Tier Applications in C#. Now the core project defines the abstractions that we're going to use throughout the system, as well as the model classes that we use to describe our domain. And we'll talk a lot more about how this is organized and where the logic belongs for this particular project. Yeah, this is the project where we're doing the Domain-Driven Design. So this is the project that we'll be focusing on throughout the course and you'll have a better understanding of why we have all these different parts.
-
Key Takeaways
So as we've talked about creating applications, it's not about writing code even though often that's a really, really fun part for us developers. But it's about solving problems, and the more complex the problems are the more difficult the whole project becomes. So Domain-Driven Design gives us some great patterns and practices for attacking these more complex problems and they get us to really focus on interacting with the domain experts, breaking apart our domain, and working on things in smaller units, and in a very organized fashion. And in the end it gives us a much more efficient and effective path to success in creating our solutions. Yeah, we talked about some of the benefits that Domain-Driven Design provides, as well as some of the drawbacks, specifically, your team just needs to know Domain-Driven Design and your domain experts need to be available to work with you on these systems. Domain-Driven Design is a big topic. We looked at some of the different concepts that are involved in DDD and we're going to look at a lot more of them in depth through this course. But remember that this is just an introduction to Domain-Driven Design so some of these aspects that are a little more advanced we're not going to be able to cover with a great deal of depth.
-
Resources
So in this course we've used material from these books, Domain-Driven Design by Eric Evans being the most important one, along with Jimmy Nilsson's and Vaughn Vernon's books. And you can of course learn more online at DDDCommunity.org. Yeah, that's a great reference, the community website. And when we were looking at the structure of our demo app, remember that we made a reference to Steve's N-Tier Applications course from Pluralsight so here's a reference and a link to that. And that's it for this module. Thank you very much. This is Steve Smith and Julie Lerman, and thanks for watching our course on Domain-Driven Design Fundamentals.
-
DDD: Modeling Problems in Software
Introduction
Hi this is Steve Smith and this is Julie Lerman. Welcome to the second module of our Domain-Driven Design Fundamentals course. This video will focus on Modeling Problems in Software. We've got a lot of great ideas to share with you and you're welcome to reach out to us online. You can find me online at TheDataFarm.com or on Twitter @julielerman. And I'm at Ardalis.com or on Twitter as @ardalis.
-
Goals?
In this module we're going to take a look at how we decompose the model for the veterinary office domain. We'll talk about the importance of domain experts in DDD. We'll drive this point home with a play in which we'll consider a few different scenarios for how the project might have gone, which should provide you with examples of ways to involve the domain expert in the design of the system. Next we'll talk about the domain model and some of the elements that typically are found in this part of the application. It's important to separate the core domain model form related sub-domains and we'll talk about bounded contexts can help us accomplish this separation. And then we'll wrap things up by talking about ubiquitous language and how this seemingly small thing, with a big name, can have a large impact on your model, your design, and of course, your application. So let's get started.
-
Learning About Our Domain by Talking With a Domain Expert
Steve and I both have a love for animals. In fact, Steve's wife, Michelle, is a veterinarian. In thinking about a sample application we could use for this course we wanted to use something complex enough to justify the use of DDD. The veterinary clinic management domain made a lot of sense allowing us to leverage our own experience as pet owners, as well as having a domain expert available in the form of Michelle, or Dr. Smith as we'll be referring to her in the course. There are many different pieces involved in managing a typical veterinary clinic. The staff needs to be able to schedule appointments. They'd likely need to schedule their own working shifts as well. They need to be able to invoice for their services and collect payments, and in many cases send out bills. They'll also need to be able to store and retrieve medical records, as well as work with external labs and specialty clinics. Most veterinary practices also have products for sale and may need to track inventory, as well as sales. And there are often follow-ups and reminders that may need to be sent by mail, phone, or perhaps email. There is certainly sufficient complexity in this domain to merit the use of Domain-Driven Design. Of course, it's a good idea to speak with a domain expert about the system's requirements before diving in and beginning to code a solution. Whether you're tasked with building a full system or just adding a new feature, an overall understanding of the client's business is a critical start. Of course, it's just the beginning. It's also important that you have a continuous conversation with the domain expert throughout the development of the system. The simple system we showed in the last module needs some updates so we're going to share some conversations we had with a domain expert to help validate our initial assumptions. An important part of this conversation is going to be identifying the things that aren't included in the scope of the project or feature. To that end we'll try to identify sub-domains within the overall problem domain and then determine whether or not we need to concern ourselves with these sub-domains at the moment. If not, we can consciously remove them from the scope, with the customer's approval, and avoid confusion and possible missed expectations later. To get started though, we do want to know a little bit about the big picture. As Julie already mentioned, my wife Michelle is a veterinarian. In addition, she has a deep understanding of software development processes having successfully managed software teams at NimblePros and Telerik. She has graciously agreed to play the role of domain expert for our course. In real life she knows quite a bit about software and technology, but for the purposes of this course she's playing the more traditional role of a veterinarian with little background in software development. Hi Dr. Smith, Thanks for your time today. Julie and I would like to learn more about what goes on in your veterinary clinic. Can you share some of the big picture processes involved in the day-to-day operation of a clinic? So the biggest thing is probably scheduling patients and keeping track of them once they arrive. Clients will usually call ahead unless it's an emergency, and then we need to get them entered into our system. Of course, surgical procedures need to be scheduled in advance. And when they're here we need to record information about the patient, our observations, notes, and diagnoses. Wow, that's quite a list, probably not what you were dreaming about when you started vet school. So many of these are all secondary to the core reason for being a vet, keeping pets healthy and I think it sets you apart from other businesses that have to manage clients and schedule appointments, but you can't run a business without it. Is that all? So when the appointment is over they also have to pay. So most of the time that's done immediately, but we do have some billing that's done after the fact. And when they're checking out they may need to buy some things for their pets, toys, or prescriptions, or maybe some prescription food as well. And we need to track all of that as well. For some of the lab work we need to send that out and get the results back. And some prescriptions go out to outside pharmacies as well. So we need to manage all of those through the system. Okay, so payments, billing, point of sale, labs, prescriptions. Anything else? I think that's about it. Oh, we also use the system to note which staff members are working when. And right now our website isn't integrated into the system at all, but we were thinking it'd be great if clients could view information about their pets, maybe schedule appointments, look up prescriptions, and we could make updates to the site without having to go through our computer contractor. Okay, great. So we'll add staff scheduling and content management to the list. I don't want to assume you know what a content management system is, we also call it a CMS, you might've heard that. It's a type of software system that lets the owner, that's you, be in charge of the information that's displayed. A blog is a really good example of a CMS that can be managed by its owner. I have a blog so I understand exactly what you mean. Something like that would be really great for us to have so we can make updates right in house. But it's kind of like a blog, especially something that's more professional than my personal blog. Cool, so I think that's probably enough of a big picture view for us to consider at the moment. Now let's try and think about which of these are connected to the others so we can determine which ones we need to worry about for our application's needs.
-
Breaking the Domain Into Sub-domains
We started with this fairly complicated view of the overall problem domain. But, now we've segregated these into different areas and we know which ones we need to focus on right now, and which ones we can treat as external collaborators. Determining where we can safely draw the line between what problem our immediate application needs to solve and what is outside of its area is certainly helpful. It's also important that this be well understood and communicated among everyone involved in the project. Speaking of communication, Steve, remember when we were first getting started with this application? It took some time for us to get on the same page as the customer on a few different concepts. Oh, I remember. Especially talking about what we meant by classes. We were talking about software classes and she thought we were talking about behavior classes. In fact, I think it's time we shared that conversation.
-
Focusing on One Sub-domain With the Domain Expert
Hi guys, welcome back to the clinic. How are things going with the computer system? We're making good progress and now we're ready to look at another, more complex feature. We know there's a lot that goes on here, but today we want to focus on appointment scheduling because we realize we're still a little confused about it. Since we've both owned pets for a long time we figure we probably have a rough idea of what's needed, but it will be good to talk through it with you. Do your patients usually schedule their appointments over the phone? Okay, so ummm, yeah, our patients aren't usually involved in the scheduling. Usually it's the clients that call in for appointments for their pets, and yeah it's usually on the phone or in person when they're checking out after an office visit. Julie and I talked about that earlier. Yeah, so Steve, the patients are the animals and the clients are the people, or the pet owners. Right, right, of course, that'll be important to get right. Remember we talked about that? So, the client needs to make an appointment for their pet. They'll talk to a staff member who will schedule the appointment. What kind of information do they need in order to do that? So that really depends on the type of appointment. It could be an office visit or it could be a surgery. Why don't we talk about the office visits first? If it's just for a wellness exam, that's pretty standard, they just need to choose an available time slot with one of the doctors. Some of the visits can be scheduled with just a technician though, so if they need just their toenails trimmed, for example. Or painted, like Sampson, he gets his toenails painted. Does he really? No, I'm joking, I just want to. Pink. I'm sure he'd love that. Okay, so office visits might be an exam requiring the doctor or another kind of appointment that only requires a technician. Right, we also have to worry about our rooms too. We only have five exam rooms available and we try not to overbook. We don't like for our clients to have to wait too long in the reception area. Especially if we have a lot of cats and big dogs out there at the same time, it makes them all really nervous. What about other staff? So our technicians will float between the exam rooms and other areas of the clinic as needed except, of course, for those scheduled technician visits. We do have a schedule for the staff, but it's separate from how we schedule our appointments. Okay, so what about the surgeries? Well, if it's a surgery, those are only scheduled on certain days and they require that the operating room be available, as well as some recovery space in the kennel area. It also depends on what kind of surgery or procedure we're going to be doing. Something simple like a dental cleaning takes less time and fewer people than a Cesarean section for a bulldog. Okay, so an appointment is either an office visit, or a surgery. Office visits happen in the exam room, surgeries require the operating room and recovery space, is that right? Right and depending on the reason for the visit or the surgery, different staff might need to be involved. Hmm, so we'll probably want to have separate classes for appointments and surgeries. Classes? No we refer our clients to obedience and puppy preschool classes at other facilities. We don't actually schedule any of those in the clinic themselves. Oh, I'm sorry, that's a software term. In software we have different classifications of concepts in the program, which are called classes. I'm just getting ahead of myself here, sorry. Don't worry we're not going to make you learn our software terms. Steve and I will try to have a little bit more self control with that. We do want to make sure we're all speaking the same language when it comes to concepts in the application though. Okay, so I have another quick question. Do we have to worry about multiple staff members scheduling appointments at the same time? No, there should only ever be one person doing the scheduling at a time. Although I could see if we grew in the future that could change. But I don't think that will happen in the next couple of years. Okay, then we don't have to worry about the rare occurrence of two people creating a conflict if they're trying to schedule an appointment for different patients in the same room or with the same doctor. That'll keep things a lot simpler. And we need to know before an appointment if certain resources are available like rooms and doctors. And then if they are and we want to schedule the appointment, then we need to be able to book the doctor, the room, and any other resources. Hey, is it okay if we refer to doctors as resources? Sure, that makes sense. I think it makes sense to use the term resources to refer to the doctors, the rooms, and the technicians since those are all things that can affect whether or not an appointment can be scheduled. But remember sometimes it'll be just a vet tech in a room, and other times it might be the doctor in the room, but sometimes you might need the doctor, the technician, and a room. Wow, this is a lot more complicated than we'd realized, but it's interesting, this is going to be cool to model in the application. Yeah, I never really thought about the details of how we do some of these things since it's just something we do and we don't really think about it. Being more explicit about what the rules are that determine how we do our scheduling could help us avoid some of the occasional scheduling problems we've had. This is going to be great! Building software is hard. One of my favorite sayings is, as software developers, we fail in two ways, we build the thing wrong, or we build the wrong thing. By making sure we understand what the customer needs and, of course, working closely with the customer throughout the development process we can dramatically reduce the likelihood of the second kind of failure, which is much harder to fix typically. Hey Steve, I like the way you quote yourself here, but it really is a great quote.
-
First High-level Model of the Sub-domain
So after talking to Dr. Smith about how appointments work we've identified a few high-level elements of her model. The central concept in this application seems to be the appointment itself. Typically an appointment is scheduled by a client for a patient. Booking an appointment often requires an exam room and a doctor, but may involve other resources. Appointments might be for office visits or vaccinations. Or they might be surgeries, which are a separate kind of thing entirely with their own rules, which involve different kinds of procedures. Surgeries require different resources too, like operating rooms and recovery rooms. That's a pretty good high-level view of the model we have so far for the appointment management part of our application. I think it's worth noting that some of the concerns of this application are going to also play a part in other sub-domains. For instance, I'm pretty sure we're also going to be working with clients and patients in a lot of the different parts of this application. Yeah, I think it's time we introduced the idea of bounded contexts.
-
Creating a Bounded Context
As you develop your model, remember to identify its bounded context. That is, where is this model valid? If you don't put boundaries around your model, eventually pieces of it will be used where they don't fit. Concepts that make sense in one part of the application may not make sense in another. Even if they have the same name and sometimes even if they literally refer to the same thing. For example, as we built out the appointment scheduling portion of the system we needed to know some very basic information about clients. But in the context of appointment scheduling these are very simple concepts with little behavior beyond their names. However, in the billing context we'll want to include contact and payment information for clients. But that's information we don't care about back in the appointment scheduling context. If we try to reuse this same exact client model in multiple places it's likely to cause inconsistent behavior in our system. That's right. For instance, we might decide to include some form of validation on clients to insure we have enough information to bill them. If we're not careful, that validation might inadvertently prevent us from being able to use clients to schedule appointments. Which certainly isn't the desired behavior. Maybe the billing system requires that clients have a valid credit card in order to save changes for them. But it wouldn't make sense for a lack of a credit card to prevent us from saving an appointment for a client in the appointment scheduling system. In this example we have two contexts, but the boundaries between them are blurred and overlapping. Eric Evans notes that models are only valid within specific contexts, therefore it's best to explicitly define the context within which a model applies. We should be able to avoid compromising the model within this context, keeping it strictly consistent within these bounds and avoiding distractions or confusion from outside issues. Once we explicitly define our bounded contexts we can easily see whether or not we have elements of our model that are trying to span multiple contexts. In this example we'd want to keep a simple view of a client in the appointment scheduling app and a richer version of the client with contact and billing information in the billing context. We would define these two views of a client in two separate classes and they will most likely live in separate applications. In fact, Evans recommends that bounded contexts maintain their separation by giving each context its own team, codebase, ands database schema. While this is ideal, in many real world apps we need to work on systems where this level of separation is not present usually due to resource constraints or for political reasons within the organization. Remember though, if you have multiple contexts you'll want to keep them bounded. And one way to maintain this separation is to keep their data, code, and team members distinct from one another. Although in real world I've never seen something with that level of separation. Yeah, but I think even if it's not possible to literally do that with your company and your team just having that concept in mind really helps, in your brain, have that idea of separation. I agree, I know that just thinking about the fact that these things ought to be separated and trying to figure out a way to do it means that even if you can't get to the ultimate level where everything is completely separate, you can still introduce separations through things like namespaces, separate folders, separate projects, anything you can do to make it clear that these are different contexts that shouldn't be sharing too much information. You know I think that's also a really important point about this course, in general, and DDD, in general. For me, it's really hard to think of all of these things we're learning as hard and fast rules, like you have to do it this way or you're not doing it right. I like to see all of this as really good guidance so it helps me keep my eye on the prize. And when there's places where I can't truly achieve exactly what DDD kind of directs me to do you know, I'm using my own experience, my own intelligence, to make decisions about how to do things and I'm letting DDD guide me in a lot of scenarios. Sure, and some of these ideals I think of like, 100 percent test coverage. It's almost impossible in most real world applications to achieve 100 percent test coverage. But just because that ideal is not something you can ever achieve doesn't mean that you shouldn't strive for more test coverage. Yeah, yeah, totally, totally agree with that. (Pause)
-
Difference Between Sub-domain and Bounded Context
Alright so the difference between a sub-domain and a bounded context, in one sense they're just totally different things, but they get confused a lot because they closely correspond to each other. So one way of I've heard it described is that sub-domain is kind of a problem space concept and the bounded context is a solution space context. In other words, the sub-domain is describing something about the way you've chosen to break down your business, or whatever the domain activity is, whereas the boundary context describes how the software and the software development has been broken down. Now, we would like those two things to correspond so a lot of times we find that they look a lot alike. One analogy that I tried on some people once that seemed to help was if you have a room that has wall-to-wall carpeting, you have a floor in that room, and you have a carpet in that room. And they have exactly the same shape and size, and yet no one gets confused and says, well, I don't understand the difference between the floor and the carpet. That is a case where someone took a carpet and they shaped it to fit the floor. That would be like a bounded context that had been shaped to fit the sub-domain. On the other hand, if you said, well, I'm going on the cheap, I'm going to take an old, used piece of carpet and put it in this room. It might not exactly fit. You might have a little gap, which you could hide under a table, you know, where the carpet had been cut in a way that wasn't good for this room. Maybe there's a part where it's a little too big you could try to cut it off, or maybe just fold it under, hide it with some more furniture. That's like a Legacy system after the business has changed a bit or the way we want to do the business has changed a bit. So really they're just two different things, but it does get confusing because in a relatively fresh solution they tend to correspond very closely.
-
Understanding Context Maps
If your organization has multiple bounded contexts, and ideally these are separated, there can be confusion when the different teams are talking to one another. Again, DDD focuses at least as much on effective communication as it does on anything specifically related to the code we produce. Evans recommends using context maps to visualize and demonstrate to all teams where the boundaries between their contexts lie. Think about a complex topographical map, it will frequently include a legend, like the one shown here, in order to explain what each of the lines and symbols on the map mean. However, this legend is only valid within the context of the map with which it appears. Trying to use this legend on another map would be confusing at best. A good first step for an existing application is to create a map that shows how things are. Remember that the names of your contexts are also important as you'll refer to them frequently when discussing different parts of the application. It may be that things are not as separate as they should be and that's worth noting. If you have separate teams responsible for different contexts that share resources, it's important that each team understands which aspects of the application they can change on their own, and which are shared dependencies they'll need to coordinate with other teams to avoid breaking things. If we look at these two sets of concepts we can see some obvious overlap. For one thing, Client appears in both contexts, but we know that for appointment scheduling we really only care about the client's name. Where as in the billing system they'll want additional information like address and payment details. However, although the details involved vary, we know that Mr. Jones, the client on the left, is the same actual person as Mr. Jones, the client on the right. However, we also have a concept of notifications on both sides and in this case they're referring to different things. On the left we're talking about setting a notification when an appointment is booked, as a reminder. And on the right, we're talking about notifying the client that their payment was received or perhaps that it's past due. Especially in smaller organizations, it's common to have one team responsible for several contexts of the same overall application. In such cases, it's also common for the team to use a single code base for the bounded context that they're working with and store it in a single repository such as this GitHub repository. Usually there will also be a shared database. As we've already noted this is not ideal since it makes it much more difficult to maintain the boundaries between the separate contexts. Part of creating a context map involves explicitly identifying its boundaries. If we try to draw the boundaries around these two bounded contexts we can see there are now several resources that belong to each bounded context. This isn't ideal if the two contexts really are meant to be kept separate. In the ideal case for a large complex system, we would have bounded contexts like these with their own teams, code bases, and database. For instance, on the left we have an appointment scheduler application, it's being worked on by Team Awesome and they're storing all of their code in their own repository called vet-appt-sched. And of course, this application has its own database. This team is free to change anything they want with their model or any other part of their system, without worrying about breaking anything outside of the boundaries for the team on the right, which is working on a billing system. And their team has decided to call themselves Team Ultimate, store their code in a repository called vet-billing, and of course, using their own database. By having this separation this can greatly increase team velocity and reduce integration bugs. Of course, you're probably wondering how the two systems will interoperate. There are a number of patterns that can be applied to enable this kind of integration. We won't be covering all of them in this course, but one question that frequently comes up is how to share cross-cutting concerns like authentication. For this scenario a common approach is to designate these shared concepts, or resources, as what we call a Shared Kernel. Team Awesome and Team Ultimate agreed to share the subset of the domain model. Since they're sharing it they also agree not to change it without coordinating with the other team first. Frequently the Shared Kernel will either be a part of the customer's core domain or some set of generic sub-domains, or even both. Though it could be any part of the model that both teams require. Using a Shred Kernel is a trade-off between code reuse and consistency, and the overhead involved in integrating changes to the Shared Kernel across multiple teams and bounded contexts. It works best when the shared model is relatively stable.
-
Eric Evans on Clearly Defining Context Boundaries
Well, there are a lot, DDD is not an easy thing to pull off whether you're new to it or not. I would say that I've seen stumbling blocks of all sorts. The most common, probably, is not having a clear enough context boundary so that as they're doing their work the work where they're trying to apply DDD isn't clearly separated from work that's being done in a different way by some kind of a clear, agreed upon boundary. If you're in a free-for-all in a big company where stuff just gets changed all over the place, you share your database and it gets updated by hundreds of different processes and so on. It's very hard to create the kind of models that we're talking about here and then actually write software that does anything interesting with those models. The bounded context is just an essential ingredient so I would say that is probably the biggest stumbling block. And it's not one that an individual on a project can usually address by themselves, it kind of has to be dealt with at the team level, organization level. That's a big one.
-
Bounded Contexts in Our Application
In our application we've organized a solution to make it clear where the boundaries are between our contexts. As we work on the application it's immediately clear to us which area we're focused on based on the solution folders that we've set up. The main area that we're currently focused on is the AppointmentScheduling bounded context. We've identified two other bounded contexts that are involved in the overall application, or will be eventually. For instance, it'll be important for users to be able to manage clients and their patients. The staff of the clinic also needs a way to manage their schedules so they know who's working on different days. We're referring to these two bounded contexts as ClientPatientManagement and ResourceScheduling. We also have a few parts of the application that are common to several bounded contexts. These are Cross-Cutting Concerns that we have consciously chosen to share. In DDD this is referred to as a Shared Kernel and thus we have a solution folder called SharedKernel where we keep such code. And it's worth noting that a bounded context does not always mean a separate application even though we've identified several different bounded contexts. In this case they all exist within the same application. When this is required it's a good idea to make it very clear where the boundaries are between the different contexts, which is why we set up our solution the way you see it here.
-
The Ubiquitous Language of a Bounded Context
We've mentioned already that an important part of DDD is an emphasis on effective communication among the stakeholders in the project. And remember, if you're a programmer, count yourself as one of the stakeholders in whatever you're working on since you certainly have a stake in its success. The language we use to describe the problem and how we choose to model it is key to the shared understanding we want to have with our domain experts in order to be successful. Having a single, shared Ubiquitous Language helps avoid unnecessary confusion and translation within the team building the software and is one of the fundamental practices of Domain-Driven Design. And when I talk about the team building the software, I don't just mean the programmers, I mean the whole team including the business people that are driving what the software should do. The discovery of the Rosetta Stone allowed us to unlock several different languages by showing the same message in three different texts. We don't want to have to have a Rosetta Stone or any other sort of tool to help us translate between what the business is talking about and what the programmers are talking about. We want to make sure that everyone is speaking the same language the whole time so that translation is unnecessary. Think about if you've ever used an online translation tool to roundtrip a sentence. You can run into similar communication issues in your application if you're constantly having to translate to and from the domain expert terms, or the programmer's terms. Here's an example of a user story for our sample system about creating appointments. Steve works for a Bulgarian company so he suggested we use Bulgarian for our translation. We use a website to translate the text to and from Bulgarian and even some other languages in between, just for fun, and then finally back into English where we get, Technical Federal knows when to visit the patient. Obviously the meaning's been lost in translation, but the point here, of course, isn't just relating to different international languages, but to the different languages spoken by business experts and programmers. Incidentally, a great practice when you're discussing your system requirements with customers is to always try and explain back to them what you think it is they want the system to do. So they have an opportunity to correct your understanding of what they think they just told you. Definitely. Remember one of the key benefits of using a ubiquitous language is that all parties can understand it without having to perform any translation. This means when you show a test or some code to a domain expert, you don't have to explain that in the system you call something an animal when the domain expert calls it a patient. Evans cautions that a project faces serious problems when its language is fractured. When domain experts stick to using their jargon while the technical team members have their own language tuned to discussing the domain terms in a design, translation errors will manifest as software bugs. Neither the customers nor the developers' language is sufficient for all parties to discuss the application effectively. What is needed is a shared, common language that incorporates and uses terms defined in the domain model. The use of this language must be ubiquitous, and the more the language is used the more it will force deficiencies in the model into the open. And by ubiquitous we mean it must be used everywhere within the bounded context. The vocabulary of the language includes the names of model classes and prominent operations. The team should use these common terms in code, in diagrams, on the whiteboard, in design documents, and especially when discussing the system. Yeah, pretty much, ubiquitous means everywhere, all the time, even in that one email you're sending off to another developer. Stick to using the terms that you've agreed make sense for this bounded context.
-
Working on a Ubiquitous Language With the Domain Expert
You've heard some of our conversations that helped lead to a ubiquitous language for the scheduling app. There was another important one that happened early on between Michelle and me that we want to share with you now. Pay attention to not only the clarification of the terms, but also to the fact that Julie and Michelle are equal partners in this conversation. Although Julie is trying to lead the conversation towards the goal of identifying the correct terms, she's careful not to make assumptions about Michelle's domain. So Michelle, last time you and Steve and I got together to talk, Steve and I came away and we've been working on just kind of fleshing things out and planning things, and I realize that we had some confusion over some of the common terms. Like things that as real pet owners we would kind of assume the terms are, but then when we're thinking about the business and software, we're thinking of the terms a little differently. So I was wondering if we could just sort that out with you so that we're all on the same page, and using the same terms, and using terms that none of us have to stop and think about what we're talking about. We'll always know what they mean. Sure. The first thing is we have these Clients, those are the people who own the pets. So when thinking about the software and business you think of them as clients, but kind of in the real world. And me, you know I have a pet, I go to the vet all the time. I think of myself as a pet owner. So what do you guys refer to those people who bring the pets, pay their bills, call and make the appointments, etc? Most of the time those would be listed as clients. Okay, so you do call them clients, you don't worry about calling them owners. Yeah. And of course, you know, it sounds kind of weird to say I own a dog, right? He kind of owns you. Yeah, that's more like it. You're the pro, you know. So then what about that dog? Are they patients? Are they Pets? Are they Clients? What do you refer to the pets as? So for the purposes of the medical record we refer to them as the patient. Okay, so it'd be Client and Patient? Exactly. And actually in veterinary medicine they talk a lot about this triad, the Veterinarian-Client-Patient relationship, where all three are really important in that. Okay, so those are actually terms that are commonly used in your industry? Yes. That sounds so weird, but you know, but that's cool. Alright, so the next one we were also going back and forth on was an Appointment or an Office Visit? So do you have a way of, you know when somebody's scheduling a visit, scheduling to come in, how do you refer to that? So there would be two big subsets of what they might be scheduling to come in for. They might schedule a surgery, which is an easy one to define. You know there's going to come in, we're going to do some sort of a procedure, usually there's going to be some anesthetic involved. That would be a surgery, and that would be outside of our normal office hours. Oh okay, so what about when they just come in for regular stuff? So when they come in for regular appointments, you could call those Office Vists or Appointments. And there are a few different subsets of those. You may have an appointment that's a wellness exam, and in that exam we would be doing of course, a physical examination and generally wellness treatments like vaccinations, some blood work, generally your healthy pet who's coming in for a routine check over. So that's an Office Visit and there's a couple other things that come under the umbrella of office visit, but I'm also thinking about scheduling because that's the thing we're really going to be focused on is the scheduling portion of the app. So we're always scheduling an appointment, an appointment for a surgery, an appointment for an office visit, whatever type of office visit that is, so is using the term Appointment, does that make sense? If I said appointment, would you think ok, that could be a surgery, that could be a check-up, that could be whatever. This thing to be scheduled is what I want to define. I think you could call them all appointments, but I would differentiate between a surgery and something that's done in the office. But then I would further differentiate in the office between a wellness exam, an exam for somebody that's coming in with a problem, or an exam that doesn't need to see a doctor, but could just be done by a technician like a toenail trim. Oh good, yeah, we always need those. Clickety-clack on the floors. Alright, so, I think then we'll use just the overall umbrella of we'll schedule an Appointment, and then we'll be more explicit about what type of an appointment that's going to be. Would that feel okay to you? Yeah, that makes sense to me. Great, excellent! Alright so I'll get back to Steve and then we've got another meeting set up, I think in a few days, to just hash out some more details after Steve and I have gotten some more of our ducks in a row. Sounds great! Excellent, thanks Michelle. Bye bye. Thank you, bye.
-
Glossary of Terms From This Module
Alright so we've covered quite a few concepts in this module. One of the most important ones is just understanding the Problem Domain and the overall thing your software is working within. And breaking things apart. I know that when I started out I had a really hard time really understanding differences between the Core Domain, the Sub-Domains, and the Bounded Contexts, especially the Sub-Domains and the Bounded Context because at first glance they looked like the same thing to me. Sure, it's really easy to have an application where you have some kind of a concept, like a customer, that you know is used by every system that your organization uses, and it ends up becoming this god-object in your database and in your different applications. Where any given application might only care about a tiny subset of that concept. Yeah, so for me, I think the most important thing is really focusing on the Bounded Context. Getting down to that and understanding about the boundaries. One thing that helps me a lot is just stating within the context of this, and then suddenly it's like, oh right, that's what a context is. It's not like some mysterious new term that Eric Evans invented, he just is leveraging what makes sense within the context of appointment scheduling, this is what a client looks like. Within the context of billing, this is what a client looks like. Sure, I think that makes a lot of sense and it's valuable even when you have an application, like a legacy application, that wasn't built with Domain-Driven Design. Let's go ahead and look at some more terms here. Like for instance, we've got what you were just talking about, I think of as Context Mapping. And even in a legacy application it can be valuable to kind of map out what are all the concepts in this application and where are the overlaps with different sub-domains that maybe we haven't even defined in this legacy application. Yeah, even if you're not planning on making huge changes to it, it's still really, really helpful to just kind of update your perspective on things. Sometimes it just leads to new understandings. I think the Shared Kernel is a really important part of this too because in almost every real-world organization I've worked with there's different types of cross-cutting concerns. And we talked about one of them being the authentication piece. Sure. And that's definitely a really common one, but there's usually others, too, that you want to share. And again it's another one of those things that sounds like it might be a big, scary, mysterious thing because you haven't referred to it that way, but if you really just start out thinking of it as the common stuff. Right, but then I think one of the important things though is, even within the context of Domain-Driven Design we have a ubiquitous language because if I say common you might have a different idea of what I mean by common. But if I say Shared Kernel we've got an agreed upon understanding of what we're talking about there. So, at first I really kind of pushed back against using these terms because I felt like a lot of the DDD experts were just throwing them around all the time. And then I started really getting a better understanding of why it's important to use those terms, it's about you know, it's the ubiquitous language of Domain-Driven Design, so everybody's on the same page. Yeah, I do agree that that's an important part of learning about DDD and other areas of software development like for instance design patterns. These things give us these terms that we can use that are very, very, dense. Yeah. If we talk about Shared Kernel it would take me three or four sentences to describe what I meant by that, but in those two words you know exactly what I mean, just like if I talk about using a strategy design pattern. That is much easier to convey than if I were to try and describe it with words and, you know, have to draw a UML diagram just to say what I mean. And it's the same, again, with ubiquitous language because now I really have a better understanding of the fact that what it means is the language is ubiquitous throughout a particular bounded context. When we're talking about a scheduling app we're going to use these terms all the way through. Like you were saying before, we use it not just when we're talking to the domain expert, but in our class names, in our methods, it's just ubiquitous throughout all of the pieces of the things that are involved in that bounded context, from one end all the way to the other of it. Yeah, and I think as we'll see when we look at the code again, some of the constructs in .NET, like namespaces, are really appropriate to ubiquitous language. Because when you prefix that same term in your code with a particular namespace that tells all the other programmers that if I say SchedulingApp.Notification we know that that has a different meaning than if I'm talking about an EmailReminder.Notification. Right, or SchedulingApp.Client versus Billing.Client. Exactly. Alright, so, let's wrap this one up and then we can move on to the next module.
-
A Quick Review
In this module we learned about our domain. In this case a veterinary practice. We talked about it at length with a real, live domain expert and identified the core elements of our domain model. We identified a variety of sub-domains and focused in on the key area that we would be addressing first with our application. We spent some time designing the system based on our conversations with Michelle, identifying boundaries between different contexts, and noting how sometimes the same object with the same name might mean something different within a different context. We got a lot of great advice from Eric Evans about bounded contexts. Finally we talked about the importance of communication, in general, and in particular, having a ubiquitous language. We know that Domain-Driven Design can help us avoid many design errors and wasted time miscommunicating as we work on a complex project.
-
Resources
Here again are some references you can take a look at for more information about DDD. Again, Eric Evans' and Vaughn Vernon's books, the DDDCommunity site, also you'll find at InfoQ.com there is a lot of great content about Domain-Driven Design. And DomainLanguage.com is Eric Evans' website. Thanks again for watching our course on Domain-Driven Design Fundamentals.
-
Elements of a Domain Model
Introduction
Hello this is Julie Lerman. And this is Steve Smith. In this module we're going to focus on organizing our bounded contexts using key Domain-Driven Design patterns. You can find me online at Ardalis.com or on Twitter as @ardalis. And you can find me online at TheDataFarm.com or on Twitter at @julielerman. Let's get started.
-
Goals
In this module we'll focus on the technical aspects involved when modeling a bounded context. We use these terms while modeling and these same terms refer to patterns we'll use when we code. The concepts flow through the entire process, which is great, you don't have to keep switching hats or mindsets. We'll start by grounding ourselves in the domain and understand why it's important to stay focused there. DDD models are driven by behaviors, not classes and properties. This is another very cool shift in thinking for those of us who have always focused on objects. Then you'll learn about some terms used to describe domain models, rich and anemic. You'll learn what the terms mean at a conceptual level and what the code that they're describing looks like. Entities are the key types in your system, but not every type in your system is an entity. You'll learn how entities fit into DDD, how they're related to one another with associations, and about how to use value objects when a type doesn't need to be an entity. Finally we'll talk about domain services and how you can use them as a place to put logic that just doesn't logically belong in any of your entities or value objects.
-
The Importance of Understanding DDD Terminology
Domain-Driven Design is filled with lots of specific terms, much like the ubiquitous language that we use to make it easier to communicate while working within a bounded context. Understanding and using the terms of DDD makes it easier to talk about the process. We'll spend the bulk of this module focusing on some of the concepts behind modeling bounded contexts. Concepts that are critical to this process, but unfortunately often misunderstood. I've definitely had my challenges with some of the DDD concepts. Some of my issues were because the terms overlap with other technologies I use. For example, I do a lot of work with Microsoft's ORM called Entity Framework. Entities are a key element in Entity Framework, and they're also a key element in DDD. So I thought my understanding of entities from Entity Framework was enough to translate to DDD entities. But it really wasn't and my less than solid grasp on DDD entities caused problems when I was trying model domains and implement the model in code. We also have the concept of a Context in Entity Framework, either ObjectContext or DbContext. While the real goal of that context is to provide interaction with the database, it also does provide a boundary around a model, but it's very different than the concept of a bounded context. And that definitely confused me for awhile. Another important element in a DDD model is value objects. These got me pretty confused at first, and my ego was saved by discovering that others have also been confused by value objects. But I've worked on my DDD education and sorted these problems out. So in this module it's really important to both Steve and I, that you start off on the right foot, with a proper understanding of entities, value objects, and some of the other DDD puzzle pieces. So that Domain-Driven Design can help you with your complex problems, not complicate them even more. (Pause)
-
Focus on the Domain
It's important to remember that first D in DDD stands for Domain. And yeah, there are the other two Ds, Driven and Design, but we really want to focus on Domain here. By now you've probably heard us talk about this plenty, but both Julie and I find that we constantly have to remind ourselves to focus on the domain. We hear ourselves begin to talk about the user interaction, with the app and have to ask, well, what part of the vet clinic domain is this user? Yeah, obviously we care about the user and how the actual application will work from their perspective, but that's for another conversation. And we have to draw ourselves back to focusing on modeling the domain. I have quite a long history with data access and I catch myself worrying about how our domain model will translate to the database so that things definitely get persisted correctly. That's when Steve needs to give me that look, you're doing it again Julie. And I have to bring my focus back to the domain of the vet clinic again. So while it may seem redundant to harp on Domain, Domain, Domain, this diligent focus will help you avoid the complications and distractions that come from thinking outside of the domain or the subdomain that you're working on. Here is an important quote from Eric Evans' book about this focus on the domain. The Domain Layer is responsible for representing concepts of the business, information about the business situation, and business rules. State that reflects the business situation is controlled and used here, even though the technical details of storing it are delegated to the infrastructure. This layer of the domain is the heart of business software. Just to reiterate, the domain model is the heart of the business software. This is the whole point behind Domain-Driven Design, focus on the domain not the technical details of how the software will work. In a typical database driven app, we're used to focusing on properties, or attributes of classes. Our apps sometimes become all about editing property values and the state of our objects. However, when we are modeling a domain we need to focus on the behaviors of that domain not simply about changing the state of objects. Michelle didn't talk to us about setting the name of a dog, or editing the time of an appointment. She told us that she needs to schedule an appointment, and when she does that she needs to book a room, and create a schedule item on a doctor's calendar as well. So scheduling an appointment is a lot more than setting the attributes of the objects involved, the appointment time and identity of the pet we're making the appointment for. We're talking instead about how the system behaves. In response to scheduling an appointment the system should also book a room and do something to the calendars of the doctor and any vet techs that might be involved.
-
Anemic and Rich Models
In order to understand the difference between design that's focused on attributes versus design focused on behaviors, it will help to understand two commonly used terms in Domain-Driven Design, Anemic Domain Models, and Rich Domain Models. An Anemic Domain Model is a domain model that is focused on the state of its objects, which is the antithesis of DDD. While the term is somewhat negative, indicating a deficiency, you don't need to perceive it that way. There's nothing wrong with anemic classes when all you need to do is some CRUD logic. But if you are creating a domain model, you've already made the decision that your domain is too complex for simple CRUD. So anemia, in a domain model, is considered an anti-pattern. Martin Fowler writes about anemic domain models with such drama that you may never mistakenly use them in your domain model. He says the basic symptom of an Anemic Domain Model is that at first blush it looks like the real thing. There are objects, many named after the nouns in the domain space, and these objects are connected with the rich relationships and structure that true domain models have. The catch comes when you look at the behavior and you realize that there is hardly any behavior on these objects, making them little more than bags of getters and setters. Indeed often these models come with design rules that say you are not to put any domain logic in the domain objects. Instead there are a set of service objects which capture all the domain logic. These services live on top of the domain model and use the domain model for data. What we aim for then is Rich Domain Models, not Anemic Domain Models, when we are modeling our domain. Rich Domain Models will represent the behaviors and business logic of your domain. Classes that simply affect state are considered an anti-pattern in a domain model and therefore get the nasty label of anemic even though they are perfectly fine in a CRUD model. While Martin Fowler and other DDDers have strong words to say about Anemic Domain Models, we'd like to share a gentler message, which is to strive for Rich Domain Models and have an awareness of the strengths and weaknesses of those that are not so rich.
-
Entities in DDD and in Our Bounded Context
Even though a DDD app is driven by behavior we still need objects. DDD expresses two types of objects, those which are defined by an identity and those which are defined by their values. We'll focus first on the objects that are defined by their identity. These objects are called entities. An entity is something we need to be able track, locate, retrieve, and store, and we do that with an identity key. Its properties may change so we can't use its properties to identify the object. If you've done any type of data persistence in software you're probably pretty familiar with entities and their keys. When we are modeling a problem we can talk about entities without having to think about how they are implemented in the resulting software. But when it is time to start coding there are patterns to follow to ensure that these objects have the technical attributes of Domain-Driven Design entities. As you can see from this section of the DDD navigation map, entities are pretty integral to our software. So before we can learn about these other elements, domain events, repositories, factories, and more you should have a very good understanding of an entity. The most important entity in our model is Appointment. This is what we will be creating, editing, and retrieving in the context of scheduling appointments. Appointment inherits from a base class we created called Entity, which provides an ID property though you can't see it in a diagram. We'll look at that more in just a bit. Notice that all of the classes shown here are inheriting from the identity base class. However, although the other classes are entities, after our discussions with Michelle, we came to the conclusion that we would like to have a separate utility for managing client and patient information, and another separate app to manage information about staff, and staff scheduling. Thus we don't need very much information or behavior related to these collaborating entities within the bounded context of appointment scheduling. Furthermore, we determined that managing the client, patient, and staff information, which is external to this model, was well suited to just simple CRUD. We didn't identify complex rules or behaviors for creating and editing that data. Thus the concepts of doctors, rooms, clients, and patients are managed outside of the scheduling bounded context. For comparison look at the CRUD classes for patient and client in the other bounded context. They are very simple, they don't inherit from our entity base class, and most interestingly they're ID properties are integers. We'll let the database assign the IDs when we create these classes. So these classes are not designed using Domain-Driven Design. Now let's go back to the appointment scheduling context. The Client, Patient, Doctor, and Room classes here are completely different from the CRUD classes we just saw. However they do have a subset of the same fields from those CRUD classes. All we need to know about these objects when we're scheduling is their IDs, their names, and maybe a few other details. But here they're simply used as look-up data and they're read-only. We determined that with Michelle as a rule for the scheduling app. We would work with existing Patients, and Clients, and Doctors, but would be able to link over to the maintenance features as needed to add or edit on the fly. So let's say the person at the clinic who does the scheduling is on the phone with Ross, and about to make an appointment for Indiana Jones to come in. But then the other line rings, she puts Ross on hold, and it's me. In the nicest way possible, I've called to just let her know that they've got my last name spelled wrong, that happens all the time, people just want to put that H in there. So even though she's in the middle of scheduling, and scheduling has its own backend, its own bounded context, and is totally separate from client management, she can still drive the app right over to the client management area, very quickly fix my name, Update. Notice that Ross is still the active scheduling client that's showing up in the left-hand corner. So now flip back to the schedule. I notice that the update is showing, and now she can go ahead and finish up with Ross scheduling the very adorable Indiana Jones for a little Wellness Exam. So to the user there's no difference between the scheduling and clients. It's just a nice smooth flow between the two. But for the purposes of designing our application everything is bound within their individual context and we don't have to worry about switching context quickly when we need to do something like that. So remember, we're talking about what makes these all entities. An Appointment object needs to be located and tracked and we need to be able to edit them easily. Using a unique identity allows us to persist and retrieve an appointment even if some of its values change. Appointment is definitely an entity in our system. We actually had to think a little more about Client, Patient, Doctor, and Room in this particular context. Our discussions highlighted the fact that when creating appointments we only need access to some of the high-level information about the Client, Patient, Doctor, and Room, but these objects won't be edited. So we wanted these stripped down, read-only types that give us minimal amount of detail for each. We do still need to be able to uniquely identify them though, I mean, they do have some identity. If the client's name changes, a change we would make in the client management system, that new name will need to be reflected when we look at the appointment scheduling for that client. There should only ever be one record to represent a particular client in our back-end system. So Client and the other types that are reference types in this context are still entities. We triple checked our decision with another kind of domain expert, Vaughn Vernon, a DDD expert, and we were happy to get his thumbs up on this particular decision. So all these types inherit form our base entity class, however, notice that those reference types use int for their base entity's ID and not the GUID that's used by Appointment. That's because all of the management of those other types happens to be done using CRUD, and with CRUD it's easy to just use database generated ints. Appointment is built using DDD principles, and you'll see that it's much easier to use GUIDS when building DDD entities and their related logic rather than relying on the database to provide the identity values. Not only is it easier, but it follows DDD principles more clearly since we will build all of our domain logic around appointments without involving the database. We would have a hard time working with appointments in our model, and in our unit tests as we develop the application, if we always needed a database to assign their IDs. So that's not to say that you can't use integer IDs if you're going to use a DDD style of application. It just makes it a little harder. Wouldn't you say Julie? Yeah, and I've definitely come up against that with the stuff that I do with Entity Framework. I've made sure that I show patterns for continuing to use the database generated ints because I didn't want to give people the impression that they had to throw away, like for me, like 25 years of this dependency, right? And all of a sudden I have to go cold turkey and move over to GUIDs. Sure, I mean, there's tradeoffs in what you choose to use for your ID. But having an ID that we can generate in the client and just in our code has a lot of value. Oh, and every time we've been working on some of our different unit tests and we needed, as part of the test, to instantiate something that was an int. We were like, ugh, now we have to find another way to get that in there because we were protecting it and it was a problem. So Julie, Michelle, and I also talked about how to name the types that are simply reference types in this particular bounded context. At first, we were worried that we might get confused by having different definitions of Client, Patient, Doctor, and Room. We wanted to call them ClientDetails, or ClientView, or something like that. But thanks to the ubiquitous language, the fact that we are in the scheduling context drives our comprehension of what a client means in this particular space. A client in scheduling is still a client so we use the same name, even though it's a differently defined type than the client we work with in the Client Patient Management app. Right, and thanks to namespaces in our code, we're able to keep it clear which ones are which in the code.
-
Eric Evans on the Single Responsibility of Entities
Here's Eric Evans again with some additional insight into entities. Steve has asked him how entities align with a Single Responsibility Principle. If you're not familiar with this Object-Oriented Programming principle, you can learn more about it in Steve's SOLID course, here on Pluralsight. Eric, one of the questions that I've heard is, what's the single responsibility for an entity or to put it another way, does having an entity that has a lot of business logic in it violate the Single Responsibility Principle in Object-Oriented Programming? Or how would you reconcile that? I would reconcile it by saying that I don't think an entity ought to have a lot of different business logic in it. Now, I do think that these entities are very central and so it's natural that they get heaped up with lots of functionality. But if you do that you have a couple of hits. And one of them is that as you build out the system there are more and more conflicting demands on these central entities. So, they end up being huge. It's not unusual to find entities with 600 or 1,000 methods, and members, and so forth. So, I don't think that's good. The responsibility that I would focus the design of an entity on is that, I guess you could say too, very closely related responsibilities, that is the identity and the life cycle. And so the identity isn't just an ID field. There are some situations where you just put an ID on an object and it pretty much takes care of it. That's if the domain has been structured to allow that. For example, if you are UPS shipping a package, the first thing that happens is that tracking ID gets assigned to that package and it stays with it to the very end. And so there's not much else involved in the identity of a package, but that is not always the case, like especially in domains where there are people involved. You will often find that just stamping an ID onto say a customer is not really going to cover it. You might for example, have to have a phone call from that person and you have to figure out, well, is that person actually customer number 3275628? They don't know that number to tell you, or if they do you still want to verify it for yourself. So you ask them their name ---of course, that doesn't confidently identify someone, but it's evidence, you ask them their phone number, maybe some secret information about them. That's one case where identity becomes something more complex. Now actually, when it gets to be really complex I don't want to put the responsibility for even that onto the entity. So, sometimes identity matching becomes such an elaborate thing that I would have another object that was responsible for, or a set of functions, that would be responsible for that. But let's get back to something more typical. You've got an entity. Maybe most of the time you can manage with that ID field. And it goes through some life cycle. A UPS package goes through some life cycle like received by them, I suppose, and then shipped, and in transit and it moves from one location to another. And then finally it is delivered to a customer and maybe there's a receipt that they get, you know, someone has to sign something. So there's a basic life cycle that one of those packages goes through. But if there's much other logic besides that, and I bet there is, you'll want to start using some other source of logic. You might want some services that can answer questions about what should be done with the package. But you might want to have, even for the logic that that core entity is doing by itself, it might delegate it to value objects so that all the entity is really doing is saying some simple question about its status, or about what should be done next, or something like that. And the value objects interact to answer the question, or some service is invoked to answer the question. What I'm getting at here is that I think Singles Responsibility is a good principle to apply to entities and the pattern is kind of pointing you toward the sort of a responsibility that an entity ought to retain. And anything that doesn't fall in that category we ought to put somewhere else, delegate somewhere else, or put in a service, or something like that.
-
Eric Evans on the Entity Equality Methods
I have come to believe that an entity shouldn't even have an equality comparison. The question of whether an entity is the same as another entity is a non-trivial question. And equals a sort of a simple concept that I think is perfectly applicable to a value object. A value object should always have a well-defined equals, nearly always. But I think the concept isn't quite right for entities, usually you mean something like does this refer to the same entity or have I somehow got two copies of the same object, or something. I mean you really have to say, well, what do you mean? Whereas a value object, you know, if it says 32 miles, let's say is a value object. Well if I find another 32 miles value object, that's that, there's nothing else to know. But if I found two objects in a system representing a certain customer, or I want to know if they both represent the same customer, why am I asking this question? It's more complex. Am I asking it because maybe we ended up entering them twice and so we've got two customer IDs that are the same person? Or do I have two objects that have the same customer ID and they don't have the same data because they've been ---you know, we've got a distributed database and so there's some discrepancies between different copies of the data, in which case I'm trying to reconcile two objects I believe have the same identity. So we just need to take the cases, I think, individually, so equals doesn't quite tell me what I need to know.
-
How We've Implemented Entities in Our Code
So now let's take a look at an entity in our veterinary appointment scheduling application. We're going to look at the appointment class, which defines all of the information that we need to schedule an appointment for a particular animal, with a particular doctor, in a particular room. Now the appointment class inherits from Entity, which is a generic base class. In this case it's Entity, as you can see here. The Guid is defining the type of our identity property, our ID. Right, and we talked about that earlier when we were looking at the structure of the different entities in this model. We wanted appointment to have a Guid because we're creating new appointments on the fly. So let's take a look at that entity base class, and you can see a few things about this. First of all, it's an abstract base class so we can't just create an entity, we have to create something that is an entity such as an appointment. And using generics we're saying that the entity is going to use whatever type we ask it to and that type is for defining the ID. So for appointment we said Entity is going to be using a Guid as its identity. I mentioned this earlier, why I would need Guid for appointment in this context because I need to be able to create new appointments in this context and I'm not going to be waiting for the database to generate that ID for me. So using a Guid lets me create that ID right up front, as I'm creating that new appointment so I'm giving it it's ID. If you take a look at the constructor, the constructor just checks to make sure that you're passing a value in as opposed to only the default value of whatever the type is. So a default Guid, or a default int if you're using an integer for the identity. There's also another constructor here, which is a protected constructor. Now you've heard us talk about all our focus is on the domain right now not about our persistence layer. But, because I know that we're going to be using Entity Framework, that's the ORM we use, and I know that Entity Framework has a particular rule about constructors for our entity classes, I'm just going ahead now and taking care of that little bit of business in advance, instead of worrying about it later. Add then persistence layer there's a lot of other things I'll be working on, but I won't be putting them in our actual domain classes. But the reason that this is here is because it needs access to a parameter list constructor because it uses reflection to materialize objects from query results. That's the only reason it's there. I'm just planning ahead for my persistence and it's just a pragmatic concession that I'm making to my persistence layer even though we're focused on the domain. Okay, so further down we have a bunch of methods here for doing equality comparison. We just heard Eric Evans saying that he really doesn't like to have equality comparison in the entity itself, but here we're using pretty simple entities, so in this case we find the equality comparison to be helpful in our entity base class. Alright so let's take a look back and the rest of Appointment. Now since Appointment has more behavior than just state we don't want to have it just be a bag of properties that our application can get and set however they would like. Because that would be an Anemic Domain Model. Yes, because that would tend to lead us toward a more Anemic Domain Model. And we want a Rich one. Now in particular, we also are constraining how we create this appointment. So we're going to always have to specify either pass in a Guid ID directly, or just let it have one be passed in through the base constructor that we just saw. Now some of the times when we will want to update an appointment, remember these aren't value objects so they are not immutable, we can change them, we're going to do that through methods. And so, for instance, if we decide we want to update what room an appointment is scheduled in we're going to do that through a method rather than just a setter because there's additional behavior we want to do. In this case, perhaps, we want to raise an appointment updated event that we might handle and send a notification or do some other behavior as a result of that event. And that also gives us the flexibility in the future to change what type of logic we want to trigger. And that's something we can't do very easily if we've just let anybody in the application set the value. Right. Now, we don't want to do a lot of work in our constructors because that makes our code harder to test, among other things. So, since we do have some validation and some other checks we want to do when we create an appointment, we've created a static factory method that simply does some checks to make sure that the various required pieces of data are present. In this case most of the required pieces of data are just IDs of other related elements in our domain. So, we're just checking to make sure that they're in the valid range for an ID. Right, because otherwise somebody could satisfy the requirement that they pass in the doctor ID, but they might pass it in as 0, so we're further constraining that they don't do that either. Because otherwise it's not an appointment without all of those values set up. Correct and what this helps us do is avoid having entities that are partially constructed where they've got some of their properties set, but others are not. They're in an inconsistent state. Once we've created an appointment we need to record it as part of the clinic schedule, which involves some additional rich behavior. So if we scroll down to the bottom we've got this method called Schedule. And this is where we're going to do the additional work involved with getting an appointment, not just created and instantiated in our application, but stored in our persistence layer, as well as fit in with the other appointments that already exist in the system. Now, there are some different things that are going on here, but we're not going to get into the code at this point. Because in the next module, we're going to actually adjust our design a little bit as we've learned more about the domain, and so we'll talk more about how the scheduling is actually going to work in the next module. Alright well let's look at one other simple type that isn't really being built using DDD, it's just one of our reference types, the Doctor class. And you can see here that we do have Doctor inherit from Entity as well. In this case, it's using an int as its key, and the only thing it has as a property is just the name of the doctor. So if you recall form seeing the class descriptions of all of these classes before we heard from Eric, we had explicitly decided that these reference entities where we're actually doing their maintenance elsewhere so there's no complexity here for them. Right and they're just read-only so we're never having to create them. And we're using the ints that were created when we created these in the CRUD context in a different area of the application, but they're still entities here, but they're entities of type integer.
-
Associations (aka Relationships)
Many developers, myself included, tend to define relationships between classes in both directions. For example, an order has a line item and a line item has an order. A pet owner has pets and a pet has an owner. Many of us tend to think in bidirectional relationships by default. Because Domain-Driven Design aims for simplicity in the model, we start recognizing more quickly that the bidirectional relationships can often make things overly complex. For instance I've often found this to be true when it comes to adding in my persistence layer and I happen to mostly use an ORM Entity Framework, which brings along it's own behavior and assumptions about how relationships are managed. Sometimes the fact that my model includes navigation properties that may not be totally necessary can be the cause of some grief that's led me to take some time to consider if I really need that navigation or not. Domain-Driven Design guides you to default to one way or unidirectional relationships. That's not to say that you shouldn't ever have bidirectional relationships, but that because of the extra complexity involved you should spend some time considering if that complexity is justified. A relationship, also known as an association, should be part of a type's definition. If you introduce a bidirectional relationship it means that neither object can be defined without the other. If that's not the case then be specific about the direction of the relationship, also called the traversal direction, to keep your model design simple. Eric Evans puts it this way, a bidirectional association means that both objects can be understood only together. When application requirements do not call for traversal in both directions, adding a traversal direction reduces interdependence and simplifies the design. So, with a DDD eye we can look at our model and ask, can we define a client without identifying their pets? Can we define a pet without identifying the client who is responsible for them? This may sound like a simple set of questions, but it could lead to a whole lot of debate. For example, why would a person be scheduling an appointment if they didn't have a pet? So in the context of scheduling appointments a client doesn't make a whole lot of sense without one or more pets, or patients. Or from another perspective, a cat can't pay a bill or call to make an appointment. So, how can we define a pet without a client? These are both pretty reasonable arguments, but neither one gets us anywhere. So let's start again with defaulting to a one-way relationship. A client would need a patient to schedule an appointment. A client would not need a patient to pay a bill. Okay, and if we started from the patient end, a patient doesn't schedule an appointment so that becomes a mute point. Nor does the patient pay the bill, and you know, because my dog doesn't have a credit card. He can't use the phone very well either. So when would you start with a patient and need to know something about the client responsible for that patient? It's an interesting question. So, in the context of scheduling an appointment one could argue that we should define the traversal from client to patient and that we gain nothing by having a way to traverse from a patient back to a client. You may balk at that notion, but remember that all we care about right now is scheduling an appointment not all the other possible scenarios where it might make sense to traverse from patient to client. Sure, it's another example of YAGNI, you're not going to need it. In fact, we originally had owner as a property on patient in this context, but we realized it wasn't necessary so we removed it. However we kept the ID because we had some scenarios where it was useful. So in the end we chose to define relationships that traverse from appointment to doctor, patient, and client. And to define that traverses from client to patients, or their pets, but not the other way.
-
Value Objects
When introducing entities Steve and I talked about objects that were defined by a thread of continuity and identity, not defined by their values. So what about objects that are defined by their values? These are called value objects and they play an equally important role in a domain model as entity objects do. A value object has very specific characteristics. It is an object that is used to measure, quantify, or describe something in your domain. Rather than have an identity key, its identity is based on the composition of the values of all of its properties. Because the property values define a value object it should be immutable. In other words, you shouldn't be able to change any of the properties once you've created one of these objects. Instead you would simply create another instance with the new values. If you need to compare two value objects to determine if they are equal, you should do so by comparing all of the values. Value objects may have methods and behavior, but they should never have side effects. Any methods on the value object should only compute things, they shouldn't change the state of the value object since it's immutable, or the system. If a new value is needed, a new value object should be returned. A value object that you probably use all the time is a string. In .NET and many other languages, a string type is immutable and you now know that immutability is one of the key attributes of a value object. A string is a collection of characters and the combination of all those characters give that string meaning. For example, C-A-R, in English, a car. If a string were mutable we could change the R to T, now the string is C-A-T, a cat, which has a very different meaning than a car. Or we could add a letter, maybe put an S in front of it turning car to scar. Also completely changing the meaning of car. But it's not just the array of characters that gives a string it's meaning, the order of them is also critical. Just think of the word dog, D-O-G, shifting its letters around gives us something with a very different meaning. So one of the things that .NET makes it really easy to do is to modify strings, like you can change the length of it or make one all uppercase, but when you call, for example, ToUpper on a string it doesn't just change that string object it gives you a new instance of a string that now has all uppercase characters. I've heard many developers say that monetary values and financial systems have been perfect candidates for value objects in their system. And Ward Cunningham provides us with a really helpful example, a company's worth. If a company is worth 50 million dollars that means something, 50 million dollars. It's a very specific measurement. 50 million on its own is not a measurement, it has no meaning without the unit, which in this case is dollars. In fact, dollars doesn't really help does it? Because is it US dollars, or Canadian dollars, Australian dollars? But US dollars alone doesn't describe worth either. It only makes sense when you put the two together as 50 million US dollars. There's actually one more factor to take into account, is the point in time of this 50 million dollars because of the way financial systems work and the fluidity of monetary values. So we could still just have the two properties in this company class, but by creating a value object you also protect and constrain the measurement. So for instance, we might have a class called Company. It might have one property that was a decimal that was the worth and another property called the Worth Unit, that might be a string that says US Dollar. The problem with this approach is that it doesn't tie these things together in any way. Nobody should be able to set the unit without also setting the amount, they should change together. More importantly nobody can just randomly change the units. 50 million rupees is a totally different value than 50 million dollars. The value is represented by the object as a whole. It's immutable. If the company's worth changes then we can just set the worth property to a brand-new Worth value object. A value object is not always for a measurement though, it can be another type of description. I've used this one often, DataTimeRange, and it was perfect for the vet appointment scheduling app. We usually set a start and end time together and can't really set one without the other. Also, we often need to pass the two values, start and end time, around from one method to another. So we've encapsulated them in a value object called DateTimeRange. The properties have private setters, which makes the object immutable since we can't change them. We aren't showing the full logic of the class here, but when we look at the value objects in our application you'll see more of how we implement a value object in our software to ensure that it meets all of the attributes, not just immutability, but how we handle equality comparison and other logic. In his book, Implementing Domain-Driven Design, Vaughn Vernon recommends that we should try to use value objects instead of entities where ever possible. He says, it may surprise you to learn that we should strive to model using Value Objects instead of Entities wherever possible. Even when a domain concept must be modeled as an entity the Entity's design should be biased toward serving as a value container rather than a child Entity container. What this means is that you'll find that your design will have a number of entities who have very little logic of their own or very few primitives as their properties, but instead will have a number of properties that each are themselves, a value object. So he's not saying everything should be value objects, but that it's probably our natural instinct to start by thinking of things as entities and then maybe once in awhile go, oh, maybe that should be a value object. So what Vaughn is suggesting is really start by thinking every time, should this be a value object, and you will surprise yourself at how many times something that you originally might have thought of as an entity really does make a lot more sense as a value object. Or sometimes when you're looking at an entity there might be a couple of properties that seem to always go together. You might be able to bundle these properties into a single value object.
-
Eric Evans on the Methods in Value Objects
I think that value objects are a really good place to put methods and logic. A better place than any of these because we can do our reasoning without side effects and identity, all those things that make logic tricky. We can put functions on those value objects and do the pure reasoning there. So, you know, people focus on the data so much, and with entities they do too, they say, well, basically then an entity is just an ID, what more is there to it? Well, I think there's quite a bit more to it because that ID is just, you know, what does it mean, how do you interpret it? But, with a value object there's very often closely related logic. Well, a good example actually might be with dates. Dates are a classic value object, and there's all kinds of logic with them. Now we usually are doing this in the context of some entity. Let's say that a person has a date of birth field and we want to know how old they are. So, if you want to do that you have to know what, of course, assuming you know what time it is now or what the date is now, it's pretty easy conceptually to think, well then I'll just take today's date minus a certain date in the past and I'll get the number of years. Maybe I'll get that and round off to the nearest, to round down to the nearest year, which is what we usually mean when we say how old someone is. That logic though isn't so trivially easy. Joda-Time is a pretty good example of value objects that actually do something. You see that those objects are much more than just data, it's much more than just a data structure that says here are two dates. It's really an object with logic on it. But logic with no side effects right? Yes, and that would be one of the rules that I would apply to value objects. One of the reasons that we can use them so freely, because when I say interval give me your duration, I haven't modified the interval, or dates that were in it, I just get back that new direction object. If I said, oh, I don't want this full interval, I would have to create a new interval. There's no way to modify any of the Joda-Time objects and there's none of the methods that they have ever modified anything.
-
Value Objects in Our Code
Here's a closer look at the DateTimeRange value object we created. A value object doesn't make sense on its own so you can see it's used as a property in the Appointment type. Let's take a look at the code for some important concepts of how to implement a value object. The DateTimeRange class inherits from value object. Both of these types are located in our SharedKernel because of the fact that we could use them in multiple different bounded contexts within our application. Looking at the DateTimeRange, the first thing you'll notice is that it inherits from ValueObject, if we look at that type. This is a class that Jimmy Bogard wrote and you can find it at the URL shown here. The benefit of using this base class for your value object is that it implements IEquatable and also provides you with overrides for Equals and GetHashCode that will automatically, through reflection, take a look at the different properties that your value object has and compare each of them to determine whether or not two value objects are equal. One thing to point out about this class, however, is that it doesn't take into account the possibility that your value object has a collection property. Now we're knowingly using this because so far we don't have any value objects that have collection properties. Okay, so looking back at DateTimeRange, one of the things to note about this is that we've made it immutable. Now unfortunately C# doesn't have any built-in language support for immutable types, but we can achieve the same thing by making sure that our properties don't have any public setters, or any other way for the user to set the state of this object once it's been created. We provide whatever state this type needs in its constructor. And notice that we have two different public constructors, the first one is kind of obvious, we pass in the values for a start and an end time. But as we were building it, Steve had a very good idea to take into account scenarios where we might have a start time and a duration. So if we're creating an appointment, we know that it starts at 10:00 a.m. and it's a half an hour long. Well this lets us pass in that information and then the constructor itself will set the value for the end date based on those two values. You just heard Eric Evans talking about having methods in value objects that can provide additional logic that the value object can be responsible for. As an example of that we've got this DurationInMinutes method that lets us easily find out how many minutes a particular DateTimeRange represents. As we evolve our application perhaps we'll want to add a duration in days, or a duration in hours, but right now duration in minutes satisfies all of our needs. The other methods I want to point out are similar to some of the string methods we were talking about. Remember the string methods return new instances of the string based on the existing string plus whatever logic the method you asked for performs on it. So we do the same thing here, for example, with newEnd we might need to change the end time of a DateTimeRange on, for example, an appointment. So since this is immutable we're not going to change the end time but we can say, hey, DateTimeRange, we need a new one that looks just like you, except the end time changes. So all we have to do is call this method, pass in the end time, and it returns a whole new DateTimeRange instance for us with a correct start and end time. And so we have other methods that allow us to do this in different ways. And then the other value object that we can point our here is AnimalType. This is just to give you an idea that your value objects can be extremely simple. In this case AnimalType is just a combination of the species and the breed of a particular pet, or patient that we're dealing with at the vet clinic. And there's not a whole lot of other behavior here, but it does provide us with a container for encapsulating these two related properties together as a single value object.
-
Eric Evans on the Entity Logic in Value Objects
I think that actually is good. If there is logic that is really the classic software logic I like to add that in value objects. One thing, you can really test value objects tremendously easier than entities. You can use them more freely. So, then your entity becomes this critical piece of glue, and an orchestrator, usually among those different value objects, but doesn't really do a lot, can't complete a lot of operations on its own. But that doesn't mean that we still won't have some logic in it, but it'll be very concise. Actually I think a nice way that it can end up is if we manage to get the ubiquitous language to the point where you look in the methods of the entity and you see, kind of, higher level things. That read like, almost sort of, use case level of communication rather than the nitty-gritty detail.
-
Domain Services
When an operation is important to the model, but doesn't necessarily belong on any one entity or value object a service is often appropriate. But don't be too quick to give up on finding a natural home for the operation on an existing entity or value object. Or you may end up with a very procedural anemic model. Frequently domain services serve as orchestrators for operations that require several different collaborating entities, or value objects. Evans notes that good domain services must first, and foremost, not be a natural part of an existing entity or value object. Again, we don't want to shift all of our rich behavior from our entities and value objects to our services. Services should also have a defined interface that's comprised of domain model elements. And finally, domain services should be stateless, though they may have side effects. What this means is we should always be able to simply create a new instance of a service to perform an operation, rather than having to rely on any previous history that might've occurred within a particular service instance. But of course, the result of calling a method on a service might result in changes to the state of the system itself. These rules apply specifically to domain services, which belong in the core of our application. Your software will likely also use services to perform work related to infrastructure or as part of the front end of the application. Here are some examples of the kinds of services we might find in different layers of a DDD application. The UI layer represents the front end of the system and should have as little business logic as possible. It is frequently combined with the application layer, which should be concerned with behavior necessary for the application, but unrelated to the customer's problem domain. For example, the application may need to work with file formats or parse some XML, and it might have services for these purposes, but these are unrelated to the domain. In the core of the application, where we store our core model and domain objects, we will define any domain services for operations that don't belong somewhere else. These services will frequently involve operations on multiple domain elements or may be responsible for orchestrating some kind of workflow. For instance processing an order might involve a series of steps and multiple domain elements as the system checks inventory, verifies customer information, maybe charges a credit card, and then sends messages to ship the order, notify the customer, and reduce inventory. Finally, we have infrastructure-level services, these will usually implement interfaces that are defined in the core of the domain, such as ISendEmail. But since they require access to external dependencies like file systems, databases, or network resources, they live in the infrastructure layer of the system. With respect to our domain, you may find infrastructure not very interesting. Although, the people who create the internal workings of those services might find them quite fascinating. We'll look at implementing services in our application later on in the course.
-
Glossary
We've covered a lot of ground in this module and you've learned a lot of new terms so we just want to review some of them with you before we move on to the next module. The first is a pair of terms that often go hand in hand, Anemic Domain Models versus Rich Domain Models and remember the Anemic Domain Models, while often looked down upon from the perspective of DDD, they're perfectly fine for CRUD. These are models that look a lot more like a database schema than a class that has lots of methods and rich behavior in it. On the other side of that is a Rich Domain Model, which is what we strive for in Domain-Driven Design and that's a model that really is focused on behavior, not just changing the values of properties. Then we talked about entities and entities tend to be one of the core pieces of our domain model. The key that distinguishes an entity from other types in our model is that it has some kind of identity that we can use to track it over time, and to bring it in and out of persistence. So we also talked about immutability, which is a really critical attribute for value objects. And immutability just means once an object has been instantiated, you can't change the value of any of its properties. Another important term we learned about is the value object. A value object is an immutable class that is defined by the sum of the different properties that it has. We don't' need an identity for a particular value object, in fact, a value object doesn't have an identity outside of the individual properties that it has. And in order for us to compare value objects, we simply compare all of its properties, and if they all match then we can consider these two value objects to be equal. We also learned about domain services and these are interesting because domain services give you a place to put logic and behavior that you can't find a home for in the entities and value objects in your domain. And the last terms that we want to review is side effects. Side effects are changes that occur in your application, or any kind of interaction with the outside world. Now technically any change to the state of the application can be considered a side effect, but generally when we're talking about them, we're talking about things that changed other than the main intent of the operation that you're performing. For instance it's often a good idea to keep operations that query information separate from those that change state. And if you follow this practice then any queries that you make that result in changes to state would be said to have side effects.
-
Key Takeaways
Wow, so we've covered a lot of ground in this particular module. Let's remember where we started. We've talked about a lot of different details, but the big picture is that we want to have a focus on the domain. Remember we talked about D is for Domain. Now you did see me make one concession to my persistence layer when I showed you the protective constructor in our entity base class, but for the most part we're not worrying at all about our persistence layer, staying focused on the domain. And at some point we are going to have to persist this data into some kind of data store, but I'm not worried about that. I happen to be really good at the ORM that we'll be using for our persistence layer. And I'm confident that whatever our domain throws at us, we'll be able to get it into and out of the data store with no problems.
-
Resources
So here are some references that you'll find useful. Again, we can't recommend enough Eric Evans', DDD book and also Vaughn Vernon's book, so here they are again. For this particular model we talked about domain services. Jimmy Bogard has a great blog and a good article on services at the URL shown here. Yeah, you know I have so many times been hunting down just some clarity on something with DDD, or other aspects of programming, and so often I come upon Jimmy's blog. And he just writes in a way that things just make so much sense to me when he explains them. And again, we want to thank Eric Evans for his time and helping us with this module. Absolutely. You can find more from him on the DomainLanguage.com website. And your course. Oh right, we talked about Single Responsibility Principle with Eric. And if you're not familiar with that principle you can learn more about it, it's one of the SOLID principles that I covered in my SOLID course. This is Julie Lerman. And this is Steve Smith. And thanks for watching our course on Fundamentals of Domain-Driven Design.
-
Aggregates in Domain-Driven Design
Introduction
Hello, this is Julie Lerman and this is Steve Smith. Welcome to the fourth module of our course, Domain Driven Design Fundamentals. In these next two modules we'll talk about how to manage complexity in your domain. This module will focus on the aggregate pattern.
-
Goals
We've talked about the domain model and the need to have effective communication in order to ensure the model is a useful representation of the customer's problem space, however, most problems that weren't using Domain Driven Design can be quite complex, so now we're going to specifically look at some patterns and techniques that can be used to manage this complexity. We'll cover several new terms along the way including Aggregates and Aggregate Roots. You'll learn about Invariants and the Aggregate Roots responsibility for them. Then, we'll look at our application and see how thinking about the Aggregate Roots pattern helps us revise and simplify out model, and finally, we'll walk through how we've implemented this pattern in our code.
-
Tackling Data Complexity
Let's start by considering data complexity. If you've ever worked on a relatively large or mature application, you've probably seen some fairly complex data models. One way to reduce the complexity that we already talked about is by limiting how many bidirectional relationships you have. Another is the use of aggregates and aggregate roots. If your design doesn't have any clear notion of aggregates, the dependencies between your entities may grow out of control resulting in a model like this one, and if your object model reflects a data model like this one, trying to populate all of the dependent objects of one object might result in trying to load the entire database into memory, and the same problem exists when it comes time to save changes. With a model like this there's just no limit to which areas of the data model might be affected. Even though in the real world at the highest levels of your system all of these things really do interrelate, we need to be able to separate them to keep the complexity of the system in check. I've gone into a lot of clients where their entity data model looks like this and they're using this one big, huge, single model throughout their entire system, so one of the things that I work on with them is breaking this down and using the whole concept of bounded contexts to start looking at what makes sense for smaller models. Yeah, a system that's designed like this is what we tend to call a big ball of mud because everything is just kind of slapped together and it collapses under its own weight once it gets to a certain level of complexity. Great, so let's --- see how we can use aggregates to help solve the problem.
-
Introducing Aggregates and Aggregate Roots
Aggregates consist of one or more entities and value objects that change together. We need to treat them as a unit for data changes and we need to consider the entire aggregates consistency before we apply changes. In the example shown here the Address is part of the Customer and the component is quite literally a part of the product. We can treat a set of changes to a Customer and their Address as a single transaction. Every Aggregate must have an Aggregate Root, which is the parent object of all members of the Aggregate, and it's possible to have an Aggregate that consists of just one object in which case, that object would still be the Aggregate Root. In some cases, the Aggregate may have rules that enforce data consistency that apply across multiple objects. For instance, maybe our product consists of a collection of components, but in order to be in a valid state it needs to have a specific set of such components. As an example, if the product is a Lego mini fig the collection of parts won't be a valid product unless it includes a head, an upper torso, a lower torso, two arms, two hands, and two legs. If we allowed the collection of components to be modified independently of the product it was associated with, we could easily end up with consistency problems. If we want to modify the composition of a product in this example, we should do so as a transaction, so that we start and end with a valid product. Data changes to the Aggregate should follow ACID, that is they should be Atomic, Consistent, Isolated, and Durable. It's also the Responsibility of the Aggregate Root to maintain its invariants, such as the number and type of components it requires in the example, and a variant is a condition that should always be true for the system to be in a consistent state. When considering whether particular objects should be treated as an Aggregate Root you should think about whether deleting it should cascade. In other words, if you need to also delete the other objects in its Aggregate hierarchy. If so, it's likely the object in question should be considered an Aggregate Root. Another way to think about whether it makes sense to have an object as an Aggregate Root is to ask, does it make sense to have just this object detached from its parent? In the example shown here, each component represents a specific piece of the product. Part of the definition for the component might be a standard part number, but it also includes specific product information like whether it's the left or the right hand of the figure. While it may make sense somewhere in the application to refer to a yellow hand part all on its own, referring to the left hand of a particular product that happens to use the yellow hand part is not likely to be referenced separately from the product object. Thus, we wouldn't provide persistence options for retrieving a single component separately from its product. We would retrieve and save whole products along with their components. In the Domain Driven Design book Eric Evans states this pretty simply. He says, "An aggregate is a cluster of associated objects that we treat as a unit for the purpose of data changes."
-
Interacting With Aggregates
Aggregates serve as boundaries between logical groupings within our application. We enforce these boundaries by prohibiting direct references to objects within an aggregate that aren't the root of the Aggregate. Consider the Customer with the Address. It's perfectly okay for Customer to reference Address. Address might be an entity or it might be a value object, it doesn't really matter in this scenario. What's important though, is that the only way to get to the Address in this Aggregate is through the Customer. We won't be referencing an address by some identity outside of this aggregate, but that's not the case for Customer, since the Customer is the Aggregate Root it can be referenced from other aggregates. In this common example an Order might reference a Customer. Depending on our context, it might make sense for a Customer to reference an Order, but in this case let's assume it only makes sense for the order to be central to the applications design. What's not okay is for the Order to reference a Customer's Address directly. This violates the integrity of the Customer Aggregate. Remember that Aggregates and Aggregate Roots only apply to objects not data, and when we're talking about references we're talking about object references, properties that use an object directly. This is especially important with ORMs. For example, if you were to save an address that had a Customer object attached to the Customer property, there are scenarios in which entity framework would involve the Customer in the database insert or update, possibly even a delete, and this behavior leads to a lot of confusion. I frequently advise developers to just remove the navigation property and use the foreign key ID instead. It's a little more work, but removing some of the ORM magic results in more control over the behavior and this aligns perfectly with the fact that one common way to enforce the aggregates is to replace direct navigation properties in the models non-root types with key references, and this reduces the number of dependency relationships within the model.
-
Evolving the Appointments Aggregate
Since we're dealing with appointment scheduling, our initial design might look something like this. An appointment involves bringing together a Patient and a Doctor in an exam room for a particular type of exam and since we'll typically need to know the owners information when we deal with the scheduling, it's important to have a reference to the client from the patient also. If we model our system this way, any time we saved an Appointment it's going to scan all of these objects for changes and save them as well. Modeling it this way, the scope of our domain for appointment scheduling is much greater than it needs to be since, in our case, we don't expect to modify any of the other objects when we're creating an appointment. Right, an appointment is basically just a list of resources tied to a particular time span. It models who, what, when, and where, but it doesn't ever need to change any of these associated concepts. As a result, we can simplify our design by eliminating most of these object relationships from the appointment classes design. Recall that for an object to be a good candidate for being an Aggregate Root, it should be the case that deleting that object should also delete the other objects within the Aggregate. In the design shown here, if a customer cancels an appointment, and we delete it from the system, it doesn't make sense that this should delete all of the associated objects. Here's another perspective on that same model. By simply including the IDs of the related concepts, rather than object references, we're able to ensure that creating and changing appointments has a minimal impact on our system when we persist the appointment. This relationship works because an appointment in the real world is really just a note that includes a place, time, and additional details. Adding and removing appointments shouldn't impact the people and places involved and this revised design reflects this.
-
Using Invariants to Better Understand Our Aggregate
We do still have a bit more learning to do with this model though. Somewhere in our design we need to enforce certain invariants about appointments, like that they shouldn't be double booked. Our current thinking is that appointments need to include this rich behavior with regard to how they're scheduled. It's the Aggregate Roots responsibility to verify any invariants the Aggregate may have and, in this case, the appointment is still acting as an Aggregate Root, even if we've eliminated the navigation properties to the other objects that it might be working with. Let's make sure we're clear on invariants and then we'll see how invariants in our application impact our design. An example of an invariant in the real world is the speed of light, which is a constant that you just can't break in terms of the physics of the universe as we know it. Some things in your system must be true in order for the model to be consistent of valid. Other examples of invariants might be that the total of the items on a purchase order do not exceed the PO amount or that appointments do not overlap or that an end date on an object must follow the begin date on that object. Sometimes an invariant only involves a single object, maybe a particular property or field, such as name, is required. In this case, we may model the system such that one can't even create the object without the required information. Our Value objects are like this. For example, you can't create an instance of a dateTimeRange object without defining both the start and end time. However, sometimes the invariants involve how multiple objects relate to one another. In the example here the Purchase Order and the individual line items would most likely be modeled as separate objects, however, the Purchase Order would be the Aggregate Root, and as such it would be responsible for verifying this invariant. The individual line items on the Purchase Order probably don't know anything about one another, nor should they, so it wouldn't make sense to put the responsibility for enforcing this invariant in the line item object. What about appointments? How does one appointment know whether it overlaps another? As we focused on these invariants and where they belong in our design, it became clear to us that the appointment didn't really make sense as an Aggregate Root. If you apply this thinking to our appointment scheduling context, it follows that one appointment doesn't really know anything about other appointments, but the Schedule knows about such things. Let's evolve our domain model to follow this pattern and see where that leads us.
-
Modeling Breakthroughs and Refactoring
This feels like a big change to the model and these kind of epiphanies happen when you're working on the model, but that's not a bad thing. It's not like you've wasted a lot of time focusing on appointment as an Aggregate Root. This is the beauty of modeling your domain, having conversations with different people, with the domain experts, because ideas like this bubble up, and suddenly something big like this becomes clear, so you're not going to get it 100% right the first time. Your understanding will evolve as you learn more about the domain and from time to time you'll realize there are big changes that can dramatically improve your design. In the Domain Driven Design book, Eric Evans talks about these breakthroughs in his section about Refactoring Toward Deeper Insight. This is really an important part of Domain Driven Design and about a quarter of the book is dedicated to it.
-
Considering Schedule as Our New Aggregate
Even though the initial design we had was about scheduling, the Schedule itself was never part of our model. Once we include Schedule as its own explicit object in our model, it makes the design much simpler. Appointments no longer need to know anything about other appointments. The responsibility for ensuring that appointments are not double booked, and similar invariants, can be performed by the Schedule, which is the Aggregate Root. Let's see if this passes our other tests about defining Aggregate Roots. When we save changes to a Schedule does it make sense to update any changed appointments? Yes, it does make sense, and if we were to delete an entire schedule, would it make sense to delete all of its appointments? Yeah, I think that would make sense also. Yeah, I think this is the schedule for a particular clinic. At the moment we only have on clinic, but if we imagine a scenario in which multiple clinics each have their own schedule, it wouldn't make sense to delete a clinics schedule, but then keep its appointments floating around, so I think that works. Great. If a schedule exists for each clinic, then it makes sense to persist this schedule, which means that it needs an ID and, therefore, is truly an entity, and when we retrieve a schedule we'll most likely be filtering which related appointments we want to look at. For example, today's schedule or this week's schedule. That would mean we want all of todays or all of this weeks appointments from a particular clinics schedule. It really does make a lot more sense to me to tie the appointments to a schedule than directly to a clinic. I like it. Now, let's see how this affects our design.
-
The Schedule Aggregate in Our Application
In our solution we've renamed the folder where we'd stored our aggregate. Instead of having an appointment aggregate folder, we now have a ScheduleAggregate, which includes the Schedule class, the Appointment, as well as the other classes that are involved in this Aggregate. Looking at the Schedule class, it has very few properties. The most important one that we're going to use to look these up is the ClinicId and, in the case of our application, we're starting out with just a single clinic. It also has a DateRange type that defines which set of appointments this particular instance covers. We don't want to have to always get all of the appointments, but notice that the DateRange isn't persisted, really we've got a note there that lets us know it's not persisted. The reason is because the DateRange doesn't define a schedule, the schedule exists across all time, but when we're actually interacting with schedules in our application we do want to constrain it by a DateRange, so today's schedule or this week's schedule, etc., so whenever we create an instance of the schedule we want to apply the DateRange and say this is the schedule for this DateRange, so it's also interesting, it's an example of where our domain model doesn't map exactly to our data model because we won't be storing --- the DateRange values in the database along with the schedule. Now, whether or not it's persisted really isn't a concern of our model, but that is something that we've defined in our persistence layer, we have actually applied an entity framework mapping specifying that this particular property doesn't map back to the database so not to worry about it in queries. A Schedules main purpose is to have a collection of appointments and perform some operations on or enforce certain invariants with those appointments. You see we've got this collection of appointments defined, then in terms of how we construct the schedule we have a couple of different constructors. We have one that allows us to create a schedule just given nothing that's required by entity framework and we have a separate one that will let us define one given an ID and a DateRange. In the repository that we're going to build we're also going to need the ability to add a set of existing appointments to a particular schedule instance. We've got this AddExistingAppointments method in the Schedule type, but Steve and I have both agreed that this --- has a little bit of a code smell to it. It's bothering us because in order to make it available to the repository we've made it public, but because it's public it's now part of the public interface of Schedule and it really is something we only need for the repository, so we'll go ahead and refactor that in a few minutes. Right, our public interface is really just about adding and deleting individual appointments and doing some business logic, like checking to see whether or not there's a conflict between two appointments. This repository specify logic --- we're going to pull that out of there. We're showing you this well flushed out type with our methods for handling all the complexity that we've identified for the Schedule Aggregate, but don't imagine that it came easily to us. We actually did a lot of refactoring to come to this point. For example, this method MarkConflictingAppointments, we actually had that logic originally inside of our AddAppointment method, but then we realized we needed access to it from the DeleteAppointments, and as we extracted it we were able to fine-tune it more and more. We've already mentioned that refactoring plays a huge role in Domain Driven Design. It also happens to be something that when you've done the refactoring and when you get to this point it's really satisfying. Right. Part of building out this domain logic also resulted in our creating a new method for our dateTimeRange called Overlaps, which allowed us to easily see where multiple appointments overlapped in terms of their time ranges. Yeah, and putting Overlaps into the value object made so much sense because it's not an easy bit of logic to figure out, so it's part of dateTimeRange and now any other time we use that in our application nobody has to, you know, go to stack overflow or whatever to figure out how to do that logic again, so we've just got a nice value object method in there to make like easy for the developers. Now that we have Schedule as the Aggregate Root, if we take a look back at the Appointment class, we can see that it's gotten a lot simpler. There's a few properties here that we want to point out, such as this TrackingState that we're using to determine whether or not a particular entity has been marked for deletion, and we'll talk more about that in a moment, and then we're also setting this IsPotentiallyConflicting property. This is another property that's not being persisted. We've told the context not to persist this back to the database, but we're going to use this in the user interface, so that when the user of the system adds an appointment that might be a conflict, they will see a visual queue that lets them know that maybe they should rearrange the schedule. Let's take a look at what that's going to look like in the UI. I'll create an appointment for Steve's cute dog, Darwin, so I'm going to select Darwin first and then I'll create it to conflict with an appointment that they already have in a different room. Notice that instantly I get these red boxes around both of the conflicting appointments, so inside my model that IsPotentiallyConflicting property has been set to true for both of those appointments and the UI is just responding to that. I can go ahead and delete one of those appointments and you can see with the appointment in Room 1 the red line is gone, that's because that appointments IsPotentiallyConflicting property is back to false, so the UI knows not to have the red line around it anymore.
-
Review Aggregate Tips
Let's step back a moment and review some of the things we've just learned about designing aggregates. First of all, aggregates exist to reduce complexity. You might not always need an aggregate. Don't add complexity just for the sake of using an aggregate. Another is that entities with an aggregate can only reference the root entity of another aggregate, but you can always use foreign key values as a reference to entities inside another aggregate. It's perfectly okay to use this and it'll avoid the need for when you go to save that aggregate for it to cascade its persistence into other aggregates. If you find you're needing to use a lot of foreign key references to aggregate children often, you may need to reconsider the design of your aggregate in your domain model. Another pointer was, don't be afraid to have an aggregate of one. In other words, an aggregate that only has one object in it. Finally, don't forget the rule of Cascading Deletes. Remember, one test for whether or not a particular object makes sense as an Aggregate Root is to consider whether deleting that object should also delete all of the other child objects in that aggregate hierarchy. If it doesn't, then you probably have chosen the wrong structure for your aggregate.
-
Glossary
Once again, we have covered quite a bit in this module. Let's review some of the terms that you learned in this video. The first thing we talked about was an Aggregate. An Aggregate is a group of related objects that work together in a transaction. While our Aggregate evolved through this module, we ended up with an Aggregate that was a schedule and a bunch of appointments. The Schedule became the root of that aggregate. The root becomes the entry point through which you do any work with the Aggregate and the root also is what's in charge of making sure that all of the rules that apply to that graph of objects are met. Each of the rules that describes the state that the system must be in in order to be valid is called an invariant, and the Aggregate Root enforces the invariants making sure the system is in a consistent state. Another term that we talked about is Persistence Ignorant Classes and you've seen how we've designed the entities and the value objects, so they're totally unaware of how they're persisted, and all of that persistence logic happens in a repository, which is the focus of our next module.
-
Resources
Here again, is a list of references that you can follow up with to learn more about things that we talked about in this module. Of course, again, Eric and Vaughn's fabulous books, Eric's website, the DDDCommunity website, which is always a great resource, and here are two other courses on Pluralsight that we mentioned in this module. Thanks. This has been another module in the Domain Driven Design Fundamentals course, I am Steve Smith and I'm Julie Lerman and there is still some more to go.
-
Repositories
Introduction
Hello. I'm Julie Lerman and this is Steve Smith. In this module of Domain Driven Design Fundamentals you'll learn about repositories, another critical pattern for Domain Driven Design.
-
Goals
We'll start by defining what repositories are and then we'll provide some tips for working with them, as well as talking about some of their benefits. There are different ways to define repositories and plenty of debate around their use. We will address some of these points. Then, we'll open up Visual Studio again, and show you how we've implemented some repositories in the scheduling app.
-
Introducing Repositories
Now, Julie if this were an in-person class I'd definitely ask for a show of hands, who has heard of the Repository design pattern? I would expect most hands to go up. I hope so too. I think the repository pattern is by far the most popular element of DDD to be practiced outside of Domain Driven Design. They can be valuable in some many applications as a ways to simplify data access and enforce separation of concerns. When I began learning about repositories and implementing them in my own software design, it had a huge impact on my application architecture. Along with automated testing practices, it really forced me to consider separation of concerns with each method and behavior I added to my software. You're going to make me stop and think. Okay, were does this really belong. Personally, I love the pattern and I've found it makes it much easier for me to write good, testable code. We're going to talk about using repositories within a DDD application, but if you want to learn more about the pattern itself, you can look in the Design Patterns Library, and I know Julie always discusses using them with Entity Framework in her Entity Framework in the Enterprise course. Any system that needs to persisted between restarts has some kind of persistent storage for the state of the system, like database. Many applications focus a great deal of effort on the mechanics of querying, fetching, and translating data to and from objects to the point where it distracts from the model that these objects are meant to represent, and having Adhoc access to the data source also promotes having developers query for any bit of data they want, any time they want, rather than using aggregates. This makes it pretty difficult to manage the consistency of aggregates by enforcing their invariants. At best, the logic for enforcing the integrity of the model becomes scattered among many queries and at worst, it's not done at all. Applying Model First design and separation of concerns means pushing persistence behavior into its own set of abstractions, which we refer to as repositories. Only certain objects like, specifically, Aggregate Roots, should be available via global requests. Repositories provide this access and, through omission, prevent access to non-aggregate objects, except through their Aggregate Roots. They give you the ability to constrain the data excess, so you avoid lots of random data access codes throughout your application. When you think about the life cycle of an object in your application you should consider two cases. In the first case, you have objects that are not persisted. These objects are created, perform some work, and then they're destroyed. In the second case you have objects that are persisted. These objects have a slightly more involved life cycle, since after the object is created it must be reconstituted with whatever state it had when it was last saved. Then, it can perform whatever work the application needs it to do after which, it may need to save its state to some persistent storage before finally being destroyed. You can use repositories to manage the life cycle of your persistent objects without the objects having to know anything about their persistence. We call these objects persistence ignorant. In his book, Domain Driven Design, Eric Evans speak quite a bit about repositories. They can be summed up by saying that, "A repository represents all objects of a certain type as a conceptual set…like a collection with more elaborate querying capability."
-
Repository Tips, Benefits, and Guidance
Here's some basic guidance you should keep in mind when designing repositories. First, a repository should have the illusion of a collection of a specific type of object. You'll be adding the objects to the collection, removing them, and retrieving objects from the collection, but that it is an illusion of a collection is important to keep in mind. When you interact with a repository these are the types of methods you'll be calling, add, remove, and retrieve. Your calling code doesn't care how the repository performs those actions, so in the repository you might have code that responds to a retrieve method, goes out to a database and gets data, but it could be getting data that's already in memory or it might be grabbing data from a text file in your computer. Another important recommendation for repositories is to setup access through a well-known global interface, that way developers that need to interact with the repository will be familiar with the common pattern for using it. Here's a simple repository interface that we're using in our solution. Our repositories implement it directly. Depending on the size and complexity of your software, you may have a few layers of interfaces. For example, if you anticipate having number of repositories for a Schedule aggregate used in different bounded contexts, you might want an ISchedule repository interface that not only implements the lower level interface, but defines some other methods or properties that every Schedule repository is required to have, regardless of the bounded context it might reside in. Because our repository acts like a collection, you'll want methods to add and remove objects to encapsulate the underlying data insertion and deletion operations. We've got these define in our IRepository. It is up to each concrete implementation to define how add and remove will actually work. While IRepository covers the least common denominators for all repositories, don't hesitate to provide methods in your Repository classes that select objects based on certain criteria. These should return fully instantiated objects that match the criteria. For example, in our Schedule repository we know that we'll frequently want to return a schedule containing appointments for a particular date, so why not just make that a method in the repository to make it easy for developers working with the repository to get that particular schedule instead of appointments, so with this GetScheduledAppointmentsForDate method we can control how the query is executed. In our case, we're using Entity Framework for our data access, so we've put our best Entity Framework expert on this task. Yeah that wasn't me. Now everyone using the repository can benefit from the expertise of that person, me in this case, where they aren't having to figure out the best way to write that logic. We're also avoiding the possibility of having multiple, randomly written methods throughout the application to perform this particular query. In addition to these specific tips for implementing repositories, you should also keep in mind these are more overarching tips. First, be sure to provide repositories only for Aggregate Roots that require direct access and next, keep the clients focused on the model while delegating all of the object storage and excess concerns to the repositories. Repositories can add a number of benefits to our application. First of all, they provide a common abstraction for all of our persistence concerns, which provides a means for clients to very simply obtain model objects and to manage their life cycle. They also promote separation of concerns. The domain logic and the user interface can both vary independently from the data and the backend data source that is used by the application. The public interface of a repository very clearly communicates our design decisions. Only certain objects should be accessed directly, so repositories provide and control this access. Another important benefit is that repositories make it easier to test our code. They reduce tight coupling to external resources like database, which would normally make unit testing difficult, but having repositories separate from client code and domain logic means that we can easily make improvements to optimize data access for this application, tuning for performance, adding caching behavior, etc. is all much easier and safer when the code for data access is all encapsulated in one or more well-known classes, and we have in fact gone through a number of refactors with the Schedule repository where we fine tune the querying and some other logic that's within there. Remember, your client code can be ignorant of the implementation of your repositories, but developers cannot. It's important that developers understand how your specific repository is implemented, otherwise, they can run into a number of different problems. We're talking about not just the developers who are implementing the repository, but also the developers who are using the repository. Some of the common repository problems that someone working with a repository might encounter are N + 1 query errors. This is where in order to display a list of rows from the database you end up calling one query to get the list and then a number of queries equal to the count of that list to fetch each item individually. Another one that I see a lot is when people are fetching related data. With Entity Framework are they using eager loading or lazy loading and, especially with lazy loading, there's a lot of developers who don't really know what to expect from it and just because it's easy and it just works they use it and then run into all kinds of problems because of it. Depending on how your data is structured sometimes if you're trying to fetch just one or two properties that are represented in a particular column in a data table, you might end up fetching more data than required if you pull back the entire row, which might include dozens of columns and a lot of actual data there, so these are things that knowing how your underlying data is persisted and how your repository is implemented, how those things work, can make a huge difference in your application.
-
Comparing Repositories and Factories
One thing to consider is that at first glance repositories and factories may seem very similar because they have similar responsibilities. In both cases, we use these patterns to get objects that we want to work with. However, factories are only involved in creating new objects, while repositories are used to find and update existing objects. Now they may not actually exist yet in memory in our application, but they exist somewhere else in persistence and we're just reconstituting them. It is possible and, in fact, not all that uncommon for a repository to use a factory as part of its creation of objects. Just remember when it comes to factories versus repositories factories should not be involved in persistence, so remember, factories, persistence, no. Repositories, that's all they do, so repositories, persistence, yes, yes, yes.
-
To IRepository T or Not to IRepository T?
For simple entities and aggregates having a standard set of CRUD operations on your repository makes a lot of sense. However, for less standard aggregates, like our Schedule, it may not make sense to implement these common operations. Originally, when we were using appointment as our Aggregate Root it made perfect sense for the appointment repository to implement the IRepository interface like the one you see here. Once we had the epiphany of creating a Schedule object to model the schedule and then to use it as our Aggregate Root, we reconsidered how we would implement this repository in the application. We actually had some heated discussions about what made sense for the Schedule repository and we ended up choosing to implement just two methods, one for retrieving all of the appointments for a given clinic and date, and another to update changes made to the schedule, including any of its appointments, and of course, as the application grows we would expect to add additional methods for fetching appointments.
-
Generic Repositories in DDD
If you do choose to create a generic repository interface, that doesn't necessarily mean you'll implement it generically. You might only choose to create implementations for Aggregate Root, which would comply with DDD recommendations. However, it can be convenient to create a generic repository of T implementation class that you can then use with any entity. We actually have this type of generic repository in our client patient management context. Recall that this context is pretty simple, employing mostly simple CRUD operations and, therefore, it didn't justify a DDD implementation. In that scenario, having a reusable generic repository works really well for our solution, since we won't need to create a separate repository for each entity, but it also means that any client code can use this repository to fetch data for any entity. We definitely don't want to use this in a DDD implementation because it means that it's allowed to bypass the Aggregate Root for interacting with data. If you've really liked the code reuse you get from having a generic repository implementation, one way to keep it from allowing too much access to the internals of your aggregates would be to use a marker interface, perhaps one that simply extends the entity interface. Then, you could update your generic repository to require this interface, rather than working with any entity. At that point, client code will not be able to instantiate the generic repository with NonRoot entities, so we are able to use our repository to restrict access to NonRoot entities from clients of our model.
-
Repositories in Our Application
In our veterinary application we've defined the ISchedule repository that we just saw and we've stored that inside of the core of the AppointmentScheduling bounded context. Next, the implementation of ISchedule repository is located inside of the infrastructure part of that bounded context inside of a project called AppointmentScheduling.Data. This project is responsible for all of the data access for this particular bounded context. Remember that Steve and I had made an explicit decision about ISchedule repository only exposing a method for update and a method that will explicitly get a schedule with its appointments for a particular date, so those are the only two methods that we have in here. The Update method again, this is where we're doing persistence, so we've got some persistence code in here that's related to the object relational mapper we're using, which is Entity Framework. What we're doing here is we're taking the State that we've stored in each of our objects, we have a custom enum called TrackingState, so in each of the objects we're able to set the state of the objects and then we're transferring that information to Entity Framework and letting Entity Framework be aware of what that objects state is. I also want to point out that updating the Schedule aggregate really only means updating the appointments in that aggregate. Notice that we're not doing anything with the state of the Schedule entity itself. That's because we don't need to update the schedule and the persistence layer. Its persisted properties, that's the ScheduleId and the ClinicId, shouldn't ever really change. If we are creating a new clinic and therefore, a new schedule we wouldn't be doing that within the context of this Schedule aggregate and Schedule repository, but with the appointments we're not sure what they're state is and therefore, we're iterating through the various appointments and depending on the --- tracking state that we applied, we're letting Entity Framework know what that particular appointment's state should be and then when we call SaveChanges Entity Framework will go ahead and run the appropriate insert, update, delete statements for those appointments. The other public method in here is GetScheduleForDate. GetScheduledForDate has a lot going on in here and some of this logic --- that may seem a little convoluted to you is result of the fact that we don't have explicit navigation properties to some of the information that we want to see about the schedule on our user interface. This is where you're going to see the repository really taking one for the team because we designed the domain the way we really wanted the domain to work. I'll just walk you quickly through some of what's going on here because this isn't meant to be an Entity Framework lesson, but what's important about this is demonstrating the fact that we designed the domain the way we wanted to without worrying about how we were going to persist this. When we get to the repository we found, oh yeah, because the navigation properties are in our domain, that was an explicit choice not having them there, but it meant that we needed to do a little extra ninja work with the object relational mapper in the end. We know the ClinicId and we know the date that we want to filter the schedule on, so all I need to do is query for the ScheduleId using that ClinicId and then create a new Schedule instance using that ScheduleId, the ClinicId, and the Date. Once we've got that, then we can go ask Entity Framework to go ahead and query for all of the appointments for that Schedule and for the Date on which we need those appointments, but there's still one more query that I'm doing and again, this is because of a lack of navigation properties. On the user interface we want to be able to display the name of the client, the name of the patient, and the type of the appointment. That information is not in the appointment. The appointment only has the IDs, so I'm actually running another query to pull back a list of strings that will give me that information for each one of the appointments. I'm doing that in the GetAppointmentHighlights method. Now just for the sake of this demo, I've embedded the T SQL right in here just so that you could see that I'm doing something special to get that information, and then pushing it back to my main method. That's something I would normally really put into a stored procedure, but once I get that data back, then we merge that data back together with all the appointments and then we can return that schedule fully loaded with all of its appointment information to whatever part of the application requested it.
-
Refactoring for Better Separation
So Steve, remember the last time we were looking at that AddExistingAppointments method of Schedule, we said we wanted to refactor that. We wanted to have it available for the repository, but we didn't want to have it as part of the public interface of our Schedule class, so why don't we go ahead and attack that now. Sure, this shouldn't be too difficult. On the Schedule object notice that we don't have any way to set the appointments from outside of the method and so, when we construct this from our repository we needed some way to provide the appointments and so, initially, we said we would just create a new method called AddExistingAppointments and we knew that we would only ever want to call that from the repository. Instead, what we want to do now is make it so that adding the appointments is something that's required whenever you create a schedule, and so we're just going to add that to the constructor. Right, and then once that's in the constructor we won't need to do it after the fact anymore. Exactly. We'll have to make a small change to how the repository logic works, but it won't be too bad, so let's just take care of this real quick (Typing). Now that we can take in the appointments in our constructor, we just have to be able to pass those in when we call from the repository, and since we're about to get rid of this method I'm going to go ahead and comment it out of Schedule, so we no longer have AddExistingAppointments, and that's going to break our repository, but we'll be able to fix it real quick. First, instead of getting back the whole schedule from this other helper method, the only part of that that we need is the Id, so we're going to go ahead and grab just the ScheduleId from a helper method and to get that we're going to use a new method that's slightly different than the one we had before. We're going to take the GetScheduleForClinicAndDate method, which returns a Schedule object, and change it so it just returns the guid with the ScheduleId because when we are making this call we only know the ClinicId. Now there's a few different ways that we could do that code. The first one would be to actually fetch back the entire --- Schedule entity and then fetch back the Id off of it. You can see that one shown here in this comment, however, it's more efficient to get just the Guid Id back from the database. Now that we've got the guid for this particular Schedule, we can jump back up to our GetScheduleForDate method. We don't need this line at the top where we were populating our schedule, we're going to actually do that last. Once we have the Id we just need to change a couple things where instead of looking for schedule.Id we're going to get scheduleId. Likewise, when we go to get the highlights, rather than passing in the schedule we'll pass in just the Id, jump down to the highlights, change it to take an Id, (Typing) and it only uses it in one place and it only needed the Id anyway, so it's now scheduleId and we're done with that. We don't need to call our AddExistingAppointments method anymore. Instead, we're just going to return a new schedule with the new constructor, pass in the scheduleId, the DateTimeRange. We've got a nice helper method for creating a OneDayRange with our date. Remember we need to pass in the clinicId at this point too and finally, that list of appointments that we created using Julie's Entity Framework ninja-ry. That is so much more efficient and more readable. I love refactoring. I just wanted to point out that helper method, something that we added into the DateTimeRange value object when we realized we needed it. We kind of realized we needed it after the fact, but that's how a typical thing that might come up with a really good conversation with a domain expert. Yeah, here's what that looks like, it's just a simple little static factory method that lets us take in a date range that is for a particular day because it might not be intuitive that when we way we need all the appointments for a particular date that the date range needs to be two different days. Yeah. I hated that code and I insisted that we had a simpler way to do this. Yeah, it's easy to not understand it, what do you mean I need to have two days of dates in order to get the appointments that are in one day, but, so this helps and it also helps if we're looking at the code with a domain expert because we can talk about a particular days appointments instead of saying, well, between midnight on the first and midnight on the second, which most people don't think like that. You know, they can put a man on the moon, but they still can't write good Date APIs. Refactoring is a critical part of evolving your domain with DDD and having tests in place is equally critical to ensure that your software continues to function as you refactor. Fortunately, our test pointed out that we'd forgotten to implement one last method call when we did this. After fixing this problem all of our tests passed and now we were confident that our refactor was successful. If you're unfamiliar with building automated tests, there are lots of great courses on testing right here on Pluralsight. You can even start with my course, Automated Testing for Fraidy Cats Like Me, and then move on to some of the more advanced courses that are available.
-
Glossary
In Domain Driven Design it's really important to remember that a repository is designed to work specifically with an aggregate root to encapsulate all the data persistence that happens for everything within that aggregate, but what's important, and specific to DDD, is that you don't create repositories for random entities that are within the aggregate, you need to focus on the aggregate root. You saw us do this with saving the appointments from the Schedule repository where Schedule was the aggregate root, so that's what really makes repository different when you're in DDD than perhaps when you've used it in other contexts. It's important when we're doing these transactions that they follow these ACID properties that if you've worked with databases in the past you should be familiar with, but let's review them real quick. We want our transactions with the database to be atomic, which means that either the entire transaction occurs or none of it does, and we don't end up with an aggregate, for instance, where some its members have been persisted and others have not. We want it to be consistent, which means that the constraints on the data are applied and that's kind of what we just talked about with the aggregate root enforcing those rules. We want it to be isolated so that if two different aggregates are being committed at the same time they don't conflict or step on one another as they're being committed, we need them to occur in a sequence. Finally, they should be durable, which means that once the transaction has occurred, if something were to happen to the system, if the memory, you know, were to be wiped or the computer rebooted, we would be able to reload the system and reload that state because it was stored in some durable fashion.
-
References
Don't forget these great resources you can follow-up with to learn more about Domain Driven Design. Thanks. This has been another module in the Domain Driven Design Fundamentals course, I'm Steve Smith, I'm Julie Lerman, and thanks for watching.
-
Domain Events and Anti-corruption Layers
Introduction
Hi. This is Steve Smith and this is Julie Lerman. In this module of Domain Driven Design Fundamentals you will learn about Domain Events and Anti-Corruption Layers, two patterns for decoupling how the domain model communicates internally and with other systems.
-
Goals
We'll start with Domain Events, which can be used to separate concerns, allowing different areas of the application to evolve independently and sometimes helping with scalability as well. Then, we'll take a look at Anti-Corruption Layers, which can minimize the amount of work involved when two sub-systems, using two different models, need to communicate with one another. Let's get started.
-
Introducing Domain Events
Domain Events are a critical part of a bounded context. They provide a way to describe important activities or state changes that occur in the system. Then, other parts of the domain can respond to these events in a loosely coupled manner. In this way, the objects that are raising the events don't need to worry about the behavior that needs to occur when the event happens and, likewise, he event handling objects don't need to know where the event came from. This is similar to how repositories allow us to encapsulate all of our data access codes so the rest of the domain doesn't need to know about it. We can also use events to communicate outside of our domain, which we'll look at in just a moment. Another thing that's worth remembering is that domain events are encapsulated as objects. This may be different from how you're used to coding events, it certainly was different for me when I first started learning about them. For example, in a user interface events are more commonly written as some form of a delegate in another class, but here they're first class members of the domain model. Right. Although you can implement domain events using techniques like the Event keyword in C#, the domain events themselves should be full-fledged classes. In fact, all of these parts of Domain Driven Design are defined as objects in our domain model. Vaughn Vernon describes domain events simply saying, we should use the domain event to capture an occurrence of something that happened in the domain. The domain events should be part of our ubiquitous language. The customer or domain expert should understand what you're talking about when you say, when an appointment is confirmed an AppointmentConfirmed event is raised. You may already be familiar with the idea of events from working with user interfaces. Many foreign-based user interface clients like VB6, .NET Winforms, and ASP.NET web forms, like the one shown here, make heavy use of events and event handlers. In this example there is a single page with a single button and in the code behind you can see that two event handlers have been written, one to handle when the page has loaded, and one to handle when a button click event occurs. Events are helpful because they let us avoid a lot of conditional logic. Instead, we can write code that signals a certain thing has happened and we can have other code in our system listen for these signals and take action accordingly. In this kind of code you don't have a separate class for a load event or for a click event and it may take some getting used to that now in our model we're going to create a whole class to represent an event. Domain events offer the same advantages to our model as the events in the user interface. Rather than having to include all of the behavior that might need to occur whenever the state of one of our objects changes, instead we can raise an event. Then, we could write separate code to deal with the event, keeping the design of our model simple, and helping to ensure that each of our classes has only one responsibility. Essentially, a domain event is a message, a record about something that occurred in the past, which may be of interest to other parts of our application or even other applications entirely. In the UI we think about the logic of our event handlers as happening in response to certain events occurring in the application. When the page has loaded, run the code in Page_Load. When the user clicks the button, run the Button_Click event handler. Be especially attentive to these kinds of phrases when discussing the application with your domain experts. These frequently refer to situations that are important to the domain expert, the system or the user, and which might therefore be worth modeling as domain events. You may also discover behavior in the application that will benefit from being treated as domain events that the domain expert isn't initially aware of. Remember that domain events represent something that happened. Since we can't generally alter history, this means they should be immutable. It's a good idea to name the event using terms from the bounded context ubiquitous language describing clearly what occurred. If they're fired as part of a command on a domain object, be sure to use the command name. Here's some examples. Depending on the application, it might be important to have events to represent when a user has authenticated, when an appointment has been confirmed or when a payment has been received. Be sure to only create events as you need them in your model. You should follow the YAGNI principle, that's, You Ain't Gonna Need It. In other words, don't create domain events unless you have some behavior that needs to occur when the event takes place and you want to decouple the behavior from its trigger. You really only need to do this when the behavior doesn't belong in the class that's triggering it. Here are some more things to keep in mind when you're creating domain events. We've already mentioned that domain events are objects, but to be more specific, each domain event should be its own class. It's also usually a good idea to note when the event took place, since frequently the code that's handling the event might run sometime after the event occurred. It can be helpful to create an interface that defines the common requirements of your domain events like this one, the IDomainEvent, that's capturing the date and time the event occurred. Also, when you're designing your event you need to think about the event-specific details you want to capture. If it's related to an entity, you might want to include the current state of the entity in the events definition. Think about what information you would need to trigger the event again. This can provide you with the set of information that is important to this event. Similarly, you may need to know the identities of any aggregates involved in the event, even if you don't include the entire aggregate itself. This will allow event handlers to pull the information back from the system that they might require when they're handling the event. Ideally, DomainEvent objects should be lightweight, so you want to be sure you capture sufficient information to handle the event, but not so much that the Event object itself becomes bloated. Since the Main events are immutable, they're typically fully instantiated via their constructors, and since they're simply noting that something has happened in the system they don't usually have any behavior our side effects of their own.
-
Domain Events Demo in a Simple App
We've put together a simple console application that we're going to use to demonstrate the value that domain events can have in your application and the idea behind this is to strip things down to as small of a level as possible and then we'll also show how domain events are playing a real role in a more real world way in our veterinary scheduling application. If we look at this program, which I've already got running, it's very simple. Here's the output. You can see that it starts with Console to say it's starting the application and then it creates the service, uses the service to schedule an appointment, and then we'll also go ahead and create an appointment without the service and confirm the appointment without the service and then write out that it's done. If we look at the output, I've added some ConsoleWriteLines inside of these classes for the sole purpose of you being able to see what is going on here and when these things are happening, so you can see that Appointment::Create was called, you can see notification email was sent, you can see that the User Interface was updated, the entity was saved, and that is what the service call did, and then when we create the appointment, the second appointment without the service, you can see more of the same type of things. Right, so that the WriteLines have nothing to do with the lesson of domain events. They're just there so that we can see what's going on inside of this little demo. Exactly. If we look at the ScheduleAppointmentService first, it's pretty simple. All it's going to do is create an appointment and then save it using our repository and, in this case, everything is sort of hard coded, again, being interest of simplicity, but now if we look at Appointment this is where we really want to focus our attention. Let's look at this Create method, which was sort of a static factory method. It's similar to the static factory method that we use in our scheduling application. You can see here, on line 29, there's a comment that says, we're going to send an email to notify them this appointment was created. Imagine, if you will, that there is 5-10 lines of actual network code there that's setting a from address and a to address and the subject and creating the email and all of that stuff. To simulate that we're just putting out a Console.WriteLine to say that this email was sent. The WriteLine is not important to the logic that's being performed. Similarly, the update user interface is the same idea. Imagine there's some code here that's drawing some pixels on a form or, you know, spitting something out to an HTML output, it's logic that probably doesn't belong inside of our appointment entity, and we'll talk about that in a moment, but we're simulating it here with a comment and a WriteLine to say it happened. Now Julie, you and I both agree that our Appointment class is no place for us to have the logic for sending emails and updating the user interface, right? Right. That's all kind of network stuff and a _____ shouldn't even need to know what infrastructure things happened in response to creating it. Exactly. Now if we were going to refactor this without using any kind of events, what we might do is pull out this code into a helper method, just to make it so that the Create method was smaller and had fewer responsibilities. We might extract that code further into another class and put that class inside of our infrastructure project and have that be where things about email occur. Right, and then we could just call out to it or, yeah, exactly, or inject it through an interface, but again, if the Create method has to know about it and has to call it, then you've still got this problem where you're appointment has to know everything that has to happen whenever you create a new appointment and that's --- maybe more responsibility than it should have. Exactly. There's a principle called the Hollywood Principle that says, don't call us, we'll call you. Now I never heard of this principle until you mentioned it to me recently. It's awesome. It's a good one. It's closely related to the dependency inversion principle and the idea here is that rather than us, you know, the appointment class, calling something to send an email and something to update the UI, we're going to let those things call in to our system or reverse that dependency and the way we can do that is we can just trigger an event and the event will then get picked up by whatever handler is interested in this particular behavior, just the fact that a deployment was created, and those things can call back into Appointment if they need to, to get any information they need, and react to that event. Now let's take a look at what this exact same program would look like if we used some events. Here's the same application and when it's running you'll see that it has pretty much the same output. I've modified it a little bit so that we can see the source of where the different messages are coming from because I wanted to call out that some of these are coming from a handler for email, some are related to the UI, some are from the database, to make it more clear where these actions are taking place as the code runs. You can see it works pretty much the same way, but let's take a look at the appointment class and see what's different about it. If we look at the Create event, you'll see that it doesn't have any calls in there to send emails or update the UI. Instead it has just this one line of code that says to raise an AppointmentCreated event and it instantiates a new one of those events and passes it in the appointment and that makes much more sense for the appointments factory method because really the factory method should just be about creating the appointment and not all of that other stuff. It's important that the event happened, so we want to be able to test that that occurs and the nice thing is that with this particular way of raising events, this is Udi Dahan's method that he advocates for doing domain events, there are a number of other ways that you can do them, but in this case, we can go and we can unit test this fairly easily and simply register this event to occur right inside of our unit test, call that create method, and then verify that we got this callback here that we specified did in fact occur. If we run this test, you'll see that it passes (Typing). Now the magic of how this works, I'll show you briefly, and then we'll look at an example in our scheduling app, is simply that we define a couple of interfaces, so we have an IDomainEvent interface that just says when it occurred, and we have an IHandle interface that is anything that handles a particular type of event and it just has a Handle method. Now the logic for sending emails and notifying the UI has been moved into these Handler classes and, for instance, you can see that the NotifyUIAppointmentConfirmed handler has this one method with one line of code to do that, and obviously there'd be real code here, again, this is just console code to make it so you can see what's happening. Let's look at the events themselves. We've got an AppointmentCreated event and, in this case, you can see we added a little bit more logic in here to take, you know, what do we need to know in order to react to an AppointmentCreated event? We need the appointment, so we've got the appointment. Also, remember the interface has the dateCreated, so we're also passing in the dateCreated along with Appointment. The class that does the actual work of registering domain events and then making sure they get called is this DomainEvents class. Again, this is written by Udi Dahan and you can see it's not very long, it's about 50 lines of code and it has just these three methods where you can register for an event, you can clear all the callbacks, which is mainly useful when you're doing testing, and then you can choose to Raise an event, which then will go through and find every handler and call its Handle method. Having this logic already existing that we can leverage in our apps is really a huge thing because the leap that you need to make to understand domain events is going to be a smaller leap right? You don't have to worry about structuring all this logic and we can just take advantage of it. Yeah. The nice thing I like about this approach is that it doesn't pollute our code terribly much. If we look back at our actual domain model, it's very clean. Right? We just have one line of code to raise each one of these events. The events themselves are very simple, the handlers are very simple, so everything is simple, and it has one responsibility, which means it's easy to test and easy to follow. The last part that makes some of this work, in this particular case, is we're using an IoC container and so, if we jump back to our main program class you'll see there is this InitIoC call and all that does is use our container, in this case it's StructureMap, and there's one line of code in here that really makes this work, and it's this line right here, which says, ConnectImplementationsToTypesClosing the typeof IHandle, and what that will do is it'll make it so that that DomainEvents class knows about every class in your solution that implements the IHandle T interface and then when an event is fired that needs to be handled by something that says, hey, I handle that particular type of event, that domain events class will be able to locate it in the container and call its handle method with the details from that particular event. Don't worry too much about inversion of control here, we just wanted you to see how the puzzle pieces go together, but there is a great course on Inversion of Control by John Sommes, right here on Pluralsight that you can watch to learn more. You can see this is starting to get a little complicated, just in terms of structuring the domain events, and really only if you've never seen this before. Once you've done it a few times, and especially with Udi's DomainEvents class, it does make it a lot easier to do, but that's why we wanted to show this to you in this console app where things are really simplified. Now let's go over to our appointment scheduling application and see how we've implemented domain events within there.
-
Domain Events in Our Application
Now let's look at how we're leveraging domain events in the veterinary scheduling application that we've been working with. In our Appointment class we're going to raise a few events when certain changes occurred with the appointment, so if we scroll down and we look at the UpdateRoom method you'll see that it raises an appointmentUpdatedEvent and likewise, updateTime or is this an appointment updated event. Finally, when we confirm an appointment down here you can see that we also raised a different event, in this case it's an appointmentConfirmedEvent. UpdateRoom and UpdateTime will trigger the same event, whereas the conform triggers a different kind of an event, so all the things that are going to happen in response to what appointmentUpdatedEvent, whether it's the room or the time will be the same logic. Let's take a look at the AppointmentUpdatedEvent and this is similar to the one we saw in the simpler console app. We're passing in the appointment, but here we're not bothering to pass in the date time we're just saying it's happening now, so we're just going to set that value right there in the constructor. Now let's look at some of the ways that these events are used. First, let's look at Schedule and Schedule again, is our aggregate root and the way Schedule is going to use these events is it's actually going to register itself as a handler for this AppointmentUpdatedEvent. Another way to look at that is the Schedule is saying, hey, if anything happens to trigger the AppointmentUpdatedEvent talk to me. Right? That's --- that Hollywood agent again, --- the don't call us, we'll call you guy. If somebody wants to hire that guy, talk to me about it. If something happens to update the event, come talk to me about it, I know what to do. What this line of code here is doing is saying, whenever an AppointmentUpdatedEvent occurs I want you to call the Handle method and if we scroll down to the bottom here we'll see that this Handle method simply calls MarkConflictingAppointments and part of the reason why this is necessary is because of that design decision we made to have these one way relationships. Because our Schedule is our aggregate root, it of course, has this member property of appointments, so it can go through and look at appointments and edit appointments any time it needs to. However, when something happens inside of Appointment there's no way for an individual appointment to reach out and call the Schedule on which it exists because it doesn't have any kind of a navigation property to get to that schedule, it only knows about the ScheduleId, and so in order for it to get a reference to a Schedule we would have to have it get a repository and pull that schedule out of the repository and that's not the kind of logic that we want to have inside of our domain model. Right, so by using DomainEvents.Register inside of schedule we're wiring those to up where that navigation property doesn't exist. Now the other thing that you can do that's kind of cool is you can also wire up to those events from other places in your application and we'll see how that works in just a second. Let's go ahead and run this code and see how these breakpoints occur when we change the time of an appointment. So let's look at Tinker bell here. We'll double-click just so we can see a picture. Ahh. Ahh. Now we've seen that Tinker bell and Radar are double booked here at 9 o'clock, so we're going to move one of them down so that it's no longer double booked and when we do that it's going to fire off a few of these breakpoints. The first breakpoint we hit is the DomainEvents.Register because one of the first things that's going to happen in our code is we're going to have to create a schedule and get all these appointments as it repopulates the backend for this particular page, and so we now have that registration in place. If we jump past that we'll see that next we're going to update the time for this appointment and we know that that's going to also raise this AppointmentUpdatedEvent that you see, and so if we jump past this one to the next breakpoint we'll see that now we're in the Handle method of Schedule, which is responsible for checking for appointment conflicts. In this case there are no conflicts, everything just flowed through nicely. Why don't you make a change now Steve where we're going to actually trigger the code for the conflicts. Sure. So one of the interesting things that we want the system to do is notify the user whenever there's a conflict and the conflict in this case would be that we've got the same animal scheduled in two different rooms at the same time. If we have Julie and Sampson in Exam Room 2 at 11 a.m. and we want to add them in Exam Room 1 at the same time (Typing), we should see that that will be a conflict and let me step through these breakpoints real quick. This'll be indicated by the red box that you can now see around both of them. If we then move one of them out of that spot, so if we take the Exam Room 2 appointment and move it up to 10:30, we'll hit those events again, and the end result will be that they are no longer in conflict. The other thing that's cool that you can do with these events is actually the way that it's showing those red lines and some other things is by capturing those events at the UI level. If I take Sampson and I put him back at 11, we're going to get an alert down here now at the bottom right because I'm not in the middle of breakpoints telling us that he was updated and every time I change that appointment we'll get another one of those alerts. Right? Then if I drag it off to some place where it's not, you see that we still get another alert, but the red box goes away. The way that's happening is we've got an event handler and this event handler is in our web project, but it's actually handling that same event, that AppointmentUpdatedEvent, and it's implementing the IHandle interface, which means that we've got our IoC container, in this case, StructureMap, is going to automatically call this anytime that event occurs. With Schedule we didn't do that, just to make one quick point here, because we explicitly registered that event we didn't have to implement IHandle on Schedule, so there's no interface polluting schedule with that to say that it, you know, our entity is also an event handler, we're just doing that by hand inside of it. At the UI level we want to be able to add event handlers for anything just to see if there's something that we want to be able to do in response to them at the UI level or at other areas in our application. Maybe we want to send an email, maybe we want to log something to a log file. Whatever it is we can add handlers anywhere. Right, so now we've got two somewhat disconnected things responding to the same event and I say disconnected because one is our backend, the server-side code has logic to respond to AppointmentUpdatedEvent, that's what the Schedule aggregate root is doing, but now we're also having this UI code handle the event as well. Right, and in this case the way we're talking to the browser, which is on the client, is through SignalR and that's why this is in a Hubs folder because that's where SignalR Hubs are located. SignalR is built into ASP.NET now and allows you to have two directional communication between your client and your server, so your Javascript code in your browser can call directly into your server, which isn't all that interesting, but more interesting is that your server can call directly out into the clients Javascript code, and that's what we're doing to show these alerts. Yeah, SignalR is really cool, but you don't need to know SignalR in order to do Domain Driven Design. It's just that DDD is setup in a way that it really takes advantage of that kind of functionality. Now if you do want to learn more about SignalR, of course you can find a great course on it right here on Pluralsight. Alright, so let's look back at Appointment. We can see that we've kept this entity very clean by limiting what happens when certain changes occur to just raising events and then the other things that are responsible for dealing with those events are somew here else in the application. With this one little bit of logic to say, okay, when we update time we also want to raise this event, you saw all of that other magic happening, the Schedule checking for conflicts and SignalRs telling us to change the UI, you might even add more responses to AppointmentUpdatedEvent. Our little Appointment class doesn't need to know any of that. All it needs to do is say, hey, I updated. Right, and remember this is a fundamentals course, so we know that there's not a ton of real business logic going on in this, but that's to keep it in a manageable size for you to understand. In a real application with real complexity there's going to be a lot more going on and being able to encapsulate when things change in events, so that you can separate out that logic and test it in isolation will make it much easier for you to tame the complexity of your applications. Definitely and that, you know, and that's why we're here with Domain Driven Design right? Exactly.
-
Domain Event Boundaries
One last thing to remember about domain events is where they will ultimately be consumed. You just saw that we've defined the domain events as part of our core domain model. One way to keep these domain events from getting bloated is to make sure they only have the information required for their particular consumer, which'll mostly just be within the same domain model. When your bounded context needs to expose events beyond its boundaries it's likely you'll want to translate your domain events into some kind of cross domain type. This is because your domain events may not include enough information within the event itself for the external system to efficiently act on it. In our example, if we were to expose an event that included an appointment, it would only include the Ids for the Doctor, Patient, Client, Room and AppointmentType because that's how we define the appointment. These Ids would, at worst, be meaningless outside of our bounded context and, at best, would require the external system to make additional requests to get the details about these other objects and that would be pretty inefficient. To avoid this we can handle the domain event within our domain. For instance, in a service like this one and expose a different event to external systems via some kind of messaging infrastructure. In this example you can see that we are creating a custom event using a dynamic type and we are populating it with the actual details from the appointment. Then we are publishing this message for external consumption. You can imagine that inside the Fetch Client name, Patient name, and Appointment Type, etc. section we're using a repository or something similar to pull that information out of our domain. It may be valuable to do something similar when raising events to your UI layer from your core. In the example we just completed we wrote a domain event handler directly in the UI. This works, obviously, but if you find that this couples your UI too closely to your domain or if the information your UI event handler needs goes beyond what your domain event carries, you may want to raise a custom event specifically designed to provide your UI layer with the information it requires. You can do this using the same approach that we just showed for cross-system events.
-
Anti-corruption Layers
The last topic we want to discuss in this module is anti-corruption layers. An anti-corruption layer, as the name implies, helps to prevent corruption in your domain model. Right. Just like super heroes help to fight corruption, these layers provide a sense of security to your model when it needs to interact with other systems or bounded contexts. Returning to our mine map you can see that the Anti-Corruption layer is used to translate and insulate as part of a context map, mapping between a bounded context and foreign systems. When your system needs to communicate with other systems, especially Legacy applications that weren't written or modeled as well as your current system, you need to be careful not to let assumptions and design decisions from that system bleed into your model. For instance, if the other systems model includes a customer, even if that customer refers to the same actual business customer, it's likely that it will be modeled differently than a customer in your system. It's best to have a layer that can translate to and from other systems models. In DDD this is the job of an Anti-Corruption layer. Right. Like we mentioned in the beginning of the course, even other bounded context in your own system may be different enough to merit having an anti-corruption layer in place to protect the two distinct models from one another and, of course, legacy applications frequently use very different models from newer systems. An anti-corruption layer isn't a design pattern, however, it's usually comprised of several design patterns. The job of the layer is simply to translate between the foreign systems model and your own. In addition to translating the objects themselves, the anti-corruption layer can also clean up the way in which you must communicate with the other system. It may provide a façade to simplify the API or an adapter to make the foreign system behave in a way that is known to your system. You can learn more about these design patterns in the design patterns library on Pluralsight. We're usually most concerned with having an anti-corruption in place when communicating with Legacy systems. Eric Evans notes why that's important. "Even when the other system is well designed, it is not based on the same model as the client. And often the other system is not well designed." Since this is a fundamentals course, we're not going to dig deeply into anti-corruption layers because they can be fairly complex, as well as very customized to each scenario, but here's an example structure of one, which comes from Eric Evan's book showing how an anti-corruption layer can connect your beautiful system on the left with a not so beautiful system on the right. I really like this diagram, I think Eric had some fun putting it together. Gee, what gives you that impression Steve? Of course, in the middle you can see how the anti-corruption layer is using a façade and some adapters, but on the right it's protecting us from a big, Complicated Interface, some Messy Classes, and some things we just don't even want to know about. Right, and of course your own system is comprised of an Elegant Class, a Very Expressive Class, and of course, Even More Good Stuff, and maybe even some stuff we should be refactoring as well. There's no one way to create an anti-corruption layer. Whatever you need in order to insulate your system from the systems it works with is what you should put inside of this layer, which should allow you to simplify how you interact with other systems, ensure that their domain decisions do not bleed into your design, and ensure any necessary translation is done along the way.
-
Glossary
We've covered some new topics in this module and there's a few new terms that we want to make sure we review. Domain Events are a type of object that actually represents something that occurred within the domain that other parts of the system may find interesting and want to tie their behavior to and this is a great way to keep you system decoupled and to keep you individual objects simpler because they don't have to know about all of the behavior that might occur when some of it takes place. We also referred to the Hollywood Principle, which can be summed up as, "Don't call us, we'll call you." A dependent object does not have to worry about communicating with a controlling object, such as an aggregate root. Leveraging domain events, we can ensure that the controlling object is able to call back to the dependent objects, so we setup our Schedule entity to receive callbacks from appointments whenever they needed to change, there is potentially conflicted property values. Inversion of control often just referred to as IoC, is a pattern that lets us construct the code to apply that don't call us, we'll call you pattern. You still have to use an IoC container structure map to coordinate calls to event handlers based on where we define that domain events should be triggered and we also pointed you to a much more in depth IoC course on Pluralsight. Finally, we looked at anti-corruption layers, which can be used to ensure that our model that we work so hard to produce doesn't become polluted by the models of other systems we work with based on objects that wanted to return to us or the type of API that they want us to code to, so we put anti-corruption layers in place to shield our model from those other systems or bounded contexts that we might work with from our bounded context.
-
References
Here are some references where you can learn more about the topics that we covered during this module. As always, these two books are an excellent reference, as is Eric Evans website. Here is a link to Udi Dahan's implementation of Domain Events, which we demonstrated in this module, and also on Pluralsight here are several other courses where you can learn more about some of the topics we discussed. I am Steve Smith and I'm Julie Lerman and thanks for watching this module of our Domain Driven Design Fundamentals course.
-
Reaping the Benefits of Domain-Driven Design
Introduction
Hello. This is Julie Lerman and this is Steve Smith. In this module we're going to wrap up our course on Domain Driven Design Fundamentals by showing how we can reap the benefits of our design when it's time to add additional functionality to the system. Let's get started.
-
Goals
In this module we'll first review our current system design and see how it incorporates DDD patterns and practices. Then, we'll circle back to our customer, Michelle, to see how the new Vet Clinic Appointment management system is working out. During that quick conversation we'll learn about a new feature and we'll show how we can implement that feature. The main benefit of our design choices is the ease with which the system can be extended and maintained in the future and we hope you'll agree that adding to the current design is quite straightforward.
-
Our Current System Design
So far our system is pretty simple, though it's fairly complex as most course demo apps go. We have a single aggregate for a schedule, which contains a number of appointments. We limit access to the schedule through the ScheduleRepository class, which is responsible for retrieving and storing the schedule in our database. We've identified a couple of Value Objects that allow us to better model concepts in the domain and we're making use of Domain Events to allow our domain, and other parts of our system to respond to changes in the state of our model. It's taken us a while to get to this point, but now that we're here the design of the system is very clean and it reflects the customers domain, as well as we've been able to model it so far. Of course, given some time constraints. Yes, we do have to ship the app, I mean this course, at some point. Alright, of course, as we build on this application our model would continue to evolve, but we've shown you techniques you can use to ensure that you can grow the application without being overwhelmed by the complexity you're trying to model. Actually, as it turns out, the customer does have one more request for us. She said something about customers forgetting their appointments. Let's have another quick conversation.
-
Discussing a New Feature With the Domain Expert
Hey Michelle, great to see you. How are things going with the new scheduling application? It's been fantastic. We're really able to see very easily who's scheduled each day and book new appointments and move things around as needed and the front desk folks really appreciate that it highlights the appointments that are conflicting or are confirmed, that makes it much easier for them, but one thing that's still a problem is the fact that sometimes our clients forget their appointments. It probably happens at least a couple of times every day and our staff really don't have the time to call every client to make sure they remember ahead of time. So, you'd like the system to call them then? Well we understand there are services that'll do that sort of thing and we might move to that eventually, but for now if we could just send an email that would probably help remind clients to put it in their calendar. Oh, okay, so do you want an email to go out when the schedule the appointment or on the day before they're scheduled to come in or maybe even both? Oh wow. If we could do both that would be great. One to let them know when they have booked, so that they know that we've got it in our schedule, and another one to remind them that they have an appointment the next day, just in case they forgot. That shouldn't be too hard. Our model already handles certain events that occur, like when appointments are scheduled, and appointments already support being marked as confirmed too. Sure, and I think all we really need to build that'll be new is some kind of service for sending the emails and some way for clients to click a link in the email so they can confirm the appointment. Since it's email it shouldn't be a problem to send these out the day before even if that day isn't a weekday or a workday right? Hmm. No, I think that should be fine. It shouldn't hurt anything to send an email on a Sunday or a holiday and of course we'll ask our clients to opt into these reminders so we're not sending anything unsolicited. Sounds good. We'll get started and should have something for review real soon.
-
Planning the Implementation
Before we get into the gory details of the implementation we just want to make sure that you understand the very high level of what we're doing here. The first thing is triggered when the appointment is scheduled and in response to that our system will send a confirmation email to a client. Once the client gets that confirmation email they can click a link to confirm that they're going to make it to the appointment and the system will then mark that appointment as confirmed so that on the schedule the staff will see that it's got a green box around it and they should expect the client will actually show up. What's nice about this implementation is that it benefits so much from a lot of the infrastructure we already have in place and thanks to your DDD based architecture it's just as easy to add in a few extra features that we need to make this work. As we go through this you'll see us using some existing and some new Domain Events, some Application Events, a number of Event Handlers, and Services. One new tool you'll see is something we haven't talked about yet, Messaging Queues to communicate between separate applications. The application we've been working with will need to communicate with a public website that the customers will interact with when they confirm their appointment.
-
Adding a Message Queue Between Our Apps
The first part of the process happens when the appointment is scheduled and you've already seen our AddNewAppointment method inside of the Schedule aggregate root, so we're just adding a little more logic within that method and raising an AppointmentScheduledEvent. Now we have a service that's wired up to handle that event that's called the RelayAppointmentScheduledService and what that does is it takes in data that comes along with the event, which is appointment data, and it creates its own understanding of an Appointment object using the data. Then it calls into our messaging logic and its Publish method along with this appointment and what Publish does is talks to our message queue, which happens to be SQL Server Service Broker and it takes that appointment, formats it as JSON, and inserts it into the queue. Before we go any further, we did just mention something new, which is Message Queues and we just want to talk about that a little bit and it's a pretty advanced topic for this fundamentals course, so we're going to talk about it at pretty much a high level. Yeah. Message Queues are nice to use between applications for a number of reasons that it can help decouple them and make it so that one of the applications can just drop off something into a Message Queue and continue on with its work and not have to worry about what happens to the message after that. Right, or if whichever application or applications it's trying to communicate with it doesn't need to worry if that application is available and listening at that very moment. The message can sit in the queue and when the other application is ready to grab it, it does. With a Message Queue we're really just dealing with a single message and one application drops it and the other one takes it and then the message is gone. Yeah, and there's lots of different implementations of Message Queues that you can find online. Some of them are free. Most of the cloud services that are out there now have these types of things built-in as well. What we're using in our demo is SQL Servers Service Broker, which is kind of, we like to think of it as the poor man's Message Queue, so you'll see us interacting with the Service Broker. Yeah, some of the other ones that you can find online are RabittMQ, MSMQ, which is built-in to Microsoft Server, Amazon Web Services has queues, as does Azure. What we're doing here is dealing with a single message at a time in something of a silo app, since we control both applications that are communicating with each other, but sometimes you need to have a lot more flexibility than that. You might actually have a number of applications that are interested in that message and you may not even know in advance or control those applications, so this is when something called a Service Bus comes into play. Right, so you'll frequently hear about something called an Enterprise Service Bus and there's again, a number of examples of these that you can find available and what the Enterprise Service Bus does, it usually sits on top of Message Queues and other features and one of the responsibilities it has is making sure that messages get delivered to the different applications that care about that message. It might even be an application that didn't even exist or you didn't know about when you were first setting up the Message Queue, so even at that point because Service Bus allows you to decouple the routing of the message, it's possible to go ahead and hook up other applications to listen to the queue. Right, so you'll see in our scenario that we have our Scheduling application raising an event that an appointment was created, but it might be that maybe in the future we would want to add some other application that wants to react to that event, like we can publish it to Facebook, hey, I'm going to go see the vet. Exactly. If we have a Service Bus we could simply wire up in our Service Bus for this new Facebook notifier service to also pick up that event, but with just Message Queues, as you'll see in our implementation, we would have to change our scheduler application to know about this new app and write to its queue because we don't have any advanced routing, everything's hard coded in our simple scenario. We've got a list of some of the more common Service Buses, such as NServiceBus, Azure has a Service Bus, Mass Transit and Mule are open source Service Buses, and Neudesic has one called Neuron ESB. Now we also wanted to point out that Peter Ritchie has a great blog post on just an overview and comparing and contrasting using Service Buses and queues and brokers, and there's a bit.ly shortcut to that URL. Let's see what this looks like in the application. We'll go ahead and create a new appointment. Let's bring Samson into see Dr. Jones again. There's the appointment. Nothing has changed from the perspective of the user, but what's changed in the background is that now we have this message sitting in the queue that got pushed into the queue. There's the message. It's just a string, JSON formatted. It shows the date and the time of the event, and the client name, and there's more details. That's sitting in a very specific queue that we setup for notifying users of appointments that have been scheduled, waiting for some other application to come and grab it.
-
Combining Message Queues, Events, and Services
The next step in our workflow is to send an email to the client letting them know about the appointment that they've just scheduled. We can't do this easily from our Scheduler application because we need for the user to be able to click on a link that specifies that they want to confirm their appointment and so it needs to be accessible publicly. We've decided to put this on the veterinary clinics public website and so that will be responsible, both for sending the emails and for hosting the link that the customer will click. The way this works is that public website has a method called CheckMessages, which runs on a timer and periodically checks the Message Queue to see if anything new has arrived. Once it finds a message on the queue, it will retrieve the information from that message to create a confirmation email using code like what you see here. One of the most important pieces of this email is right at the bottom where the email embeds a link back to the public website, not really LocalHost, which includes the guid that represents the AppointmentId. The website then sends the email and that's what the user will end up clicking on in their email and trigger a confirmation using the website. Alright, so now we're looking at the VetClinicPublic website, which is a super simple demo solution that we put together, and one of the things it does when it starts is start checking for messages, which you can see here. Right, but we don't have it running quite yet because it would have already pulled the message out of the queue, so we're going to start it up now and it will start checking messages. As soon as it finds one of those messages off of the message queue, it's going to send an email and we're going to use a tool called smtp4dev, which you can download from CodePlex. What it does is it runs on your local port 25 and will react whenever it sees you trying to send an email through LocalHost. This is such a great tool for testing when your application needs to send out emails. Nice find Steve. Currently there aren't any emails in smtp4dev, but as soon as we start the web application it's going to check our Message Queue and send an email that we should see smtp4dev. Okay, so we're about to start checking for messages. There's a message. Why don't you open it up so we can see that it is the same message that we sent out for Samson's appointment. There's the hyperlink that leads us back to being able to confirm. Let's say first, high level, what happens when we click on that confirm button, and then we'll come back and click in and watch it in action. Now our user has the email and their beautiful CONFIRM link in the email. When they click that it opens up the website, browsing directly to the guid that was their appointment, and in response the website calls its own method called confirm, which takes the relevant AppointmentId and pushes it into Scheduler Queue, which is another one of the queues in our Service Broker. Now AppointmentId is in a message and a queue waiting to be retrieved by our scheduling application. Let's see that in action now. Great. When we clicked on that we now confirmed the appointment. Once the user clicks on the CONFIRM link it drops the message with the confirmation back into the Scheduler Queue and you can see that message right here where it's specifying the AppointmentId that has just been confirmed. Now you can see that the two different applications are communicating back and forth with each other using their two separate Message Queues. The last step now is for this confirmation information that's sitting in the Scheduler Queue to get back to the Scheduling app. Now in our Scheduler application we have implemented a CheckMessages method, just like you saw in the public website, which is periodically listening to the Scheduler Message Queue to see if there are incoming messages that it needs to deal with. When it finds one it responds to the AppointmentConfirmedEvent with the EmailConfirmationHandler. The handler looks up the appointment from the AppointmentId that was contained inside of the message and from there it calls Appointment.Confirm. Appointment, as you recall, is our entity and it's confirm method also then triggers some events, which, for instance, our user interface can listen to and when it sees that that event has been fired it will trigger a change in the UI, dropping the appointment in a green box to show that this client has confirmed this appointment. Okay, so all that's going to happen at this point is when the message comes through it's going to make the Samson appointment right here with a green border and popup a dialogue to notify us that this change occurred, and that nice little message pops up. Very slick. This is actually really easy to implement because we already had the website listening for events. Remember how it was able to display new appointments and display conflicts, we implemented another design rule based on a particular property of the appointment, which is confirmed. All we did was setup another event handler.
-
Debugging to See Detailed Implementation in Code
Now we're going to take a deep dive into the code that makes all of this work and we'll go through it step by step, so that you can see how all this is wired together. We'll do that by literally just debugging through the whole process, so you can see how all the code links up. Now, if you have the Pro version of your subscription, you'll be able to get access to the full source code, which we hope you'll be able to quickly get up and running yourself, but even if you don't we're going to show you all the moving parts so that this will be valuable for you in your own applications, if you need to do something similar. We're back in the Vet Manager and the user is on the phone with Steve who wants to make an appointment with Darwin. Everything works just the same way it's worked before. We'll go ahead and add a new appointment and save the appointment, which triggers the Schedule aggregate roots AddNewAppointment method. We haven't changed anything in the method. The only thing that's different is that now we've got an additional subscriber that's listening for this domain event, this particular domain event, the appointmentScheduledEvent to be raised. We'll go ahead and raise the event and watch what happens. At this point, we're looking at a new class that we created, which is this RelayAppointmentScheduledService, and what it's responsible for is creating the event that is going to get pushed onto the Message Bus for the notification application, which is our public website. This is the new piece of logic that's listening for the event that we just raised. You can see it's implementing IHandle AppointmentScheduledEvent and in the method the first thing we do is we get this AppointmentDTO and this DTO is a data transfer object and the reason why we need AppointmentDTO instead of just Appointment is because we're going to actually send this data in a message outside of our application and outside of this aggregate root and outside of our application other applications don't care about things like the DoctorId and the PatientId. They want to be able to create an email to send and they want to know things like the Doctor's name and the client's name, and things like that, not the Ids. Instead of using AppointmentType that the Scheduling domain uses, we've got this DTO, which has its own class definition, it's got the ID that we know about, but then the rest of these fields are everything we would need to construct an email with. Now we're going to take that and put it into an ApplicationEvent. We've given it the same name, it's the AppointmentScheduledEvent, but notice the namespace is different. This is in the ApplicationEvents namespace and so it's slightly different than the one that we published within our application. The main difference here is that it takes this DTO type, instead of taking in the Appointment entity type that's part of our aggregate root. In our application we're using these ApplicationEvents in a similar way as we use DomainEvents, except we're raising the events from our application to listeners that are external from our application. This is also an important pattern for communicating between one or more bounded contexts in your system. Now in the Publish method that lives inside of a Service Broker Message Publisher, which is inside of an infrastructure project, so this is in our Data project, it's not inside of our core domain any longer, but it is still in the main Scheduling application. Yes, and what it's responsible for doing is actually getting that message into a structure, a format, that our SQL Service Broker can understand and so it's doing some low level work here talking to SQL Server, putting things into JSON format in this case, and then actually sending the message, so once this thing fires we should be able to inspect the Message Queue in SQL Server and verify that our message has actually been queued up on the Notifier Queue as expected. That's what we did before, but this time we're actually seeing the code that's making all of this happen. Alright, so that completes the actual thread of the UI. Now, looking at the Notifier Queue in SQL Server Service Broker, we can see the new message sitting there. Now we're going to pause this and switch to the other application. Here we are in the web app. We've just started it up again, and we've showed this to you before, now we're going to watch the flow of the code after StartCheckingMessages is called. Right. We didn't actually look at any of the detail of how we're doing that timer, so if you're interested in that it's just these couple of methods here that creates a thread on this StartJob method, it's running right here, and it very simply sets up a timer to check every 5 seconds to see if there's anything new coming from the Message Queue. In this case, it only checks for one message every 5 seconds, so it would be possible to flood our system, but given that they're only doing a couple of dozen appointments per day we don't expect that that'll be an issue for this system, and we could always tweak the timing. Jumping to the next breakpoint you can see now we're inside of the actual CheckMessages method and it uses a lot of similar plumbing code to talk to SQL Server. We're using this ServiceBrokerWrapper to help a little bit with pulling out these messages, and then it needs to deserialize the object that it's getting from JSON back into an AppointmentScheduledEvent. In this case, we've defined a copy of the same class inside of our public website. Alternately, we could have had these shared classes in a common assembly that was distributed to both applications, either one would work. JSON will deserialize it as long as they match, and then immediately inside of his method we're going to send this confirmation email. We get this email sender from StructureMap and it implements the ISendConfirmationEmail interface and then we call the method on this particular implementation to send the confirmation email. The code behind SendConfirmationEmail is just completely standard SMTP code to send any mail out, so nothing special happening there. I just wanted to remind you that critical piece of that email is the hyperlink that we build using the AppointmentId and ten it just gets sent out. Right, and that's the whole reason why this needs to be in the public website and not in our Scheduler application because our Scheduler is just sitting inside the local network at the vet clinic and the customer needs to be able to click on this link to communicate back. Okay, and so that's it for now, for the public website. The next step in the workflow is triggered with the user clicks on that link in the email. Now back to the website where the confirmed method has been triggered. Right. In the confirm method it's just going to call something to send that message back now from the public website to the Schedulers queue, which is s separate queue. The definition of that message in this case, is really just going to be a message that contains the AppointmentId of the appointment that's just been confirmed. Right, If we look at this event, you can see it's very simple. It's got just the appointment ID and some other information that's not related to the event itself, so we can see the same kind of code here that's connecting to our SQL Database in order to post this message to the queue, and in this case it's posting it from the Notifier service to the scheduler service, which means that this message will show up in Schedulers queue once this code executes. Now we should be able to check to see that the SchedulerQueue has, the new message and you can see that it does. Once we start pausing this application we'll see the next step in the work flow. Now remember we're back inside of our main Scheduling application where it is running a similar timer-based thing to check the queue. In this case it's checking the scheduler queue and it just picked up that AppointmentConfirmed Event that was in that message, translated it back from JSON, and is raising it within our domain. This is really the last piece of the puzzle where the handler for that event retrieves the appointment for that particular schedule, calls the confirms method on Appointment, which will change that value, and then use this repository to update back to the database. It's worth noting, in this case, that the AppointmentConfirmedEvent that we are handling in this EmailConfirmationHandler is the ApplicationEvent, AppointmentConfirmedEvent. When we actually confirm the amount using its confirmed method, remember that it's going to raise a domain event that we handle separately in the user interface, so once we continue running, now we're inside of the application and we have just confirmed it and his is going to raise now in AppointmentConfirmedEvent that is part of our domain. Once we've raised this event the UI is going to pick it up. That's because we already have the plumbing in place for the UI to listen from changes to Appointment properties. Now we're back inside the website and this is in our Hubs folder, so this is using ASP.NETs SignalR. It's going to finally raise this confirmation information and show the alert in the browser. This is a new handler, but the logic inside of the handler is not new. We've been using that same logic all along with the AppointmentUpdateHandler that triggers changes to the UI. Right, so then finally once this runs, if we jump back to the browser we'll see the notification and we'll see that my appointment now has a green border. The last couple things we wanted to show about this user interface that we got for free are the ability to see the whole Work Week and it's a little bit tight here in this resolution, but you can see all the different scheduled items and their still formatted with the green boxes to show that their confirmed or you can just look at the agenda for each room and so each doctor could see, you know, here's who they have on their agenda for today and they could print this out, and that's not anything we've written, that's actually part of the control that we're using. Yeah, exactly.
-
Homework: Extending the App Again
Once we've implemented sending emails when an appointment is scheduled, being able to send reminder emails to clients the day before their appointments should be pretty straightforward. One approach would be to create a service that runs each day, gets a list of scheduled appointments for the next day, and sends reminder emails to each client. Bonus points if it can avoid sending duplicate emails if it accidently is run more than once per day, and of course, it should avoid sending emails to clients who have opted out of the reminders. Consider this your homework assignment once you're done with this module. You can download the source code or else mock out the scheduling system and then think about how you would implement a service to do this using what you've learned in this course and some of the tricks you've just seen in this last module.
-
Considering the UI When Designing the Domain
The control we used solved a number of the problems we thought we were going to have when embarking on this application, but the fact that the UI kind of impacted how we designed our domain begs the question about, well, if you're totally focused on the domain, why would you even be thinking about the UI, but thinking about the UI while we're working on the domain is not the anti-pattern you may think it is. Yes, we've been focusing on the domain, but frequently the user interface needs to be considered, especially in the early stages of planning. You don't want to try to flush out the whole domain design before you start thinking about the UI. In a _____ takeout session I attended in 2013 Jimmy Nilsson, who's the author of the book, Applying Domain-Driven Design and Patterns, talked about the importance of thinking about the UI in the early stages of planning and revisiting it while modeling the domain rather than ignoring it until the end. In his session he describes how even the UI sketching he does in the early stages of his application planning can affect the whole design of the system. As we were building the Scheduler sample for this course we actually discovered a huge benefit to considering the UI early in the process. We initially had expected to encounter a lot of complexity in the appointment scheduling problem, but we found a UI control that helped visualize the schedule for the user, such that the system no longer needed to be as complex. In our scenario scheduling is a big part of the application, but it isn't our domain. Our domain is the veterinary clinic. We consider scheduling to be more of a cross-cutting concern and one that could be partially solved through a rich user interface. We ended up using Telerik's Kindle UI scheduler widget, which can display schedules in a variety of formats, as you've seen. Initially, our UI was going to be very simple and we were going to have domain complexity involved in things like finding available time slots or detecting conflicting appointments or double-booking. Being able to display the schedule to the user in a rich fashion made it unnecessary for the domain to worry about some of these concerns, since for instance, the user can easily see where open time slots exist on the schedule. Of course, you don't have to use this Telerik control or even a commercial control to achieve this. There are certainly other options available to achieve rich user interfaces for whatever platform you might be developing on. Thinking about the UI upfront and discovering this kind of solution kept us from wasting a lot of time trying to solve certain scheduling problems in our domain. Of course, you don't want your UI to totally drive how you model your domain, but as Jimmy Nilsson notes, you shouldn't ignore it either.
-
Eric Evans on the Fallacy of Perfectionism
Steve and I believe that it would be fitting to leave you with one last thought from the father of Domain Driven Design, Eric Evans, who was kind enough to talk to us about DDD for this course, so we could share with you some of his wisdom. Eric talked about the fallacy of perfectionism, which aligns with our own sentiments about considering what you've learned here to be guidance to help you solve complex software problems, not a roadblock to productivity. When we talk about stumbling blocks there is one thing that I have noticed and that is there's something about DDD that brings out the perfectionist in people and so they get to trying to make everything just right and they get a model and they say, well yeah, this model, it's not really good enough, and then they change it and it's still not good enough and they just basically churn and churn and I'm here to say no model is going to be perfect and you know, the model that you've got, it probably doesn't look as nice as the examples in books, and the reason is because in book _____ people have the luxury of choosing an example that really gets the point across and one that's been refined. Of course also, they can simplify the domain a bit so that it doesn't have some of the messy cases. There are reasons that book examples tend to be very, very nice. The examples in my book, to some extent, have flaws in them. I actually tried to keep some --- kind of imperfect rough edges in the examples in the book to get across that the model they're going for isn't some kind of ideal model. We need to know what we're doing with this thing, like the scenarios we're trying to address, and we want a model that really helps us do that, really just makes it easier to make a piece of software that solves those problems, and that's it. If the model that you're looking at does that, then go, and if you have a better idea then you can refactor, but don't get stuck.
-
Remember This From This Module!
If you remember nothing else from this particular module, the one thing to keep in mind is how simple it was for us to add in what was potentially a really complicated feature. Because of our DDD implementation and some of the infrastructure we had already built, it wasn't really very challenging to plug these new puzzle pieces into the application. Right. We introduced a couple of new concepts. We talked about Message Queues and those fit really nicely into our existing architecture because we were already using events to correspond to interesting things happening within our application.
-
Glossary
We introduced two new terms in this module. Message Queue was a piece that we used directly in this application and the Message Queue allowed us to stick a message in an external place, in our case the SQL Server Service Broker, by one application and another application can come along and retrieve that message, so the Message Queue allows our applications to communicate with each other, but they can do it in a disconnected way. We mentioned, but we didn't show, this concept of a Service Bus, often called an Enterprise Service Bus, which you may want to introduce if you start having more than just a couple applications needing to talk to one another. In our case, you saw that we were hard coding where we were sending our messages and it might be that some future application is going to also want to know about some of those events. A Service Bus would be able to allow any application to subscribe to any event that it was interested in without each individual application having to be changed in response to this new application being added to the system.
-
References
In this module, if you're looking to learn more, we still recommend these three great books, including, Applying DDD with Patterns in .NET and C#, by Jimmy Nilsson, who we quoted in this module, and we pointed to Peter Ritchie's blog post on the Enterprise Service Buses, so here's a bit.ly link for that, along with a bit.ly link so that you can watch Jimmy Nilsson's full ticket presentation on DDD. Also, don't forget about the DDDCommunity.org website, which is a great resource, and Eric Evans website, which is DomainLanguage.com. Of course, if you want to learn more about Message Queues, which we used in this module, there's a great course on them available on Pluralsight. Julie and I have spent a lot of time and energy on this course, certainly more than any of our other courses. We really hope it's helpful to you. DDD is such a vast topic that coming into this project we both considered ourselves to still be students of Domain Driven Design, but we were surprised by how much more we learned by working out how to express these DDD concepts to viewers of our course. Definitely. A quote I'm fond of from my karate dojo that you can see here is, "To Teach Is To Learn Twice," and that definitely applied to this course, maybe more than twice. Remember too that DDD is still an evolving school of thought with new articles, books, and presentations coming out frequently. You should have a good grasp of the fundamentals now that you've completed this course. Please let us know what you think about it either via our contact information here or on the courses discussion forum. Thanks for watching.