 I'm really happy to be here. My name's Andrew, and I used to live here in Los Angeles. My wife and I lived here for a couple years, and we were on the west side, and every time we wanted to get out here for Korean food in Koreatown or something like that, we would get on the freeway and sit about 45 minutes in traffic, and get to dinner by 9 p.m., but it was worth it. And there's really nothing like that up in the Bay Area, so we really miss our time here in Los Angeles. Now, I actually used to work in Santa Monica, and I'm really happy to say that I was able to be a part of the LA Elixir Meetup as it just got off the ground, and we were back then, we were just meeting in a little conference room, and now it's really cool to see the LA Elixir community all get together here. So give yourselves a round of applause, because it's really awesome to see this at the largest LA Elixir Meetup in history. All right, so, pardon me while I get my slides in order. All right, so, at Carbon 5, we build a lot of systems, and we work with a lot of clients, big and small, and we basically jump into projects where we see systems that are old or legacy systems, or systems that just have a lot of cruft. And so, every time we kinda come into a system, we oftentimes see that there are a lot of difficulties running and managing these systems. Raise your hands if you've worked in a large monolith or a legacy system, or just a system of any sort of complexity. And now, keep your hands raised if you really wanna tear your hair out every single time you're in the system. That's right, because all of us know that maintaining really large systems is just really hard. But also, what I wanna get at is that it's really difficult to work in these systems. We should kinda cut ourselves some slack, because no one really is sitting there in a corner saying, wait till Wally sees what turd I'm gonna put in this system. We're not sitting there intentionally making life difficult for others. And if you're on a team where you actually are doing that, I recommend some group therapy. But the bulk of complexity is really accidental. It really happens when we're shipping things fast. We need to keep the business to float. The customer needs to be satisfied. And we can't be faulty necessarily for delivering features to the customer. But you do this over and over and over and over again. And eventually, little abstractions that used to work for small systems kinda topple over when you start to get to a system that's maybe 10X or 20X larger than what it was meant to be. A lot of times, or essentially what our jobs as programmers are, are to work and build and improve upon abstractions in the system. Working with abstractions allow us to encapsulate complexity, push it off to the side and help us keep things in our head. Today, what I wanted to talk about are Phoenix Context, which are one tool that the Phoenix core team has given us to deal with complexity in our systems. But beyond that, I wanted to introduce the concepts of domain-driven design through an exercise called the Context Mapping Exercise. And finally, we're gonna close it up by walking through some real code samples in Elixir to kinda see how that might look in our real systems. So let's first go through the Phoenix Context. What are Context? Context are simply Elixir modules that group system functionality together by business domain. In fact, this isn't necessarily even a Phoenix thing or a Context thing, this is just a good programming abstraction thing. And the goal with the Context are twofold. One is we wanna keep cohesion within our systems. So by grouping like concepts together, these concepts can change together and they're all localized in the same areas of the system. You don't have to make a change in one file and run across the other side of the system or open another application to change a related concept. They're all in there together. So the idea is you keep them close together because they're probably gonna change. Second of all, these modules are meant to hide internal implementations from the outside world. And vice versa, when you're an external caller and you're calling into a Context, you don't need to know how the sausage is made on the inside. Really, you don't care if it's persisting to a database or making a network call. The idea is that a lot of the stuff is hidden away from you. Let's really quickly run through what generating a Phoenix Context looks like through the scaffolding. And by doing so, maybe we can get an idea for what the Phoenix core team had in mind for us. So over here, I'm running this command and what it's gonna do is it's gonna create a user resource within an identity context. More on that in a bit. Additionally, what I'm saying is my user resource has these attributes, a name and an email. So I run the command and out pops one, an Ecto schema. You'll notice that it's a user resource that's nested inside of this identity module. And then I'm gonna get this identity module and in this module definition, I'm gonna get a bunch of CRUD actions that allow me to modify this resource. Within here, you're gonna see calls to Ecto and you're gonna see a lot of the specific underlying implementation details to do so. Next, I'm gonna get a web controller and in this controller, it's gonna live outside of my identity context. It's gonna live in the web context and essentially what that web context does is it intercepts HTTP requests and then it turns around and it calls across to the other context. It calls to my new identity context and it says, hey, do your thing but give me what I'm asking for. And finally, we get a migration. And so just by looking at the files that come out of this migration or out of this scaffolding command, we can already see the general philosophy of this approach. All web concerns will live in the web context and context should encapsulate their persistence and domain logic. So right out of the box, the core team wants us to keep those two things separate. And finally, the outer context module is the public interface into that module for the rest of the system. Well, this kind of makes sense. I guess it makes sense. I mean, but I do have a few more questions. For example, what should I name the context? How did I come up with the name identity? Like, couldn't it have been something else? How should I think about resources that are needed in multiple contexts? How do I know if it's too broad or if it's too fine? And is it easy enough to change? And how do I know if my context, when will I know when it's getting too big and it needs to be split up or does it even need to be split up? And so we come to this core question of how do we design system boundaries? And this is a time old question that we've debated over the years. And it was introduced, the book Domain Driven Design was published in 2003 and it really helped us think about designing system boundaries. Now, when you go Google Domain Driven Design, you're gonna get both a set of high level strategic design activities and concepts, and you're gonna get these really low level specific concrete software patterns all in .NET or Java or what have you. And it's gonna be really confusing and there's gonna be a lot of noise and I don't want us to get tripped up in a lot of those things because Domain Driven Design has a lot of ideas. Many of them are amazing. Some of them are not necessary to the core ideas. And today, what I wanna get us to understand and appreciate are the core ideas of Domain Driven Design. Typically when we think of abstractions, we think in horizontal layers of abstractions. We wanna make a tiered application. We're gonna have a web layer. We're gonna have a domain layer. We're gonna have a persistence layer. You know, that's typically how we think about modularizing our code. But what if we took a bigger picture and what if instead what we did was we thought about vertically decomposed business use cases? What if we broke up our application in vertical slices? And many of you who practice maybe an agile design methodology or you're into behavior-driven development, you're already used to thinking about building features in vertical slices. So what if our systems more consistently applied those vertical slices? Today, you're gonna all join me in my imaginary company. It's called AutoMax and it's a used car marketplace. And the way it works is that sellers come to our locations and they bring in their cars for inspections and we basically take them into their garage and give their cars a rating and appraise their value and then we make them an offer. We buy their car. Their car is sitting in the lot. We post it online. An interesting buyer comes in and says, hey, I wanna test drive that for an explorer. I wanna test drive that Tesla or something. And they come in and they test drive the car. If they like it, they'll buy it. And so that's our business model. We're the middleman and we operate like this two-sided marketplace. Now our business is constantly changing and so our tech stack is constantly changing as well. For example, just in the past week, marketing wanted us to change copy on the website. The finance team wanted us to change how we do tax calculations. The operations team needs a new feature in the vehicle inventory system. The product team wants to do stuff in Bitcoin. I don't know why. And then customer support wants us to build a better support dashboard. Well, yeah, I mean, it's gonna happen. Your business is gonna put demands on your tech team and your software systems and we need to be able to react to them very nimbly. And so the core ideas of Domain Driven Design are to listen to the business. I'm gonna save everyone a lot of money. You don't have to buy the book because I'm gonna summarize in these two points. Number one, design your software systems according to business domains. Sounds obvious. And then two, pay attention to the language you speak in the business. When I first read that, I was like, what? What does that mean? Like the language? And it turns out that Domain Driven Design cares a lot about linguistics. Just kind of like where Emma's coming from but from a different perspective of the business. Literally, we need to listen to the folks in the business who are talking about the things they wanna build. And this brings us to our exercise of context mapping. Context mapping is a real life exercise where you have to actually talk to people on your team and you have to actually get people in a room and you have to listen and discover all the concepts at play in your systems. So it's really important, let's say you and I and our team and our product owner and our on-site customer and our business stakeholders and what have you. We get together in a room one afternoon and there we put up a big roll of butcher paper on the wall and everyone gets a bunch of sticky notes. And on these post-its, we give everyone the directive to write down all the nouns and the verbs that are present in this area of business. So let's imagine everyone's having a good time scribbling stuff down, slapping those post-its on the wall and they kinda get this type of system. This is a rough idea of what a potential activity could lead us to, but you'll notice that we have both entities, the nouns, and we also have the verbs of what happens to those entities. Most likely, it'll be far larger, more complicated, way messier, you'll probably have duplicates and all that stuff, but that's okay. The idea is to get everything up on the wall. Then what we're gonna do is we're gonna group similar concepts together. You're gonna notice some things just naturally seem like they belong together. For example, a website visitor in an online listing are probably things that are similar in their business purposes. So I've taken out some of the events because they were a little too cluttered and I've suggested that maybe this is what happens at the end of our grouping exercise. Now we take a step back and we start to observe that perhaps there's some natural clusters or some groupings that might be starting to emerge. And stepping aside now, let's do some definition of some terms. In domain-driven design, there's a concept of something called the core domain. This is what your business is here for. This is the main purpose that our company exists. So for AutoMax, our core domain is car sales. So what we're gonna do is we're gonna actually delineate that domain on our diagram by drawing a large circle of root. Second of all, we're gonna have a supporting domain. A supporting domain is an area of the company that isn't directly correlated with our core domain but it helps it accomplish that goal indirectly. So for example, you could have things like online listings or you might have financial transactions or maybe optimization and analytics and customer support and all these good things that really help the business do what it needs to do. And I'm gonna suggest, I will put forth that actually these natural clusters that we're kind of starting to observe are actually the subdomains. If you don't have an idea of what they are. So magically it's just so happens that we've identified perhaps together as a group what some of these subdomains might be. Of course, it will also never be this clean. Very likely you'll find duplicates, duplicate concepts between other areas and you'll have these discussions of things like, wait, is this the right name for the thing in this context? For example, in customer support, you might have this conversation where you might be like, do we call it a support incident or do we call it a support ticket? Are they a customer or are they a browser or something like that? Anyways, you're gonna have a lot of these discussions and it's very important to capture these discussions and put them up on the wall. Because at the end, what we're gonna come up with is something called a ubiquitous language and in domain-driven design the idea is that there's a language that the business speaks that must be captured in our code. So in an ideal world, the words that roll off your business partner or business stakeholders' mouth or the stories that are written in your backlog should correspond and cleanly map to concepts that are captured in your code. A lot of times we put these terms and terminologies into a glossary, which is a fancy word for basically a list of terms and definitions. You can throw this in a Wiki or a Google Doc and then the idea is that you keep this updated and you have the team continually refer back to it. And finally we get to the idea of a bounded context. This term is kind of what the core team had in mind for the term context. And it's got a few nuances to it. So concretely, a bounded context is a encapsulated software system. So it doesn't necessarily need to be an app, although it could. It doesn't necessarily need to be a service or an API, although it could. It could also be something like a module or a package or a Ruby gem or just anything that is something that can live on its own independently and run as real software. But linguistically, there's an even more important definition of a context. And it's a boundary for where a term, a concept or definition is allowed to live, but it only lives in that context. And the reason why is a little subtle, but I'm gonna go into it just right now. For example, let's say I have a concept of a rating. A rating is something that our mechanics give to our vehicles in our inspection context when the vehicles are in the garage. They rate the car and they give it a grade of okay, good, grade, fair, mint or something like that. But however, in the other part of the system, in the customer support sub-domain, a rating is something the customer gives us. Hey, rate your experience with us. Was it good? Zero to 10, please tell us how your experience was. And these two concepts have the same name. They're overlapping, but they actually belong. They deserve to be modeled on their own. Now, if we slapped everything together in one large software system, you might find yourself doing funny things like appending a prefix to the name of this concept and then doing something to the other concept to make them independent identities. But by the very fact that you're encapsulating them in their own modules allows you to express the fullness of what they are and to keep it really clear. So this allows us to have precise language, precise models, and our domains can express themselves as clearly as they need to be. So what I want us to do is to really become language addicts. We're gonna be really passionate about keeping our systems in line with what our business stakeholders are saying. And if there's anything that you take away from my talk today, it may not be a single drop of elixir, but what I really want us to understand is that our systems should match what is being spoken in the business. So everyone be one with your domains. Okay, finally, we're at an elixir conference, so we're gonna actually go and talk about elixir. So let's go start by looking at a Phoenix context. A Phoenix context, as you can see, exposes concepts to the outside world. So over here, our inspection context is exposing a vehicle and a mechanic, but also you can have your context express real actions that need to happen to do something in the real world. So for example, we're gonna add a vehicle to the garage queue so our mechanics can work off of the queue, or we're gonna build a feature to attach a rating to a vehicle that a mechanic applies to a vehicle. So we're gonna start right here with the UI. This is something that, like a very simple webpage, that a mechanic might pull up to go assign a rating to a vehicle. And maybe in the old world, this is what the controller that backs that form action might look like. So as you can tell, it's a little messy. It's got a lot of database concerns mixed up into the controller action. And then it's doing a few things here that really are separate things that need to be broken out. I won't go too much into it because we're about to go refactor this stuff. So one of the things we're gonna do is we're gonna pull out the correct concepts into their correct boundaries, into their correct context. So for example, we're gonna pull out the user first. The user moves into its own schema that's in the identity context, and then the identity context gets the ability for the outside world to go fetch it. Likewise, we're gonna also do the same thing for the vehicle. A vehicle and a vehicle rating are both gonna move into the inspection context and be linked up together. And finally, we're gonna build an action in the inspection context to actually encapsulate the persistence of a vehicle rating, but also perhaps it's gonna do some other internal things like check a policy function to determine whether the user is authorized to do this thing, to perform this action. And finally, we then come back to our controller and we read it again. And so the idea is that a lot of these domain ideas and domain concepts are a little bit more fluent here in the controller action. So as you can see, we're now calling only out to contexts and we're not really cluttering the web code with concerns that it doesn't need to know about. In my opinion, this is way better. So what do we notice here? Context only expose methods at their outer layers. They hide internal implementations and the domain language we use to express these concepts is encapsulated and captured in the code itself. All right, let's move on to a couple of other more advanced topics. The first of which is concept sharing. So we get in trouble when we need to use a concept in multiple domains. So for example, very likely your systems have a user concept or an idea that is probably actually implicitly attached to multiple domains. So over here, we've modeled a user that's in the identity context. The user's job is really to capture the idea of logging in of basically authentication. Perhaps it deals with OAuth credentials and yada yada yada. But the idea of a user kind of breaks down when we move into the inspection context. When that mechanic logs in to their part of the system, we actually call them a mechanic. And this mechanic actually to that part of the system, it doesn't need to know about its OAuth credentials. There's actually a view of that user that really is mechanic focused. The same thing with marketing. Let's say that we're building a landing page and really a logged in user is really just another website visitor. So how do we capture the nuances of what each domain says about its own concepts but kind of link them to the same thing under the hood? Well, here's a few ideas for what we can do. Number one, we can just do nothing. We can actually just do things as we used to do them. So maybe what we do is in the process of refactoring, maybe it's okay just to have foreign key references to a concept that's elsewhere in another context. I personally think it's okay if you're refactoring, but I think there are a few other things you can do that might be a little better. The second thing we can do is something that I call strut conversion. So the idea is that we convert external concepts into internal concepts at the outer layer. So essentially our outer context is gonna have a function that converts an external concept. In this case, we're gonna take a user that's in an external identity context and we're gonna say, hey, I know about this external world. I'm gonna go convert it to my internal representation that is relevant to me and myself only. And then callers then will use my internal representation to then perform domain actions on myself. So here's how then the caller might do it. It might start out with the logged in user. Once the system logs you in, you get into user. Well, now I need to call through the marketing domain and the marketing domain needs a visitor. So I'm gonna go first convert it to my visitor and then I'm gonna pass it through the other actions in my marketing domain. And here we can actually leverage a lot of the powers of the Elixir ecosystem. We can really lean on pattern matching and types. And so this is one of the examples of using the full powers of Elixir whereas in other dynamic languages, you may not have been able to do this. Even better, you can use type specs to further enforce the correctness of the types of data that you're passing around and to make sure that you're really only ever working with internal concepts that are valid to your part of the system. Finally, the third option is we can make what I call a collaborator schema. And so the idea is that perhaps you have a use case where you need to both read and write to your internal domain model. Just doing a straight up struct conversion is not enough. For example, let's say my mechanic logs in but I actually need a corresponding mechanic concept persisted to the database because there's extra stuff about mechanics that my user model cannot capture. So for example, maybe my mechanics need to be tracked whether they're contractors or not or skills and certifications need to be also encoded or persisted. So over here you see I've created a schema for a mechanic and what I'll do is now in my conversion function it's gonna go and create a mechanic as it converts from a user and then it's gonna go we're gonna add a conversion function that then looks it up from the database and then returns the corresponding idea. And these last two examples of what I gave you are what in domain driven design terminology would call the anti-corruption layer. We welcome outside concepts but we convert them internally to be our internal concepts. Here's a little bit, here's a small aside and it's that I would encourage you to avoid cross context joins if you can. So for example over here we have an internal concept the visitor that has a join under the hood in Ecto to the user. Now this is maybe not a big deal but if you ever wanted to then like do further refactoring or move away from Ecto or even extract that context into its own API or microservice you would be in trouble. So perhaps one way you could do is to avoid these hard database linkages and maybe just store the external foreign keys but store them in a way that doesn't couple your two contexts together. So for example you could now maybe expose it as a UUID or even just store it as a string and that way further callers or other engineers on your team will know that they're not really supposed to be linking these two concepts. A couple other ideas, the first one is called an aggregate and aggregate is kind of a tree like structure of data that belongs together. Data that naturally belongs together should ship around together. Here's what I mean. Let's say in this inspection part of the system we have a couple of internal concepts and maybe if we ran the generator we would end up with a context that had CRUD actions that allowed us to update ratings and vehicles and list prices and all that good stuff. But do we really need all of those functions at the outer layer? Because now the outside world has extra stuff to worry about when it then turns around and wants to do things to our context. So what if we just said, hey outside world, you're only ever allowed to access the root of this graph. And I say that these concepts really only make sense when they're rooted on a vehicle. The vehicle is our aggregate root. And so therefore we can kind of simplify our APIs. So instead of the outside world needing to know about everybody, the outside world actually just gets the vehicle and then it traverses the root to go find the data that it needs. And so we'll end up shipping this graph around through our APIs or through a message bus or what have you. Much in the same way we also don't want it to write to specific parts of this graph, this aggregate. So over here maybe somebody was trying to update the rating and then we allowed the outside world to know about the rating ID. Well this is an internal implementation. Instead what we want to do is we want to allow the outside world to tell us, hey I have a vehicle and I want to update the rating but you know about the actual specifics about how to update it. So I'm going to let you deal with it. And so by leveraging aggregate roots we have the ability to minimize and simplify the APIs that we exposed to the outside world. And they also have additional powers that allow you to see data for its full context. We're able to see like if you call an API you can see the state of the system as an aggregate and that'll actually help you design your systems with more confidence about the synchronized states of all this data. Finally this is maybe a more advanced topic if your system gets to this point you can introduce event driven messaging between your contexts. So let's say I have part of my context here and it's going to go and do a few side effects after it completes. Let's say that when a user registers for an account we then correspondingly create the right concepts in other contexts and then we subscribe the user to an email and then we send some analytics event to some outside provider. This is fine and good but then it clutters the code and then now this code needs to know about the APIs of all these other contexts. What if instead what we did is we publish events that is facts about the world over a bus. Those of you who are familiar with the PubSub publish subscribe patterns know that this is a nice way to kind of invert dependencies. So instead of the upstream caller meeting to know about downstream side effects we have the downstream consumers subscribe to events upstream. And as a benefit our context map actually already included the names of the events that we need to publish. So let's see what it might look like. Let's say that we publish an event. I like to follow a format, a three part format where I begin with the name of the context then I put a period and I say the name of the resource that's the user and then finally the name of the action in past tense. That's just my personal preference. And downstream subscribers will now subscribe to this event. Every time they see that event published over that topic it's gonna then expect a user struct or user map on the payload and then it can do its thing correspondingly. So I'm gonna use a library called event bus. There are plenty other ways to implement messaging because Erlang is one of the best places to be implementing messaging. There are ways to do this with GenStage. There are ways to do this with RabbitMQ. You choose your thing but the overall sketches of the idea are here. So here instead of calling downstream my event source is gonna just publish a fact over the bus. It's gonna say, hey I'm done, I created my thing and downstream whoever's downstream for me can now do their own things. And now downstream context can now subscribe to it. So over here my marketing event handler is now gonna subscribe upstream to the user and it's gonna say every time a user is created it's gonna go subscribe to the user to the email list. And it's the same thing with our inspection context. It's gonna now create a mechanic from the user. And so this is a nice way to also further decouple like what every context needs to know about other contexts. Of course every architecture talk needs a bunch of caveats. And the first one is that we really need to start small. If you have an existing system or if you're starting from scratch even don't try to do everything at once. Don't try to take all the patterns I just talked about and just build them, bake them in from day one. And the reason why is because you're gonna find yourself kind of falling over from all this complexity that you threw in when your system didn't need it. Also if you have an existing system you can take one context at a time and try it out. So you can start by extracting your identity context or your account context or something like that. And then you can build out small things from there. See how it feels. If it's a lot of pain or if it's overkill, roll it back. You don't have to do it. Second of all I wanna caution us from also just kind of cargo quilting a lot of the patterns we see out there on the internet. Instead, pay attention to what the business says about itself. Use those concepts. Name things the way that they're spoken about. If we did all those things, I feel like our lives as programmers and working in our systems would be way better. Sometimes the simplest things to do are also the hardest. So maybe we just have to get our hands dirty and start renaming. And finally systems of a certain scale. I talked about this already but sometimes your system's just not ready for it. Maybe you just gotta wait a little bit or until you actually feel the pain before you start to implement the solution. So in conclusion, we've listened to the business. We've made all the concepts say the same thing. We've defined strict boundaries between concepts. We've chosen to use specific language and we've made our domains more clear. We're using aggregates now to simplify APIs and we're using an event bus to decouple publishers from subscribers. And finally, we're using the full powers of Elixir. We're leaning on types and pattern matching and type specs and all that good stuff. Elixir's an amazing language to be working with and it's a great time to be a developer in this community. And that's what I think makes a beautiful system. Thank you everyone. Thanks a lot Andrew.