DDD for Rails Developers. Part 1: Layered Architecture.

Share this article

What is DDD

There are many kinds of complexity that you have to deal with developing software and different kinds of applications will have very different sets of problems you need to solve. If you are building the next Twitter, scalability and fault-tolerance are the problems you are probably fighting. On the other hand, these problems are almost never an issue when working on enterprise applications. The complex domain is what you tackle when developing enterprise software. Business processes of a lot of companies are far from being trivial. Thus, refining the domain that provides loose coupling and will be flexible and maintainable in the future is extremely hard and requires a lot of practice and knowledge.

The Book

Eric Evans – the author of Domain Driven Design – coined the set of practices and terminology helping in tackling domain complexity. His book is a must read for every developer working on enterprise applications and I highly recommend it.

DDD and Rails

Working more and more on large rails applications I’ve noticed that in many ways DDD and Rails contradict each other. Therefore, I’ve decided to write a short series of articles, which will be my attempt to reconcile both paradigms and to find a way to use DDD while not fighting Rails. Before I start, I’d like to mention that I’m going to write about introducing DDD concepts to an existing application. Therefore, despite that Uncle Bob’s approach (check out this awesome talk) may look appealing, introducing it to an existing Rails application with hundreds of thousands lines of code is, probably, the last thing I want to do. Hence, everything I’m going to write about here is, in some way, a compromise.

Layered Architecture

One of the core concepts of Domain Driven Design is the layered architecture. Let’s take a look at what it is, what kind of benefits it brings, and how a typical Rails application violates this fundamental concept. First of all, the term “Layered Architecture” means that you partition an application into layers:
  • User Interface. Responsible for showing information to the user and processing the user’s input.
  • Application Layer. This layer is supposed to be thin and it should not contain any domain logic. It can have functionality that is valuable for the business but is not “domain” specific. This includes generating reports, sending email notifications etc.
  • Domain Layer. Responsible for describing business processes. Abstract domain concepts (including entities, business rules) must be contained in this layer. In contrast, persistence, message sending do not belong here.
  • Infrastructure Layer. Responsible for persistence, messaging, email delivery etc.
The most important idea behind this architecture is that every layer should depend only on the layers beneath it. Thus, all dependencies have the same direction. For instance, the domain layer might depend on some pieces of infrastructure but not the other way around.

Layered Architecture and Rails

Now let’s take a look at the most common violations of the layered architecture I see in typical Rails applications:
  • Domain objects serialize themselves into JSON or XML. In my opinion, there is no big difference between representing an object as a piece of html and representing it as lf a piece of JSON. Both are meant to be consumed by external systems, both are parts of the UI layer. So every time you override the as_json method you violate the core idea of the layered architecture – you change the direction of your dependencies between layers. Your domain objects start being aware of the UI.
  • Controllers contain big chunks of business logic. Usually, it’s a few calls to the domain layer and then persisting changes using low-level methods such as update_attributes. So next time you go and call update_attributes inside a controller, stop and think again: most likely you are doing it wrong. Thankfully, most Rails developers have already realized that this kind of controllers hard to maintain. I believe, there is no excuse for doing it – even for small applications.
  • Domain objects perform all sorts of infrastructure related tasks. As a community we agreed not to write fat controllers, but we hit another problem – “Swiss army knife” domain objects. Such objects, besides describing the business, can also connect to a remote service, generate a pdf or send an email. If you come across such an object it needs to be split into at least two: one that is responsible for the domain knowledge and others for performing infrastructure related tasks.
  • Domain objects know too much about the database. If you extend your domain objects from ActiveRecord you couple the domain layer to the infrastructure layer. Your domain objects play two roles at the same time. This kind of coupling makes them almost impossible to test in isolation.
Most of these problems can be fixed. Let’s take a look at all of them to see what can be done.

Make a Separate Class for JSON Serialization

You should never override as_json or other similar methods in your domain classes. Remember, that the responsibility of the domain layer is to reflect the business concepts. I have not worked on a domain where JSON was an important concept. Therefore, if you are not writing a JSON parser, move all JSON related stuff out of your domain. Imagine a controller having an action that looks like this: [gist id=”1974980″] If you need to customize the JSON serialization of the Person class, don’t do it inside the Person class. Create a separate module (e.g. PersonJsonSerializer, PersonJsonifier) that will be responsible for it. [gist id=”1974986″] Now, your controller will look like this: [gist id=”1974993″] What have we achieved by moving the JSON serialization to a separate class?
  • Our domain remains abstract without any knowledge about the UI.
  • We split two responsibilities: being a person and serializing it to a JSON. If we didn’t do it, the Single Resposibility Principle would have been violated.
  • In addition, we made our controller easier to test. You don’t even need a real person object to test the serializer. Just stub it out. Also, you don’t need to check two branches of that if statement in your functional tests. You can just stub the serializer out. So instead of two tests, we have only one.

Controllers Don’t Contain Any Logic

In a nutshell, controllers should not contain any logic apart from parsing the user’s input or rendering proper templates. If you have a piece of business logic inside a controller move it to the domain layer. There is a misunderstanding that the domain layer consists of only persisted objects. And when you have a complex operations involving several objects you need to orchestrate it inside a controller. This is just wrong. If none of your entities seems to be a good place for this functionality, create a service class (or a module) and put it there. Imagine, we have such an action: [gist id=”1975012″] A better way to do it: [gist id=”1975016″] We’ve achieved:
  • The controller is easier to test. When before we would have to have two tests, after our change is done we need only one.
  • In addition to making our controller easier to test, we’ve made the business logic part explicit and also easier to test.

Domain Objects Should Not Know Anything About Infrastructure Underneath

In short, the domain layer should be abstract. This means that all dependencies on any kind of external services don’t belong there. Imagine that we are developing a blogging engine and one of the requirements is to send a tweet each time a post is published. What is considered to be an “OK” practice is to handle it in the after_create hook: [gist id=”1975022″] Even though it’s only a few lines of code and doesn’t look like much, it is a big deal. Firstly, you’ll have to stub out the Twitter service in all unit tests, which is not what you usually want to test. Secondly, it violates the Single Responsibility Principle, as storing information about a post is not the same as sending notifications to Twitter. Sending this kind of notifications is rather a side effect, an additional service, which is not a part of the core domain. Finally, Twitter can be unavailable and accessing it synchronously is not a good idea anyway. There are several ways of decoupling our model from infrastructure. One of them is to observe the Post class: [gist id=”1975026″] Even better way of doing it is moving all the responsibilities of generating tweets from the observer to the TwitterService class: [gist id=”2007956″] In summary, extracting this responsibility from the Post class has helped us to achieve the following:
  • It’s easier to test the Post class as you don’t need to stub Twitter out in every test.
  • Testing the TwitterService class is also straightforward. We even don’t need an instance of Post to do it.
  • We made the domain boundaries of our application explicit. We have the core domain and we have Twitter integration: two completely different domains and, as a result, two classes separated from each other by an observer.
  • Having an object responsible for integration with Twitter allows us to change its functionality (for instance, make it asynchronous) without touching the core domain.

Isolate ActiveRecord

I left the hardest problem to the end: coupling with ActiveRecord. Since the domain should be abstract the database schema should not affect how we design our entities. However, we all live in the real world and that’s why it never happens. The following things you should consider developing your domain: what objects are transient, if it’s easy to map the graph of objects into your relation schema, performance etc. Taking all these properties into account is important. However, you should not design your domain in a way that it’s impossible to test it without using the database. The DDD approach is to extract a separate layer responsible for persistence. For instance: [gist id=”1975036″] Having a separate object responsible for persistence simplifies testing a lot and makes it possible to provide alternative implementations. For instance, it’s a common practice to implement SQLPostsRepository and InMemoryPostsRepository. Therefore, you can use the first once for your integration tests and the second one for your unit tests. When your domain is not coupled to ActiveRecord, implementing repositories is a way to go. However, it won’t give you much when all your domain objects extend ActiveRecord::Base. Thus, I use a compromise variant of the repository pattern: put all persistence related methods into a separate module and just extend it. [gist id=”2009155″] Having a separate module comprising all the logic related to persistence benefits us in the following ways:
  • It provides separation of concerns. Post is a business object and PostsRepository is responsible for persistence.
  • It makes mocking the persistent layer straightforward.
  • Providing an in-memory implementation of Repository becomes straightforward too: Post.extend InMemoryPostsRepository

Summary

Tackling domain complexity is hard. The bigger your application grows the harder it gets. Jamming everything together works fine for small apps but breaks apart when you have to deal with large applications. Just applying the principles of the layered architecture can help a lot. However, there is much more in Domain Driven Design that just partitioning your application into layers: entities and values, services and factories, aggregate roots, domain boundaries, anticorruption layers and more. Understanding these concepts and principles and applying them can be really useful for all Rails developers. I’m going to continue writing on these topics.

Frequently Asked Questions (FAQs) about Domain-Driven Design for Rails Developers

What is the importance of Domain-Driven Design (DDD) in Rails development?

Domain-Driven Design (DDD) is a software development approach that focuses on the core domain and domain logic. It is highly valuable in Rails development as it helps in understanding the problem domain, which is crucial for creating effective software solutions. DDD promotes a model-driven design, which means the software model should closely represent the business domain. This approach enhances communication between developers and domain experts, leading to more efficient and accurate software development.

How does DDD differ from traditional Rails development methods?

Traditional Rails development methods often focus on the technical aspects of software development, such as database design and user interface. On the other hand, DDD emphasizes understanding the business domain and creating a software model that accurately represents it. This approach ensures that the software solution is closely aligned with business needs and can effectively solve real-world problems.

Can you explain the concept of Layered Architecture in DDD?

Layered Architecture is a key concept in DDD. It divides the software system into separate layers, each with a specific responsibility. The main layers are the Domain Layer, which contains business logic; the Application Layer, which coordinates application activity; the Interface Layer, which handles user interaction; and the Infrastructure Layer, which provides technical capabilities like persistence and messaging. This separation of concerns makes the system more manageable and maintainable.

What is an Entity in DDD and how is it designed in Ruby on Rails?

In DDD, an Entity is an object that has a distinct identity and continuity over time. It is defined by its identity rather than its attributes. In Ruby on Rails, an Entity can be designed as a Ruby class, with its identity represented by a unique ID. The class can also contain methods that encapsulate business logic related to the Entity.

How does DDD enhance communication between developers and domain experts?

DDD uses a Ubiquitous Language, a common language that is used by both developers and domain experts. This language is based on the domain model and is used in all discussions and documentation. This ensures that everyone involved in the project has a clear understanding of the domain and reduces misunderstandings and miscommunications.

What are Value Objects in DDD and how are they used in Rails?

Value Objects are objects in DDD that do not have an identity and are defined by their attributes. They are used to represent concepts in the domain that are important but do not require an identity. In Rails, Value Objects can be implemented as Ruby classes, and they are typically immutable.

How does DDD handle complex business logic?

DDD handles complex business logic through the use of Aggregates. An Aggregate is a cluster of associated objects that are treated as a unit for the purpose of data changes. This allows complex business rules to be encapsulated within the Aggregate, ensuring data consistency.

What is the role of Repositories in DDD?

Repositories in DDD are used to handle object storage and retrieval. They provide an abstraction over the underlying storage mechanism, allowing the rest of the application to interact with Aggregates without worrying about persistence details.

How does DDD fit into the Rails framework?

DDD can be effectively applied in the Rails framework. Rails’ emphasis on convention over configuration and its powerful ORM capabilities align well with DDD’s focus on domain modeling. By applying DDD principles, Rails developers can create more maintainable and scalable applications.

What are the challenges in implementing DDD in Rails?

Implementing DDD in Rails can be challenging due to the complexity of the domain modeling process and the need for close collaboration between developers and domain experts. Additionally, DDD requires a shift from traditional Rails development methods, which can be difficult for teams accustomed to those methods. However, the benefits of DDD, such as improved understanding of the business domain and more effective software solutions, often outweigh these challenges.

Victor SavkinVictor Savkin
View Author

Victor Savkin is a developer interested in Domain Driven Design, Enterprise Architecture, and Domain Specific Languages. He works on large enterprise applications written in Rails. Being a language nerd he spends a lot of his time playing with Smalltalk, Groovy, Scala, Clojure, Ruby, and Ioke.

Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week