Services - what are they and why we need them?

Model-View-Controller is a design pattern which absolutely dominated web frameworks.
On the first look it provides a great and logical separation between our application components. When we apply some basic principles (like ‘fat models, slim controllers’) to our application, we can live happily very long with this basic fragmentation.

However, when our application grows, our skinny controllers become not so skinny over time. We can’t test in isolation, because we’re highly coupled with the framework. To fix this problem, we can use service objects as a new layer in our design.

Entry point

I bet many readers had some experience with languages like C++ or Java. This languages have a lot in common, yet are completely different. But one thing is similar in them - they have well defined entry point in every application. In C++ it’s a main() function. The example main function in C++ application looks like this:

We can think about actions as separate application within our framework - each one with its private main. As I stated before, nobody sane puts logic in main. And how it applies to our controller, which in addition to it’s responsibilities takes part in computing response for a client?

Introducing service objects

That’s where service objects comes to play. Service objects encapsulates single process of our business. They take all collaborators (database, logging, external adapters like Facebook, user parameters) and performs a given process. Services belongs to our domain - They shouldn’t know they’re within Rails or webapp!

We get a lot of benefits when we introduce services, including:

Ability to test controllers - controller becomes a really thin wrapper which provides collaborators to services - thus we can only check if certain methods within controller are called when certain action occurs,

Ability to test business process in isolation - when we separate process from it’s environment, we can easily stub all collaborators and only check if certain steps are performed within our service.

Lesser coupling between our application and a framework - in an ideal world, with service objects we can achieve an absolutely technology-independent domain world with very small Rails part which only supplies entry points, routing and all 'middleware’. In this case we can even copy our application code without Rails and put it into, for example, desktop application.

They make controllers slim - even in bigger applications actions using service objects usually don’t take more than 10 LoC.

It’s a solid border between domain and the framework - without services our framework works directly on domain objects to produce desired result to clients. When we introduce this new layer we obtain a very solid border between Rails and domain - controllers see only services and should only interact with domain using them.

Example

Let’s see a basic example of refactoring controller without service to one which uses it. Imagine we’re working on app where users can order trips to interesting places. Every user can book a trip, but of course number of tickets is limited and some travel agencies have it’s special conditions.

Although we packed our logic into models (like agency, trip), we still have a lot of corner cases - and our have explicit knowledge about them. This action is big - we can split it to separate methods, but still we share too much domain knowledge with this controller. We can fix it by introducing a new service:

It’s much more concise. Also, all the knowledge about process are gone from it - now it’s only aware which situations can occur, but not when it may occur.

A word about testing

You can easily test your service using a simple unit testing, mocking your PaymentAdapter and Logger. Also, when testing controller you can stub trip_reservation_service method to easily test it. That’s a huge improvement - in a previous version you would’ve been used a tool like Capybara or Selenium - both are very slow and makes tests very implicit - it’s a 1:1 user experience after all!

Conclusion

Services in Rails can greatly improve our overall design as our application grow. We used this pattern combined with service-based architecture and repository objects in Chillout.io to improve maintainability even more. Our payment controllers heavy uses services to handle each situation - like payment renewal, initial payments etc. Results are excellent and we can be (and we are!) proud of Chillout’s codebase. Also, we use Dependor and AOP to simplify and decouple our services even more. But that’s a topic for another post.

What are your patterns to increase maintainability of your Rails applications? Do you stick with your framework, or try to escape from it? I wait for your comments!