DDD for Rails Developers. Part 1: Layered Architecture.

This entry is part 1 of 3 in the series DDD for Rails Developers

DDD for Rails Developers

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:

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.

Now, your controller will look like this:

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:

A better way to do it:

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:

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:

Even better way of doing it is moving all the responsibilities of generating tweets from the observer to the TwitterService class:

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:

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.

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.

DDD for Rails Developers

DDD for Rails Developers. Part 2: Entities and Values >>DDD for Rails Developers. Part 3: Aggregates. >>

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://blog.firsthand.ca Nicholas Henry

    Great, write up. I’m not 100% clear on the approach to isolating ActiveRecord from the examples. In ddd_example4.rb does PostRepository inherit from ActiveRecord or does it delegate to an ActiveRecord sub-class?

    • http://victorsavkin.com Vic Savkin

      Hi Nicholas,

      Thank you for your comment. Yup, you are right, there is a mistake in that gist. I’ve updated the gist.

      Cheers,
      Victor

  • Chris

    Interesting article.

    I’m not 100% sold on the decision to extract everything into services. The service approach is basically saying that duck’s shouldnt quack themselves, they should quack via a Quacking serivce. It doesn’t feel right or intuitive. It seems to go against the object orientated principle, where objects are responsible for their own actions.

    This approach definitely solves the monolithic model problem though. I like the DCI approach where the domain objects are injected with needed methods only when they are required. This feels more natural to me and i think makes it easier to think about. Admittedly i have never used DCI up to now. What do you
    think of the DCI approach?

    It’s refreshing to read an article addressing domain complex applications.

    Thanks
    Chris

    • Jose Cortinas

      If the object is responsible for it’s own actions, therefore conforming to the single responsibility principle, it should not be responsible for serializing it’s data into different formats. Model objects hold business related behavior and information. (not the same as persistence)

      As for the twitter service, all of that makes perfect sense. The Post model has no need to know of handling twitter information, making connects, authenticating, etc. The TwitterNotifier’s responsibility is to respond to events that occur in the lifecycle of the model it was registered to observe. That then leaves a last responsibility, which is to actually talk to twitter. We facilitate reuse by having a generic service object, as well as decoupling our need to stub out huge swaths of someone else’s library in every test related to the Post model. If we had kept the after_create hook in as was originally written, every single test that attempted to create a Post model would try to contact twitter, which is honestly a huge pain to have to deal with.

      • Jose Cortinas

        making connections**

        typo :-

      • Chris

        You’re right, I agree. I misread the post and thought it was suggesting create a service for anything you might want to do with a domain model, rather than doing it for external services like it clearly says.

    • http://victorsavkin.com Vic Savkin

      Thanks for your comment, Chris. I am also really interested in trying the DCI approach in production. I tried it for a few small projects and I wrote a short post about the topic a few months ago.

      Not everything goes to services, but some things go for sure (like integration with external services). I agree that it violates Tell Don’t Ask and may not look intuitive. But on the other hand, not doing it most likely will violate the Layered Architecture Principle and The Single Responsibility Principle. There is a nice screencast about conflicting principles by Gary Bernhardt that you may check out.

      Cheers,
      Victor

  • http://leafac.com Leandro Facchinetti

    First, thank you for writing this great post.

    I wonder where would be good places for all these layers to be on the directory structure of a rails app. Inside their own folder inside the app folder, maybe?
    Something like app/observers/twitter_notification.rb?

    How does it get loaded and accessible by the application?

    Thanks, again.

    Bye.

    • http://victorsavkin.com Vic Savkin

      Hi Leandro,

      There are different opinions how the directory structure of an application should look like. Some people prefer to group files by their subdomain, others like to group files by the layer they belong to.

      Creating a separate folder inside app is one way of doing it. With Rails 3 you even won’t have to do anything, it will be automatically added to autoload_path.

      Cheers,
      Victor

      • http://leafac.com Leandro Facchinetti

        Thanks, Vic!

        That solves a big question I always had.

  • http://techoctave.com/c7 TVD

    Hi Vic,

    Great article. Not too long ago, I too was in the Plain Old Ruby Objects (POROs) camp. So I was surprised to see you hadn’t recommend DataMapper or another Ruby ORM that doesn’t follow the ActiveRecord design pattern. Why was that? For simplicity sake?

    You’ve alluded to three (find_by_id, create, save), but I think it’s important to mention all the other querying capabilities, Unit of Work functions and other concerns you’d have to write from scratch to have a reliable Data Access Layer (DAL). ActiveRecord gives you those for free.

    Today, I believe DDD is less about POROs versus ActiveRecord and more about simply focusing on understanding your problem domain. It’s about putting the D in DDD.

    Best,

    TVD

    • http://victorsavkin.com Vic Savkin

      Thanks for your comment. I haven’t mentioned other ORMs available for Rails because:

      1. Most Rails developers still use AR. Thus, if you start apply DDD principles to an existing application you will probably have to deal with AR a lot.
      2. The post was already too long so I’ve decided to cover everything related to the object life cycle (including other persistence alternatives) in another post.

      I completely agree with you that a deep understanding of the domain is more important than factories, services or repositories.

      Cheers,
      Victor

  • Aaron

    I like the idea of an external service layer for the business logic, and that’s the defacto for the java web world.
    Sometime the rails’ rich model strategy does make things hard to maintain, especially for large projects.

  • Andrea

    Great Post.
    Btw using an observer for the after_create call will not avoid you need to stub the twitter service. Any time you will save the Post object the Observer will be fired and that will complain if the twitter object is not there.

    • http://victorsavkin.com Vic Savkin

      Thanks for you comment, Andrea. You don’t need to stub the service. What you should do instead is to disable all observers when you run unit tests. There are many ways to do it, and you can check out one of them here.

      Cheers,
      Victor

  • Gian Carlo

    First of all thanks for great post. I arrived in the Rails land two years ago after more than ten years in the Java world and I enjoyed very much the fresh air of change for all the reason you can imagine.
    But after almost two year on the same Rails project I began to see some of the limits that in my POV are coming from a lack of OO in some of the areas and of DDD style on others.

    Said that I’ve got two question for you:

    1) I value very much OO principles and architecture and I miss a lot a domain (not model) code I used to write I but don’t you think that layering a lot the application (in a DDD fashion) from the beginning could sweep away part of the advantages that Rails takes to web development (at least for small apps)? I mean using a repository pattern will explode the number of method in the persistence layer and this could crystallize a bit the code structure.

    2) Have you ever used an alternative ORM to AR in a production webapp? Which one? Would you recommend using it?

    TIA

  • http://victorsavkin.com Vic Savkin

    Thank you, Gian. I’ll be the last person advocating using DDD principles in every single application. If you are working on a small application (just a spike) that most likely won’t grow, don’t spend time on proper separation of application layers: just jam everything together. Adding repositories and anticorruption layers to small apps is unnecessary overhead. Most small applications are easy to maintain anyway. On the other hand, if from the very beginning you know that your application is going to grow, spend some time on doing everything right.

    I tried DataMapper for a side project. Though, I liked it more than AR, I don’t think it solves the main problem – coupling between the domain and persistence layers. But I still recommend checking it out.

    Cheers,
    Victor

  • Gian Carlo

    Hi Victor,
    thx for your answer. I completely agree with you that it depends on the size of the project. The difficult thing in what you say is understanding the size of a project before the project begins. I know the keyword in this case is “adapt and embrace the change” but in real life is quite difficult to perform structural / architectural refactorings when the project is grown old even with a good amount of tests.

    But after your post I’ll start performing some small to big change in a DDD direction and if you please I’ll let you know how’s going.

    Cheers,
    Gian Carlo

    • http://victorsavkin.com Vic Savkin

      I’d be happy to hear how it’s going.

  • arun

    Great Post!

    I have used nulldb to separate out ActiveRecord and make domain tests real fast.
    It seems quite coming along well.

    Have your tried it? Do you recommend using it?
    https://github.com/nulldb

    • http://victorsavkin.com Vic Savkin

      Thank you, Arun. I haven’t tried NullDB, but I’d like to to. It looks very interesting.

      Cheers,
      Victor

  • Joe Meirow

    Victor,

    This is a most appropriate piece. I am very new to Rails. In fact, I don’t use it full time at work, we use ASP.NET. However, I am looking very hard at Rails and would love to switch us over to it.

    Also, I had the good fortune to attend the four-day immersion class for DDD in January. It was in Denver and taught by an excellent instructor, Paul Rayner. Eric Evans did join us for the last afternoon of the class.

    Part of the course includes material entitled “What I’ve Learned About DDD Since the Book” (or something to that effect).

    Much of the concepts and information can be heard in by listening to a most excellent podcast called “The Distributed Podcast” (http://distributedpodcast.com/)

    The technologies used are .NET technologies, but that doesn’t detract from the *architectural* concepts involved. A good architecture, IMHO, is *technology agnostic*, you then choose the tools that are best suited to help reach the architectural vision. Some of the related architectural principles to DDD are CQRS and Event Sourcing. You can do them separately, but many choose to implement CQRS with DDD (and some Event Sourcing as well).

    At any rate, I am glad you took the time to write this piece as I slog my way through learning Rails not only from a developer’s standpoint, but like you, trying to reconcile it with DDD and CQRS.

    I look forward to reading more. Thanks!

    • http://victorsavkin.com Vic Savkin

      Hi Joe,

      Thanks a lot for your comment. The podcast looks very interesting, and I’m definitively going to listen to it.

      Cheers,
      Victor

  • http://about.me/manuel.vidaurre Manuel Vidaurre

    This is a good read. Coincidentally I’m going to present in June 8 in Magma Rails http://magmarails.com/ a talk about domain/architectural patterns and DDD and how those could be apply to RoR. I’m thinking before to give the talk post some articles about these, I’ll love your opinions and comments. My approach is going to be more centered in the Ubiquitous Language to model de domain the use of the UL in BDD, how to distillate the Core Domain identifying responsibilities/roles and finally building a Context Mapping.

    BTW Joe Meirow mention the Eric Evans talk “What I’ve learned about DDD since the book” the video is available on http://domaindrivendesign.org/library/evans_2009_1

    • http://victorsavkin.com Victor Savkin

      I’d be happy to read your articles and comment on them. I agree with you that the Ubiquitous Language, Core Domain and Bounded Contexts are the core components of DDD and more important than Services or Factories.

      Thank you for the link. I’ve checked out the talk, it’s great.

      Cheers,
      Victor

  • Alex Aitken

    Great post, I am really glad this topic is getting exposure.

    This goes along with Avdi Grimm is writing about in his book Object on Rails (check it out if your interested http://devblog.avdi.org/2012/03/15/now-available-objects-on-rails/)

    • http://victorsavkin.com Victor Savkin

      Thanks for your comment. I’ve read Avdi’s book, and I agree that it’s worth checking out.

      Cheers,
      Victor

      • http://nepalonrails.tumblr.com Millisami

        Good read. Before have read and watched various posts/talks but it just says so to do but none showed the real world case. Your post certainly heading towards that direction. BTW when will its next part will be released?

  • Walther Diechmann

    Hi Vic :)

    super post!

    Don’t know if this is besides the issue – or if it perhaps is not even very smart/ ‘how the mountain speaks’ – but this is the way I get around “decoupling” my classes from AR:

    class AbstractActionBase < ActiveRecord::Base
    self.abstract_class=true

    end

    All my classes inherits from this 'generic' class and should I like to test performance of another kind of persistence/ORM, all I have to do is edit my abstract_action_base.rb

    Using quite a few methods on the abstract_action_base class also catches implementation errors (that I might otherwise 'forget' to test) and on the more sinister side (due to the not quite OO kind of ruby/rails objects) allows me to implement domain-wide methods on the abstract class – like

    def can_print?
    end

    If I throw in a can_print? method on any model, it will overload the abstract method, but if I don't – my views will still work, using the abstract method :)

    Cheers,
    Walther

    • http://victorsavkin.com Victor Savkin

      Hi Walther,

      Thanks for your comment. I agree that defining an abstract super class can ease the pain of dealing with the domain-AR coupling.

      Cheers,
      Victor

  • Erich Kaderka

    JSON Serialization – why don’t you use respond_with @object instead of writing your own module?

    • http://victorsavkin.com Victor Savkin

      Thanks for your comment Erich.

      1. By default `respond_with` calls as_json on the object you passed. It doesn’t decouple your model from its json serialization, it just makes controller code more concise.

      2. But if you install the ActiveModel::Serializers gem, `respond_with` will call the serializer of the passed object, not the object itself. In this case, calling `respond_with` is alright.

      Cheers,
      Victor

  • http://tspike.com Tres Spicher

    Hi Victor,

    This is a fantastic article. I’m developing an open-source app in Rails and I want to spend time thinking about the design. One thing I’m confused about is the directionality of layer dependence.

    You state:

    “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.”

    I’m trying to reconcile this with both the book selling example and the Twitter notification example.

    In the case of the book selling example, the controller (application layer) knows about the book selling service (domain layer) and delegates the task to it.

    In the Twitter example, the TwitterNotification (infrastructure layer) knows about the Post (domain layer).

    These two dependencies point in opposite directions. Can you help me to understand the apparent discrepancy?

    Best,
    Tres

    • http://victorsavkin.com Victor Savkin

      Thanks for your comment Tres.

      The TwitterNotificaiton class lies in the Application layer. Thus, it’s OK for TwitterNotification to depend on TwitterService (infrastructure) and Post (domain).

      TwitterService, on the other hand, is a part of the infrastructure layer. It takes a string as an argument. So it knows nothing about Post or any other domain object.

      Cheers,
      Victor

  • gumnaam

    On the Twitter example what about using a service which creates the Post and then sends a Twitter message. Is there a difference here wrt service and observer arpproaches?

    • http://victorsavkin.com Victor Savkin

      Thanks for your comment. Having a class that is responsible for instantiating Posts and submitting Twitter messages is a valid approach. The difference is that you’ll have all the behavior associated with the post creation in one place (centralized). Which works fine till you have more than one thing depending on the post creation. In addition, the approach won’t work if the post creation happens deep inside your domain.

      Cheers,
      Victor

  • http://ryancheung.me Ryan Cheung

    Very helpful post. It guide me a log about applying DDD approach in Rails. I used to be very interesting in DDD and that time i was a .NET Developer. I would love to keep reading the next serial posts. Thank you!

  • http://www.zepho.com Bala Paranj

    I like all the code examples except the last one. PostRepository should not be mixed in to the Post model. If you look at the Domain Driven Design Sample app, you will see the Repository takes the model as its argument and does all the operations related to persisting.

    • http://victorsavkin.com Victor Savkin

      Thanks for your comment. I realise that the Repository is supposed to be a separate object, but unfortunately (at least in my view), this pattern doesn’t play with with AR. If I create a separate repository object, the client will have two ways of getting the data from the database:

      1. Using the repository
      2. Using the AR class

      Which isn’t dry, and not familiar for most Rails developers.

      Having a module you extend allows you to separate the “repository” methods from the domain methods at least in the source code.

      It’s not ideal, but I think it’s still better than just jamming everything into one class.

      Cheers,
      Victor

  • Sam

    Great post. I’m curious, what behavior, other than storing information about itself, would you attribute to the Post (or Book) domain model(s)? If the only responsibility a domain model has is persistence doesn’t that make it an inherently dumb object? Why delegate to a service when you could do something like @book.sell() or @book.buy() ? Is that a violation of the SRP or rather legitimate behavior of a domain model?

    Cheers,
    Sam

  • http://danieljlong.com Daniel Long

    Thank goodness someone in the Ruby community is writing about DDD, because DDD is another word for great software. I just switched into full time Rails development recently, and so far it has been challenging. Largely, I hate how much logic rails devs put into controllers, Active Record is appalling to me, and I dislike how non-friendly rails is to serious design decisions.

    Thank you for your contributions to this community. I will be using your advice as a guide moving forward. I love creating Domain Models that are decoupled from application and presentation logic, and this will help me on my quest.

    Thanks again!
    Daniel

  • http://danieljlong.com Daniel Long

    My email is actually daniel.long@lifechurch.tv. Sorry!