00:00:26.800
I'm going to get started. This is a pretty dense talk, so I need every minute I can get.
00:00:34.880
If you go to my website, it's just my name dot com. It will have the slide deck, and if you want to follow along, that would be great.
00:00:41.440
Here we are, let's get started. First, I want to tell you a little bit about myself.
00:00:48.079
My name is Mike AbiEzzi, and I'm a software consultant. I work at Quick Left, which is a mobile and web application company. I live in Boulder, and I've been here for about three years. I love it here, and I'm very excited to be at Rocky Mountain Ruby presenting to you.
00:01:03.280
I'm also really nervous because I know a lot of you, so I hope I do well. I wish myself good luck.
00:01:10.080
Building a complex domain is a difficult thing to do. This is the book 'Domain-Driven Design,' written in 2003 by Eric Evans. It's a pretty dense book, and Eric is the guy who pioneered this concept.
00:01:24.159
Because he did pioneer the concept, this is the de facto book on the subject. If you haven't read it, I highly recommend it.
00:01:37.520
How many here know what Domain-Driven Design is? Good, about 30 percent. How many of you have tried to use Domain-Driven Design in a Rails application? Great! I'd say about 20 percent.
00:01:55.680
So, it's interesting. We're going to be building a Rails application using Domain-Driven Design principles. First, let’s get some basics out of the way.
00:02:08.560
What’s the domain? A domain can be a doctor's office with doctors, patients, and record keeping. It can also be a new idea, such as Twitter, which is a microblogging service with tweets, followers, and followings. The domain can be anything; it can even be collecting really cute cat GIFs.
00:02:26.239
The point is that a domain is the subject matter around which you're building the software. I’m glad to clarify that.
00:02:39.519
Why does Domain-Driven Design exist? Well, let's look at an example. Say we're building a Rails application with a few models and a small team of two developers.
00:02:52.480
We’re really productive, turning out features and feeling awesome! That's kind of what made Rails popular in the beginning.
00:03:05.519
However, over time, your Rails models get bigger—maybe 50 to 100—and your team also gets bigger, perhaps quadrupled. You begin to slow down.
00:03:17.360
There's more defects than there used to be, and turning out features becomes more difficult. A lot of us have felt this pain.
00:03:30.159
As a consultant, I see many client applications that have gone through this. They become big monolithic Rails apps that are hard to rationalize, making it difficult to get anything done.
00:03:40.720
So, we end up feeling like this turtle. The point of Domain-Driven Design is to address the fact that software becomes complex quickly and to create structure to tackle that.
00:03:55.680
What this talk is not about: There are many topics on DDD, and I want to set expectations with everyone.
00:04:01.360
It's not about custom architecture for Domain-Driven Design, design patterns specific to DDD, or advanced topics like what a bounded context is.
00:04:14.640
Custom architecture is expensive. If you're a startup, there's a reason startups like Rails—because it’s fast.
00:04:34.160
We don’t necessarily want to go custom right away. We may not have even proven our market—we might just throw away the app in the end.
00:04:46.400
So, we have to be very conscious about when we take on custom implementations. They’re all great, but there’s a time and place for it. What we are going to talk about is taking Domain-Driven Design principles and applying them to a Rails application.
00:05:07.680
We'll also talk about how to structure your Rails application in a way that allows you to transition to custom architecture in the future. We want to move fast, utilize Rails to its full potential, and be productive.
00:05:22.240
So, what’s this?
00:05:35.760
What does this tell us about our domain? Nothing. Perfect.
00:05:40.640
So, we’re going to try to fix this. We’ll discuss seven topics: first, how do we go about defining the domain and what that process is like.
00:05:55.680
We’re going to talk about how communicating the domain is important, especially with a large team, to survive that large team.
00:06:05.760
Next, we’ll discuss relationships, why they’re important, why they might not be important, and then we’ll talk about aggregates, data access, value objects, and domain services. We will explore what all of those things are and how to implement them in a Rails application.
00:06:30.800
So first, we need a domain. Let's pick a domain that we can build a Rails application around. I'm going to pick the iOS App Store, something we are all familiar with.
00:06:57.680
Let’s pretend that Apple executives, named Scott, came to me. He wants us to build this app store for him.
00:07:12.000
We’re going to sit in a conference room together with a whiteboard, brainstorming ideas for what this app store is about. Scott mentions, yes we’re obviously going to have apps; developers can create apps.
00:07:29.680
Any developer can create an app and submit several versions of it as they make iterations. They can also add one to six screenshots. Customers are going to be purchasing this app.
00:07:43.760
Customers can install specific versions and leave comments on a version of the app. So we did this brainstorm dump with Scott. We let him get everything out of his head. We don’t interrupt, just draw it all out and take a step back.
00:08:04.080
Now, we start looking at this and refining it, giving him a visual that we can work on. I start asking about developers. I ask, is it only developers that can create apps?
00:08:20.640
What about companies like game companies? Can they create apps as well? Scott confirms that yes, companies can create apps too.
00:08:34.560
So, I note these two terms down because the word developer wasn’t enough to capture that concept. I then ask him if there’s any difference between a developer and a company in terms of how they create apps.
00:08:47.120
He says no, they’re pretty much the same; they do the same thing. I suggest that a better word than developer or company is 'seller' because they’re both selling their apps.
00:09:02.080
Scott agrees that 'seller' works, which we’ll use going forward. Looking at version, he used the words version and release interchangeably.
00:09:15.040
I thought of versions; they have version numbers but it's awkward to say a version has a version number—it’s redundant. I hit on 'release' as being the right term.
00:09:28.080
I discussed it with him; he agreed that ‘release’ was better. When we look at comments, I ask Scott what else might users leave.
00:09:39.520
Are they going to leave ratings as well? He says yes, they’re going to leave ratings. So the term 'comments' isn’t sufficient. How about the word 'review'? It captures both comments and ratings.
00:09:56.640
Now we’ve refined our domain. We went through this exercise, considering the language we’re using and ensuring its accuracy for the domain we're trying to bridge.
00:10:09.440
Let’s talk about the finer points of communication we just demonstrated. There are two sides: you have your domain expert who knows how the domain works, and then you have the software expert who knows how to build software for the domain.
00:10:25.440
You go through iterative brainstorming and drawing diagrams while speaking out assumptions. It's very important to use natural language here, as you'll be doing a lot of filling in the gaps.
00:10:38.560
As the domain expert, you are looking for anything that feels awkward or is just not quite right for the domain. Meanwhile, the developer looks for terms that are being used to describe the same concept, ensuring you don’t use two words for the same thing.
00:10:55.360
We are defining what Martin Fowler describes as the ubiquitous language—which is a common, rigorous language between developers and users.
00:11:11.440
The need for it is rigorous, as software does not cope well with ambiguity.
00:11:24.080
From your user to your product owner, to your domain expert, tester, developer, designer, and back to your code, it’s crucial to ensure consistent language across the board.
00:11:40.320
Without this, you’re going to create a fragmented language, leading to confusion. More worrisome is that your code will start having fragments of language, and without understanding your domain, how can you build it?
00:11:57.440
If there’s nothing else to remember from this presentation, it's this: ensure you have one language throughout your team. If you change terminology in your UI, make sure everyone's on board and update your code.
00:12:10.080
It’s the only way to maintain control of a complex domain: communicate efficiently, and ensure that everyone understands it properly.
00:12:22.080
Next, we’ll talk about relationships. Let’s revisit our diagram and look at the relationship between an app and a customer.
00:12:30.640
We have the app that has many customers through purchases, and below we have customers who have many apps. We need to question which of these relationships are valid.
00:12:48.480
For instance, it makes sense that a customer should know about the apps they purchased in order to redownload them later—so that relationship is valid.
00:13:06.640
Now, do apps need to know all the customers who purchased them? You might come up with statistics reasons, but for our MVP, we want to keep it limited.
00:13:23.040
We don’t need a list of all the customers that created an app. Therefore, we’re going to nix that. Similarly, we’ll look at install relationships.
00:13:37.680
Does our app need to know which releases customers installed? All releases or apps will be installed on the customer’s device, so we can send a list of all apps with versions to the server.
00:13:54.240
The server can check which versions have updates, so we don’t need that relationship either.
00:14:06.640
Regarding customers and reviews, do we want them to view a list of apps they reviewed? As far as I know, iOS doesn’t have a mechanism for that, so we don’t need that either.
00:14:18.560
What we've done is distilled our domain down to just the relationships we need. The benefit is that we’ve decoupled things that didn’t need coupling.
00:14:29.440
Whatever relationships remain are the important ones that actually matter to the domain. When we’re looking at an application and a relationship, we know it has a specific purpose.
00:14:48.560
Now, let’s get into aggregates. Finally, we’ll get into some code. Imagine we’re submitting a new release of an app.
00:15:06.640
There’s a chunk of code here; we're creating a new release, passing in a version number, creating a release object, and adding screenshots to the release while setting its status to submitted.
00:15:20.760
Then, we add the release to our app. A better way to handle this is to encapsulate it in a method called 'submit_release.' That method name describes the domain behavior we are trying to represent.
00:15:36.320
Notice that the method resides on the app; the app controls the release and the screenshots, which makes sense because an app has releases.
00:15:57.120
It’s simpler. We have a straightforward representation of that action. Furthermore, this method centralizes the complexity of managing the submission.
00:16:13.680
Now we see what an aggregate looks like: in our example, the app is our aggregate responsible for managing things like the release, screenshots, and reviews.
00:16:35.680
Everything that is done with screenshots, releases, or reviews has to pass through the app. You never manipulate them directly.
00:16:47.440
By doing this, you're decoupling the small subsets of models from the rest of the system. If this is repeated, you will build multiple aggregates controlling their own complexities.
00:17:04.800
As we look at our app, we see all these domain behaviors: submit release, approve release, flag for abuse, mark as a staff favorite.
00:17:15.440
These methods help us understand what's important for the domain. They give insight into what can be done.
00:17:24.240
Let’s shift focus to data access in aggregates. I have an app, and I’m looking for apps with a where clause.
00:17:41.920
Here, I’m looking for apps that are less than a week old and have been purchased more than 10,000 times. While that’s clear, it can frustrate me.
00:17:57.440
I understand what you’re doing, but I want to know why you're doing it. I’ll give this a big fat 'wha' to express my confusion.
00:18:05.120
So, what’s a better way of doing this? Rails gives it to you! It’s called 'scopes.' A scope gives a name to the query that you have.
00:18:22.880
Now we know that the meaning of this query is 'new and noteworthy'—we want to know what the new and noteworthy apps are in the app store.
00:18:36.960
Another cool thing is ‘fig leaf’ created by Avd Grim. It can privatize all those Active Record methods like the where clause so they can’t be used outside the app.
00:18:50.560
This serves as a reminder that you should only be writing queries within scopes. Of course, developers can always find a way around that, but this is more of a reminder.
00:19:01.840
With great power comes great responsibility, right?
00:19:11.680
Now, looking at our app again, we have different ways to access data, including our behaviors—indicating what can be done with it.
00:19:27.680
We’re establishing one expressive point of entry for aggregates. Remember, aggregates are the domain's only point of entry for data access.
00:19:41.920
If you want information on releases, screenshots, or whatever that might be, you’ll always go through your app.
00:19:58.560
This will keep data access concentrated, which is pretty cool.
00:20:10.320
Lastly, let’s talk about value objects. This tends to be the most complex and lengthy topic, so I’ll roll through it.
00:20:28.240
There are two types of domain objects: entities and value objects. An entity is a thing; that’s the simplest way to describe it. A value object, however, describes a thing.
00:20:44.160
For instance, we may have a customer object as our entity and a name object as our value object. If I’m a customer, my name is Mike AbiEzzi.
00:20:59.680
If my cousin is also a customer and his name is Mike Abiezi, we’re still two unique entities. We have our own life cycles and can deactivate our accounts independently of each other.
00:21:09.760
Our attributes may overlap, but that does not mean we are the same. That's what makes an entity unique; it has independence, attributes, and a life cycle.
00:21:25.440
Conversely, a value object describes a thing. If I have a name value object and throw it away to create a new name object, it doesn’t matter.
00:21:39.440
This characteristic—being able to create and dispose without repercussions—is what defines value objects. This leads us to make them immutable.
00:21:56.560
Immutability is beneficial because entities can change state. You can create an entity or initialize it through a constructor, setting its initial state, which may change.
00:22:10.320
On the other hand, a value object has only the constructor, which establishes its state and then concludes.
00:22:24.080
They're not complex in terms of being built, making them easier to understand. Also, when dealing with value objects, nothing can accidentally alter their value.
00:22:39.920
In contrast, entities can change, leading to unforeseen side effects across different functions.
00:22:54.880
Value objects simplify design complexities. If we have 100 models and identify 30 as possible value objects, we can reduce domain complexity.
00:23:05.680
We don’t want to unnecessarily increase the complexity.
00:23:21.680
In our example, we know all aggregates are entities—these complex things that can build and change state.
00:23:36.560
A release has a life cycle; it gets created, and older releases become invalid as new ones come out. Now, what about screenshots?
00:23:52.080
Do they have a life cycle? They somewhat do through the release, but are they important independently? Not really; they merely describe a release, making them value objects.
00:24:06.880
Now, let’s look at reviews. If we look at reviews in the context of Amazon, you can edit and view lists.
00:24:20.560
However, for our app store, we want people just to create a review. If they want to update it, they create a new one, and we delete the old one behind the scenes.
00:24:36.480
So, we treat it as a value object. What does a value object look like? It's simply a plain Ruby object. It does not inherit from anything, especially Active Record.
00:24:52.160
It has a constructor for taking in attributes, exposes read-only methods, and overrides equality methods to ensure fast comparisons.
00:25:06.240
A good method for value objects is a factory method.
00:25:11.840
For instance, when it comes to a string, we can use the 'upcase' method to generate a capitalized version. It’s a very natural behavior for value objects.
00:25:23.920
Let me show you a value object that increments the major version to demonstrate a simple behavior.
00:25:38.400
The tricky part is understanding how to persist value objects.
00:25:45.440
We’ll discuss three ways: inline on the entity’s table, serialized on the entity's table, and in its own table.
00:26:04.000
First, inline on the entities table. Suppose we have a release table with attributes for major, minor, and build. We want to store value objects separately.
00:26:18.560
To achieve this, we create a writer method called 'version_number' that takes in a version number object and assigns its attributes to the release attributes.
00:26:34.080
When reading the version, you instantiate a new version number object with the release's attributes.
00:26:49.040
The second example involves screenshots stored as an array saved to a single column called screenshots on the release table.
00:27:05.600
You’d accomplish this with a JSON serializer and deserialize when needed. This is a very basic example; for arrays, a more advanced implementation would be required.
00:27:21.760
The third example is to store value objects in their own table. In our example, we have reviews. Unfortunately, these must inherit from Active Record.
00:27:38.080
There’s a method in Active Record called 'read-only bang'. It locks your model, preventing the alteration of attributes after being set.
00:27:54.240
However, it can still be awkward—allowing you to set attributes before raising an error. Thus, we’ll create a method that raises an error when attempting to write to an attribute.
00:28:09.760
We’ll wrap this into a method called 'immutable,' so it can't change after saving, ensuring that any changes recreate the value object.
00:28:20.160
Lastly, let’s discuss domain services. Aggregates control their own complexities. If we need to process transactions between two aggregates, we require a third party.
00:28:37.600
This is where domain services come in. For instance, if we want to gift an app, we need to charge the gifter and assign access rights to the giftee.
00:28:58.640
Both the gifter and giftee customer objects are aggregates that can’t directly interact; therefore, we utilize a domain service to facilitate this cross-aggregate transaction.
00:29:14.760
The domain service is a simple Ruby object. Its name describes the behavior the domain fulfills.
00:29:27.760
You define an execute method to handle the transaction, taking in the app, gifter, and giftee as parameters, creating purchase records and access rights.
00:29:44.080
You would often want to utilize database transactions, but this gives you an overview of structuring these services effectively.
00:29:59.760
Now looking at our Rails directory again, we create a services folder that includes all these domain services, enabling oversight of these cross-aggregate transactions.
00:30:10.320
In summary, we talked about how to continuously refine the domain with a domain expert. We emphasized the necessity of a ubiquitous language for seamless communication across your team.
00:30:25.760
We discussed constraining relationships to only the necessary ones, creating aggregates to manage complexity, and express domain behaviors.
00:30:40.080
We reviewed how to express domain intent through well-defined data access, create value objects to eliminate unnecessary complexity, and establish domain services.
00:30:54.160
Looking again at our Rails directory, we started with just Rails code. Now, we have defined our domain clearly.
00:31:07.840
Here, we have aggregates, all the entry points of our application, and know where to retrieve data and enact domain behaviors. We also understand all the services that define transaction behaviors across aggregates.
00:31:23.200
So we’ve effectively built a Rails application that reflects our domain.
00:31:32.160
Thanks for listening! I’d like to thank Quick Left for sponsoring me. We're hiring, so if anyone's interested in working at Quick Left, come talk to me.
00:31:46.560
A special thanks to Paul Rayner from VirtualGenius—he's been a kind of Domain-Driven Design mentor for me.
00:32:00.320
I’m available online and looking to find a few people to mentor across different topics. If you’re interested, feel free to visit my website and go to the mentor section.
00:32:18.080
Now, are there any questions?
00:32:25.760
So the question was if I could expand on how value objects keep us out of trouble. It's simple: they simplify objects to a point where it's hard to mess up.
00:32:51.440
If they can change, then managing state could become quite cumbersome, creating complexity. However, a value object keeps everything controlled in one method.
00:33:06.720
Additionally, when you pass a value object through functions, it cannot be altered accidentally—reducing side effects.
00:33:21.680
So, if you have an object that can change state, it leads to unforeseen alterations through multiple functions, which can create confusion.
00:33:34.000
Another question was about translating this for an API, where it feels more MVC-centric. I believe it works perfectly for APIs, as you’re getting to the core of how your domain operates.
00:33:46.160
You will want to create your domain to represent that effectively. In fact, I find it potentially even easier.
00:33:59.360
Okay, great! Thank you, everyone!
00:34:02.560
So, cheers!