Does My Rails App Need a Service Layer?

Posted on by in Web

Sometimes during domain modeling you come across something that isn’t a thing. These operations that don’t quite belong to an object are called services. Services often live in a separate, service layer. The service layer lies between controllers and models, defining an application’s interface, its API.

Designing with services and a service layer is popular in the Java J2EE/Spring community. However, it’s not common in the Rails world. Is this because of the Ruby community’s general backlash against the complexity of Java? Or has the rise of HTTP-based JSON APIs made this architecture obsolete? To answer these questions, let’s take a deeper look at services and the benefits of a service layer.

What is a Service?

In Domain-Driven Design, Evans defines a service as an operation offered as an interface that stands alone in the model. In other words, a service is an action, not a thing. And instead of forcing the operation into an existing object, we should encapsulate it in a separate, stateless service.

It’s not always clear what logic constitutes a service. Taken to the extreme, domain models could be stripped entirely of their domain logic, and turned into nothing but simple data structures (an AnemicDomainModel). A good heuristic is to look for large, domain objects doing too much work and try to break their multiple responsibilities into separate services. At other times it may be your own intuition telling you that some logic just doesn’t feel to be in the right place. To get started, it’s helpful to classify services into different types.

Types of Services

Evans defines three types of services:

  • Application
  • Domain
  • Infrastructure

Application Services

An application service is a service that provides non-infrastructure related operations that wouldn’t come up when discussing the domain model outside the context of software, i.e., in its natural environment.

Exporting account transactions as a CSV file in a banking app is an example of an application service because a file format, CSV, has no meaning in the domain of banking.

Application services improve the cohesiveness of your domain model by preventing “software” implementation details from leaking into it.

Domain Services

A domain service scripts a use-case involving multiple domain objects. Forcing this logic into a domain object is awkward because these use-cases often involve rules outside the responsibility of any single object.

Continuing with the banking app, a funds transfer would be an example of a domain service. Transferring funds doesn’t quite fit in either of the involved account objects, or perhaps a customer object, so it becomes a standalone service.

An alternative implementation, could be an instance method on Account, taking the amount and destination account as parameters. However, transferring money between accounts feels like the responsibility of another object, not a core responsibility of an account. Domain services like this one really help maintain the core responsibilities of your domain objects.

Another example of a domain service is a sitewide search across multiple domain models. Since it searches across multiple domain models it doesn’t conceptually fit in any one particular model. Instead, it should live in a separate search service.

Simple CRUD operations are not domain services. For example, I would consider the following an anti-pattern:

Sure, this does make the UserController independent of the domain model, but this type of flexibility is premature. Instead, the controller should interact directly with the domain model when creating a new User. Domain services should be short scripts, not simple 1-liners that delegate to an almost identical domain model interface.

Infrastructure Services

An infrastructure service encapsulates access to an external system. Infrastructure services are often used by application and domain services.

E-mailing and message queuing are two examples of infrastructure logic that has no place in a domain model.

Infrastructure services make your domain model more reusable by separating out non-domain, “software” concerns.

What is a Service Layer?

In PoEAA, Stafford defines a service layer as an application’s boundary and set of available operations from the perspective of interfacing client layers. In other words, it’s your application’s API.

Let’s take a look at some of the benefits of a service layer to see if they are useful in the Rails world.

Supporting Multiple Clients

In most web apps, the API mainly consists of simple CRUD use-cases. Encapsulating your application’s API, allows it to be reused across multiple client controllers, for example, an HTML and a Thrift client. The resulting controllers are thinner, and more cohesive.

I see this reuse across multiple clients as less and less of a benefit because of the popularity of HTTP-based JSON APIs. Most apps will typically have an HTML client and a few additional clients, for example, an iPhone and a console app, but the same controllers will serve both HTML and JSON. In a way, the controllers have become the services because all clients talk to the app over HTTP.

This reuse was more valuable when you could have non-HTTP based clients, for example, an Adobe Flash app using Hessian.

Encapsulating Application Logic

Stafford separates business logic into domain and application logic. Domain logic being purely the problem domain. Application logic dealing with technical responsibilities, for example, coordinating a workflow or sending an email.

In order to build a more cohesive and reusable domain model, application logic should be handled by infrastructure services, possibly in a service layer. Putting application logic into a domain model that’s used by multiple clients may have unwanted side effects. For example, if you want HTML clients to be emailed after user registration but don’t want your JSON API clients to be notified at all. So instead of emailing users from the domain model…

…let your controller ask a service to do it.

The Rails community definitely has gone back and forth on where to put application logic. Creating a service layer for it is too much though. Instead, put it the appropriate respond_to block in your controllers.

Applying Cross-Cutting Concerns

Java web apps using the Spring Framework, commonly use services to apply cross-cutting concerns, for example, transaction management and security.

Transaction management is a common justification for a service layer, specifically transaction management spanning multiple transactional resources. For example, in the Spring Framework, it’s possible to atomically write to a database, send an email, and enqueue a message.

In a Rails app, ActiveRecord provides automatic, and if necessary manual, transaction management. I can’t say I’ve ever thought much about transaction management across multiple transactional resources, but I see no reason for it not to belong in a controller. I’m not entirely sure why Spring discouraged transaction management at the controller level. Maybe for easier testing.

When Should I Use A Service Layer

The only reason to use a service layer is if you have to support multiple clients using different protocols. HTTP and JSON have become the de facto client communication protocol and format, eliminating the need for a service layer. The additional indirection and flexibility of a service layer should only be considered when you have to add a client that doesn’t speak HTTP. However, it’s still beneficial to use Evans-like services from your controllers in order to encapsulate domain, application, and infrastructure logic and preserve the cohesiveness of your domain model. Always remember to only build for today, not what could happen tomorrow.


  Comments: 20

  1. This can be very useful for a CQRS application.

  2. Something to keep in mind when considering why Java projects often have a relatively large number of (hopefully smaller) classes/interfaces is that most developers use IDE’s like Eclipse and Intellij. With a statically typed language like Java, these tools offer very powerful refactoring features. If you have behavior in a controller that you feel should be in a separate service, it can take a matter of seconds to extract that into class/interface. Changing the interface of a service, modifying method signatures can be done across an entire project with pretty good confidence. I think this lowers the threshold for a developer to pull out service-like behavior. Of course java projects are massive compared to rails projects, so you need the refactoring tools to stay on top of it. While Rails projects are usually much smaller, Ruby doesn’t make it easy for IDE’s do large refactorings safely. Pulling out shared code is a much more deliberate action with a Rails project – even with an IDE like RubyMine.

    When I’ve develop major new functionality in a Java project, I use TDD. I start with interfaces and mock the behavior that I think they are going to have. This allowed me to focus on fleshing out a small piece while only worrying about the interaction with the interfaces I’ve mocked. I may not write the implementations of those interfaces until later – when I’ve fully worked out their responsibilities. This approach definitely lends itself to breaking up behavior as much as possible.

  3. fowler is the principal author of poeaa

  4. Michael Wynholds

    I love it! As Rails matures and is used on bigger and bigger projects, it starts to look more and more like Java. It used to be all about rapid development… but now we’re all talking about performance, and threads, and service layers. Thank god node.js is here to remind us that we really don’t need any of that crap.

  5. Is your last example with sending an email in different circumstances a *good* reason to have a method in the service layer to create users – i.e. as in

    This style separates out the action of creating a user and all the associated actions (like sending an email) from making a specific HTTP request. It means it is now easy to decide whether or not to send emails to users under a different set of circumstances – i.e. supposing you now had to create users through a script or in the console, and needed to make a different decision as to whether they should receive email or not. It becomes easier to test for the same reason. There is also an obvious place to put similar “things that should happen on user creation” without resorting to rails callbacks. Finally it also makes the controller a lot more focused on dealing with the HTTP request/response cycle rather than application logic.


    • A service in a service layer is a great place to put non-domain logic that often gets awkwardly shoved into domain model life cycle callbacks. By default, I tend to put this logic into a controller. Although, if you are looking for purer, more cohesive controllers or your controllers are getting overwhelmed with this kind of logic, then I think your approach of moving it into a service layer is a good idea.

    • @rolandswingler , @Jared Carroll
      I always here people talking about ‘skinny controller , fat model’, but I sometime quite confused about how to define ‘skinny’ and ‘fat’. Based from your discussion here, can I simply say we should not expose details like `user = User.create(params); user.do_something; user.do_another_thing` in the controller ? And a controller should simply deal with the HTTP request/response cycle ?

      • To me “skinny controller” means pushing SQL queries, validations, and non-infrastructure related callbacks down into the model. I’m not in favor of controllers that communicate entirely with services. Controllers should talk to models but that “conversation” should be very simple.

      • In terms of usage of the term “skinny controller”, I think Jared’s definition is right. However, for larger applications I’m starting to come to the conclusion that the rails controllers should only deal with the HTTP request/response cycle, and the introduction of a service layer helps “enforce/suggest” keeping the conversation simple. This definitely isn’t necessarily the right approach for all applications though. You should watch Uncle Bob’s talk on “Architecture: the lost years” given at a recent ruby conf., which, despite its many flaws gives a good overview of this approach

      • Just a reminder. The “skinny controller & fat model” is a nice thing to keep in mind. But! Every little thing that you don’t know where to put does not belong in the model. That’s how fat models can quickly become obese.

  6. I think that many rails apps (especially pre 3.0) have reach a high level of maturity and complexity that the need for refactoring segments of an application has become a necessity. As features are added, over an existing codebase, for many years, introducing a service layer is a valid approach for reducing complexity. Thanks for the post.

  7. Jonas Bruun Nielsen

    Good points. Adpoting Evan’s view on services is healthy as a rails programmer.
    Btw. I think you call .deliver twice in the email service example.

    • Good catch. I was sending the email twice in the example. I’ve updated the code sample to send the email only once.

      • Iemand ervaring met Super Green tea fanbertur van X-trine? Wil 10 tot 15 kg afvallen maar ben een beetje in de overgang dus gaat het moeilijk tot helemaal niet. Kan iig geen kwaad ze te nemen denk ik maar kan je gewoon groene thee erbij blijven drinken? De Gunpowder dan?

  8. Daniel Marchant

    it can boil down to as simple as Domain = data, and service = action upon data. Nouns and verbs.

  9. I’ve read a lot of discussion about this approach but your post was the most concise & clear explanation, thanks very much for taking the time to write this

  10. Antony Moquin

    Hey, on occasion I get a 404 website message when I arrive at your page. I thought you may wish to know, regards

Your feedback