Service Oriented Architecture

An augmented MVC with a focus on services

To write modular code, we decided to implement a service oriented architecture, which states that all the business logic (also known as the domain logic) of an application should be contained into isolated components called services. Around this logic tier, you will find a presentation tier defining the user interface and a data tier modelling data to be stored.

Services have the following characteristics:

  • they are as simple as possible, and only implement the business logic

  • they have very limited interactions between each others

  • they are organized and grouped to follow the business logic of our application.

Applying it to Rails apps

As you know, Ruby on Rails is our favourite backend framework. As you should also know, Rails is a MVC framework in which by default, the business logic is into models and there is no services.

To implement a service oriented architecture into Rails apps, we decided to add layers in between the original ones (model, view, controller): the services of course, but also some params and some commands.

Here are all the layers you should find into our Rails apps, grouped by broader layers called tiers:

  • presentation tier :

    • controllers: to handle an HTTP request

    • channels: to handle websockets

    • params: to parse & validate the HTTP parameters

    • commands: to call services and format their results

    • serializers: to format payload of HTTP responses (same as HTML templates)

  • logic tier:

    • services: to perform the business logic

  • data tier:

    • models: to represent database information

The relation between those elements is unidirectional: technical elements can call business logic, but not the opposite.

The relation between those layers is unidirectional: layers of the presentation tier can call the logic and data tier, and the logic tier can call the data tier. We should absolutely avoid going backwards in the flow (i.e. having a model class calling a service method, or a service calling a command).

Here is a simplified flow through the layers triggered by a single HTTP request:

Source: https://study.dnsimple.com/resources/maintaining-rails-hanami/

Around those core layers, you will find some other layers that are part of the infrastructure:

  • jobs: for asynchronous processing

  • libraries (lib folder): for non-business related utilities

  • mailers: to send transactional emails

Benefits

Doing so allows our applications to be very simple to extend and maintain.

Putting the business logic into services allows developers to directly find where the complex parts are implemented, and where to change them. This effectively reduces the time to code, and reduces the number of bugs.

Deciding to split the business logic from technical elements allows to have a reflection only in terms of business, and not to think about all the side effects, such as data persistence, allowing to produce more complex and interesting features. Services are plain old ruby objects, which are easy to understand.

Finally, this also allows us to test much more on isolation, by calling directly the services, and this can make composition easier.

References