 Hi, everybody. Story begins in a time not so long ago in a startup not very far away. And in this startup, there was a monolith. This monolith was terrible. It was a beast. It was 700,000 lines of Ruby code. It had minimal test coverage. The tests were flaky. Team after team came after it. But they could not vanquish the beast. Product got concerned. Was the business at risk? They couldn't ship features anymore. Engineers started leaving in droves. What could we do? Finally, one day, the Council of Engineering Leads came together. I was one of those engineers. They sat down and said, we must stop this. What can we do? Out of the darkness came a voice. Microservices. And I said, how? To my left, a wizard, my co-worker, turned to me and said, have you read the book? Have you read the text, domain-driven design? I turned to him and said, I think I've heard of it. I don't know. What is it? He turned and handed me the book. That day opened it. And I learned about the power. And today, we are going to access this power. Well, hi, everyone. My name is Andrew. I'm a programmer at Carbon 5. Carbon 5 is a software development consultancy. We're a friendly group of people that love to work with you on your software systems, no matter how big or how small. And I'm going to bring together two self-evident points. This is an audience that's going to get it. Maintaining large systems is really, really difficult. When systems get to a certain size, they just tend to be difficult to work in. You can't keep everything in your head at once. Additionally, complex systems aren't intentionally messy. They're not difficult to work with because we made them that way, for the most part. I mean, no one's out there writing code and saying, oh, just wait till Wally sees this turret. I'll leave in the code. No. For the most part, we're just accidentally adding complexity to the system because we've got to ship things. Dr. Dre says, we've got to build the business. We've got to make some money. We've got to secure our next round of funding. Otherwise, we don't have jobs. And eventually, you keep doing this enough and enough and enough, your system starts to fall over. When your system grows to maybe 10 or 20 times its intended size, oops, we should have refactored maybe three years ago, the system starts to fall over. And people get really sad. And a lot of the tools we have to deal with this come from modularization, systems of abstraction. Well, I turned and did some looking into the body of work that's been done into abstraction. Dave Parnas in the 70s wrote this paper while he was at Carnegie Mellon called On the Criteria to be Used in Decomposing Systems into Modules. And in this text, I'm just going to pull out the core takeaway. He says that there is a list of difficult design decisions that every module must then hide from the rest of the world. Design decisions that are likely to change should be hidden. And this thought, this work, eventually became what we consider the modern concepts of high cohesion and low coupling. Let's go through those. High cohesion is the idea that similar concepts in your code belong together. They're localized to the same part of the code base. If one changes because they're so related, the others tend to change as well. And they all tend to live together in the same part of the code. The idea of loose coupling is that your code doesn't necessarily need to know that much about the rest of the world in vice versa. You don't have that many dependencies to other systems, to other APIs, or to other modules. Today, what we're going to do is we're going to look at Phoenix context. We're going to understand this tool that the core team has given us to modularize and control complexity in our systems. Then we're going to turn to the time-tested wisdom of domain-driven design. And we're going to look at it through an exercise called context mapping. And finally, we're going to get to the code. So are you ready? Let's get to it. Well, in Phoenix 1.3, the core team released an idea of a context. Sony over here is at the core team. Thank you very much. I'm sure there's somebody else here that I may have missed. And contexts are simply elixir modules. They group system functionality together by business domains, but there's nothing magical about them. They're just modules. So let's take a look at a scaffold. When we run the scaffolding command, we'll be able to see what happens on the other side. And maybe from there, we're going to learn a little bit about the purpose and the goals of these contexts. So let's start. I'm going to create a user, and I'm going to put it into a context called Identity. So here we go. We type it out. Identity, then we talk about the name of the resource called users, followed by the name of the schema, the table name, as well as descriptors for the columns. And when I hit Enter, here's what comes out on the other side. First, we get a schema that's generated. Nothing here should be really surprising to you. The only thing interesting here is that the user is nested in the Identity module. Second of all, we get a module file, a context file. And this context module becomes our public interface to the rest of the world. Now, everyone else that wants to create one of these users, it's going to call through one of these CRUD actions in this module. Third, we're going to get a Phoenix controller. And the interesting thing here is that the controller lives in a different context. It lives in the web context. And if you look at the controller, all it does is delegate. It calls through to the context. It doesn't need to know about persistence. It doesn't need to know about Ecto. It doesn't need to know about anything under the hood. That design decision was hidden. And finally, we get a migration. So what do we learn? In Phoenix, when we use context, we learn that web things belong in the web context. We learn that contexts encapsulate persistence and domain logic. And finally, we understand that the outer context module is the public interface to that module from the rest of the system. Well, that's great and all, but I have a few questions. Number one, what should I name the context? Number two, how do I think about resources that are needed in multiple contexts? And number three, how do I know if it's too broad or too specific? What are some tools I can use to help me understand those things? And that is a time old question that engineers from before our time until now have been asking ourselves, how do we design system boundaries? And so we turn to domain-driven design, the text. And the text was published by Eric Evans in 2003. And it is both a set of high-level strategic design ideas and activities. And it's a bunch of concrete software patterns. And if you run on Google and search domain-driven design or DDD, you're going to get a lot of noise. You're going to get a lot of Java and a lot of .NET and a lot of F-sharp, maybe. And you're going to get a lot of code samples and a lot of jargon. That's just not going to make sense. And I don't want you to get tripped up because there's a few core ideas that are really golden. They're going to help you no matter what language you turn to next. And then there's also a lot of noise. So here we go. Oftentimes, when we think about abstractions, we think in terms of horizontal layers. We're thinking in terms of, like, we're going to need a web tier, and then we're going to need a domain tier, and we're going to need a persistence layer. We're going to need a bunch of adapters. And that's actually a fine way to split apart your system. But what if we had a bigger picture? What if we looked at things a little differently? And what if we thought in terms of vertically decomposed business use cases? Now, if you're working in your agile framework of choice, you're already used to splitting things up in vertical slices of functionality. So let's take it a little bit further and apply it to our architecture. Well, I made up a company. It's called AutoMax. Today, you and I are all going to get onboarded. So here we are. We're a used car marketplace. We have an online presence. We have a brick and mortar presence. Sellers who want to sell their vehicles come to our brick and mortar locations. We take their cars into our garages and our mechanics take a look at it. We rate their car based on the quality and then we appraise them for a price we buy the car if we like it. And on the other hand, buyers come in. They see the car on the internet. They want to test drive the car and they buy it if they like it too. Well, speaking of things that are design decisions or things that constantly change, the business is the place where I think a lot of change happens. Marketing just wanted us to change copy on the website. Finance wants us to change how tax calculations are done. Operations wants us to build new features in the vehicle inventory system. Product wants us to do things in Bitcoin. I don't know why. And customer support wants us to build a better support dashboard. This is fine. We got to listen to the business. And that's not just Dr. Dre talking. That's Dumbledore talking. The right thing to do is to business so hard. It's both. My apologies to Rob Connery. And so I'm going to save you a lot of money because you don't have to run out and buy the book because I'm going to summarize it right here on the slide. And the slide says, we design our software systems according to your business domains by paying attention to the language you speak in the business. This is important. Linguistics is actually a key thing to take away from domain driven design. And when I say we listen to the business, I mean we literally listen to the business. And the way we do that is through an activity called context mapping. This is an IRL, get everyone in a room, a big conference room with a big blank wall. And here we're going to discover the concepts that live in our organization, our business, and eventually our systems. So get everyone in the room. Invite your product owner. Invite your business stakeholders. Invite your onsite customer. Invite random people if you want it. It's fine. Get everyone in a room. And what you're going to see here is, hey, everyone's here, tech team, non-technical product, business, C-suite, whatever. Everyone's going to get a sticky. They're going to put up nouns and verbs up on the wall. So from their particular point of view in the system, what are the things that you interact with or the system needs to do to get your job done? And it's going to be a little chaotic and it's going to be a little fun. There's going to be a lot of confusion. But this might happen. You'll see the things in orange are the nouns and the things in blue are the verbs. I mean, it's never going to be this clean, but for the purposes of illustration, I've done it this way. Most likely, you're going to have duplicates. You're going to have things that kind of belong together, but you're not really sure. But it's OK. At this point, we're just being generative. Embrace the chaos. Next, what we're going to do is we're going to group like concepts and actions together. There's probably a natural affinity of some grouping of these things. So over here, I took the blue stickies off the slide because it's a little noisy. And I want us to look at this because there's actually a few things that seem a little bit more related than others. So we just kind of push them together. More on that in a bit. So we see that there's some natural clusters that emerge. And I'm going to step back and introduce the first term from domain-driven design. This is the core domain. The core domain is the primary area of focus of our business. So in this case, our company's core domain is car sales. If we were IKEA, it would be furniture sales. If we were Uber, it would be screening federal regulations. If we were, you get the point. So you don't have to draw this on the wall, but I'm drawing this out here because that's part of the context map. The context map basically implies that all of these concepts would apply to the core domain in some way or the other. Car sales. Next, we're going to talk about supporting domains. Supporting domains are also called subdomains. And they're areas of the business that make the core domain happen, but they're not the actual core domain. So for example, we may have an online listings subdomain. It's a website, but it's not necessarily the part where we sell the car. We might do financial transactions, but that's not exactly selling the car. We might optimize the funnel. We might do SEO, but that's not really selling the car. And finally, we might keep people happy through customer support, but that's not selling the car. And what I want to say here, here's the point where the magic happens, is that very likely you're going to already find some natural groupings and those natural groupings hint at your subdomains. And of course, I'm not suggesting that it's actually this clean. Very likely, some of these domains oftentimes overlap. Oftentimes, they're messy. You'll have concepts that actually belong in one and the other. Once again, embrace the chaos. We'll take a step back, pat yourself on the back because you've just discovered your domains. This context map pointed us to our business domains. In fact, they might actually point us to real organization structures according to Conway's law. And going back to this, these are the beginnings of the modules that we may want to build into our application. And if you took it even further, these may be the microservices that you eventually want to build. Well, here's another term. It's a ubiquitous language. The ubiquitous language is a little misleading, but it's the vocabulary that you speak within each domain. And within each domain, I encourage you to build a glossary. What this glossary is, it's super simple. It's a list of terms and definitions for the words that you speak. And here, the business is leading. Always, always, if your product owner or a business stakeholder uses one term, you stop them and say, do you call it an automobile or do you call it a vehicle? Which one do you use? Oh, you call it a vehicle? OK, I'm going to write that down. And you turn around and you refactor that thing to vehicle in your code. If they say, do you rate something or do you inspect something? Or get that straight. Be clear on those concepts and speak about them consistently. A nice side effect is that if you have a glossary, onboarding people is really easy. You never have to have confusions as to what things mean when you onboard someone. So this term of abounded context, as I was referring to back in a few slides, these subdomains in an ideal world should map clearly to a bounded context. So what do we mean by that? A bounded context has two different ways of looking at it. One is it's a running software system. So it can be a module. It could be an app. It could be a service. It could be a Ruby Jam, a Java package, blah, blah, blah. It's essentially code that belongs together that eventually runs together. But there's an even more nuanced definition for this. It's a delineation in our domain where concepts are bounded. Meaning, I'm going to go back a bit, that a concept that lives in perhaps one of these domains is only allowed to mean that particular meaning within that structure, within that context. And that context could be overloaded in a different part of the system, but that's OK because we bounded it inside of another structure or context. Here's a couple of examples. A rating might mean something when our mechanics assign a car a rating. It might mean quality. But our customer support team might also assign a rating. And that might correspond with a MPS score or something. Those two concepts have the same name, but they're wildly different. And because we put them or we nest them into different contexts, they're allowed to fully express what they mean. Imagine we didn't have any of these contexts. We would have had to awkwardly rename these two concepts to mean something. And then you get in this weird, awkward, ambiguous place where terms don't actually mean the same thing. So the same thing happens with the user. It could also be a mechanic. It could be a seller or a buyer, et cetera. So a bounded context means that we can use precise language and we can model things as clearly and as flexibly as we need to be. A very powerful modeling tool and our products can now stay in sync with our business stakeholders according to the language that they use. So to round out this entire section, I want us all to be very zealous about the language we use, about the language we speak. Be very careful about it. Listen to the business. We're not just going to be our Dr. Dre's and we're not going to be Dumbledore's. We're going to be both. By the way, did you know that if you put Dr. Dre and Dumbledore together, you get DDDD? OK, so it's time for some elixir. Here we have a context. Context can expose domain entities. So here we have the nouns that are being exposed and they can also expose domain actions. So over here we have the verbs that are being exposed here in our context. A couple of things about these two slides is that this is using terminology that we generated from our context map. This is terminology that our team completely agrees on. Let's assume, for the sake of this next example, that we're looking at this screen in our application. Wally, the mechanic, is logged in into our back end admin panel and he's giving his rating of a certain vehicle. Well, here's what it looked like before we introduced contexts. It's a little messy. There's a lot of ecto. There's this authorization policy thingy. And it's a little confusing. So why don't we just go refactor it? So here we're going to start out this refactoring exercise by first plucking the user and pushing it into the identity context. We do the same thing for the action of fetching that thing. And so now we remove the need for the controller to understand the database layer or the persistence layer and we push that up to the context responsibility. We'll do the same thing for the vehicle and the rating and we'll do the same thing for the action in the inspection domain. So all that stuff around authorization policies and ecto persistence get pushed back into our context. And now we turn around and we replace that code that we just refactored out with calls to the context. It's way better. What did we just notice here? We just saw, once again, that contexts only expose public methods to the rest of the world. They hide internal implementations that are likely to change. And finally, we were very careful to use domain language when we were naming our actions and our concepts. Here's kind of a question I get a lot, is what if we need to use a concept across multiple contexts? If you're like me and you've built a lot of big apps, you'll know that your user model oftentimes becomes like a God object, meaning that your systems, whenever you build any sort of system and you have some sort of identity or some authorization concept, you end up hooking a lot of things into that concept. And eventually, the dependencies to that concept begin to spider out and cause the system to become kind of brittle. So let's take a look at what that might look like in our system. So in our system, the user in the identity context is concerned only with authentication and authorization. It might store some Facebook tokens. It might store Google OAuth or SAML or passwords. But that's all it really cares to know about. But however, this logged in user has to have some additional information pushed onto it in a different context. For example, like a mechanic in the inspection domain might have a certain semantic meaning about it. And it needs some additional information. Additionally, perhaps we discovered in our context mapping exercise that our marketing team actually refers to these all as visitors, not users. They call them visitors. So here's a few options. One is we could do nothing. We could just choose to just use users everywhere. So here's what a quick refactoring might have done for us. We choose to, in our inspection domain, do a rating with a user that's a logged in user. But really under the hood, or really to our product owner, it's actually a mechanic that's logged in. But you wouldn't know that from reading the code. Obviously, I wouldn't recommend this, but I understand why a first step refactoring might lead you to this solution. Well, here's a better idea. Is we could do a pure, what I call a struct conversion. But really it's just the mapping from an external concept into an internal concept. And this is really useful for use cases where you're purely read-only. Here's what I mean. Let's assume in the marketing context, our visitors really have two relevant attributes. The handle, which is really like your email address or some username or Facebook username, I don't know. And then they have an internal UUID that we assign to them in their cookie or something. That's really all the marketing code cares about. Now, when we want to do something in the marketing domain, we need a visitor. We don't need a user. So I'm going to make this mapping function from a user to a visitor. And over here, I'm sure there's a better way to do this. But anyways, what I'm doing here is I'm basically remapping the email field from the user struct into the handle field on the visitor struct. And then when we then turn to use this in our marketing context, we then apply the mapping function. So here, you can see that we get the logged in user, but we convert them to the visitor. And then we go ahead and use whatever we have planned internally in our domain. Over here, we're subscribing them to some email newsletter. And over here is where the power of Elixir really shines. We're able to do pattern matching, perhaps, to enforce that we get a certain type of struct. Even better, we could write some type specs to really enforce that we're passing in the right types of objects, or the right types of structs. And finally, if you really do need to introduce an internal concept that is persisted to the database, then you can create what I call a collaborating schema. So if you need to write. And so let's see what that looks like. For example, the inspection context might have a mechanic table. And we might need to persist a few other things. We might also need to know that a mechanic is a contractor or has a certain type of certification. And over here, you can see that we just link them to the user. And so now, we're going to create, every time you enter this context, you're going to go create a mechanic from the user or you're going to go look up the mechanic from the user or some combination thereof. And what we've done in these past two examples is create what in domain-driven design we call an anti-corruption layer. What that means is that every context is very concerned about the domain purity of what's being passed around and talked about. It knows that what's coming in from the outside world is not exactly the perfect mapping to our internal world. And we're going to take great pains to actually map it. One last thing. You'll see in that third example, I directly linked to the user. But that's actually a hidden coupling under the hood. There's an Ecto sort of relationship that may be difficult to refactor out in the future should this grow. So what if we sacrificed a little bit of ease of use, made it a little bit more awkward? But what if we actually enforced some sort of separation between our contexts, and instead stored maybe UUID blobs or restored our user IDs as strings? That way we signal to the user or to the developer that this is actually a hard break. One concept I want to talk about is an aggregate. Aggregate is data. Aggregates are data that belong together. These data is naturally fall into tree-like structures. Here's an example. In our inspection context, we have a rating for a vehicle. We have a list price on a vehicle, and we have the vehicle itself. And really, naturally, they really should look more like this. Now in the past, maybe our context, we generated all these things. And we exposed all of the nitty-gritty, gory details out to the outside world. But remember, that's a big surface area that we're exposing to the outside world, and that also allows for more possibility of bugs and sort of state problems. Because the outside world is able to mess with the internals of my inspection context. So what if I instead I just returned a nested data structure? The vehicle is what I call the aggregate root. That's like the mother object for everything else that just hangs off of it. And then everything else kind of dangles off of it, and the caller is left to parse that tree structure, the aggregate. Similarly, we don't allow operations to happen on kind of the sub-resources in the aggregate. And instead, we enforce that public methods only take aggregate roots. The only way you can change something is to tell me what your root is and then tell me what you want to change on the root. So expose aggregate roots only to the outside world and pass around aggregates. If you're exposing an API, if you're sending events on the bus, you're going to want to pass the entire root. Additionally, you get the benefit of when you pass around roots, you kind of get the context. You get a higher level view on what's actually happening in the system. If you were just to make a change on a line item, really, you want to see what's going to actually happen in context of the order. So try using aggregates. And finally, I want to talk about event-driven messaging. And here we're getting into some of the more nitty-gritty or some of the more concrete software patterns in domain-driven design. But there's a simple thing we can do here. So here we have a identity context action create user. And it has some explicit couplings to some downstream side effects. So we're going to have to maybe create a mechanic in the inspection domain when someone signs up. And we might subscribe this person to an email list. And then we might then send Google Analytics or mix panel a event. Well, this kind of couples our context. What if we could decouple them somehow? And we can. We can kind of invert this sort of data flow by, instead, publishing an event over a bus. Our interested context downstream can subscribe to these domain events. And in fact, when we together built our context maps and we also developed a glossary, we actually already talked about these. These were those blue stickies that had the verbs that happened in our system. And so here's what I oftentimes like to do. I like to follow a three-part naming convention for my events. The first part is the context. It's the name of the context that where this event originates from. The second one is the name of the resource or the aggregate root that is being, that an action is being performed upon or this event is being fired from. And finally, it's the name of the verb in past tense on that resource. Downstream subscribers can now turn on the screen and go subscribe to this event. One of the nice side effects of doing this is now we get to independently scale downstream side effects, should they be particularly slow or need a lot of time or have some difficult part about operationalizing them. And then there's also a downside of this where you now introduce the complexity of managing a bus. Now I'm going to show some code. I'm going to use this library called event bus. It's a Elixir OTP implementation of an event bus. But you can replace this with your library, with your queue, or you could actually just use straight up, I think you can use GenStage or something for this, GenFlow. Well, we're going to go ahead and replace all those calls downstream with simply the publishing of an event. And the details down here aren't that important. Really the most important thing is that we publish the event after the thing happens. And downstream here, you see that we send the aggregate route out over the bus as well. And now subscribers downstream, here the marketing code, is going to go listen for that event, and then it's going to perform its own internal context side effect on its own. In fact, this is another place where we implement an anti-corruption layer because this is effectively outside data coming in. And this is the same implementation for a different context. OK, every architecture talk needs a caveat section because your mileage may vary. And I'll tell you right now, your mileage really will vary. First of all, I encourage everyone here to start small. If you're inspired by any of these ideas, don't just go and try to refactor everything to use these software patterns. Refactor things little by little. Take one context and build it, see how it feels. Was it awkward? Did you run into pushback from other teams? Those are all things you've got to think about. Also, you have to think about things like, did I get the concept right? Refactoring and renaming things is surprisingly really, really difficult. Second of all, don't get ahead of yourselves without understanding the core concepts of domain-driven design. Start with the context map. Make sure you're in agreement on your teams that what you're doing is kosher. Make sure other teams know what you're doing. Make sure that you're always updating your glossary with the latest terminologies, and you're doing your best to keep things consistent from your business stakeholder all the way down to your code. And finally, if you're not feeling the pain, don't do this. I just told you to do this. And then at the end, I told you not to do it. This is only for systems of certain scale and for teams that feel certain pains. If you have a small-scale system, if you have a crud-based app that's honestly not doing that much, you don't need this. So don't do it. In conclusion, what I've hope I've showed you is that it's important to listen to the business. You've got to build a context map which will help you, and it will guide you toward using the right boundaries in your code. You're going to create a glossary. You're going to use the right words. We're going to be very zealous about using context to hide implementation details. We're going to embrace the nuances of language. We're going to use our Phoenix context, or just our modules, to specifically allow languages to allow concepts and terms to run free. We developed a few architecture tools, and we are probably on the best, most robust system to build all of these contexts. If you ask me, that's what make really beautiful systems. All right, go on, everyone. I've given you the power. Use your powers for good and not evil. Thank you very much. Andrew, thanks for the talk. Going to the event bus thing you were talking about. How do you solve the problem of the lack of visibility? When I'm creating a user, I want all these things to happen. Now it feels almost like magic. This thing subscribing, what if it's not adequately subscribed? I feel like there's a lot of potential for problems. It's the observer after create hook kind of problems that have been around in Rails. That's a fair question. A lot of what is helpful is you build in tools to be able to trace events through your systems. So oftentimes you can trace things. Perhaps your incoming web request has a UUID on the header, and you can pass that token through your system. So some event sourcing or event bus implementations allow you to tag messages with that UUID. And if you have a log aggregation tool, you can then just plug that in and be able to get log traces. But you do bring a good point. We have trade-offs. And now when we distribute our computation, we're going to lose visibility. And it's going to be a little bit more difficult to debug. So you were talking about the core domain. And I'm curious, in your experience, what percentage of a system that is maybe not architected in this way, you then start moving it towards this kind of style? What percentage of that system fits in the core domain? And what percentage is outside of it? The core domain is a bit of a super important concept in the day-to-day operation of your system. It's more to highlight the fact that your software system is used by your business, and it applies toward the businesses and goals. So it's a bit of a conceptual idea. But I do hear what you're saying. Sometimes you'll have some sort of software part of your system that does something totally different. And so I don't know. You might have stomped me there. But I'm sure there's software that just does random stuff. It doesn't really apply to your core domain. But I would say the vast majority of things that don't apply to the core domain are actually sub-domain things that are actually important to the business, if you look at it from a certain perspective. All right. Thank you, Andrew.