Reno, NV    +1 (800)0 621-0871

Go back

Development | Ruby on Rails

Refactoring with LightService

  |  July 25, 2013

In a previous post, Ramon discussed that he prefers to break up large classes into smaller classes for quick tests. We applied this approach in one of our projects and this blog post tells the story of what happened next.

While extracting to smaller classes and sticking to the Single Responsibility Principle is a net gain, we found a few problems with this approach:

Problem #1: Loading a program in your head

Having small classes implies that there are more objects that talk to each other. The result is a wide object graph:


Problem #2: Pairs of objects may have different ways of communicating

A FooExtractor.extract(..) needs to call a SomethingTransformer.transform(..). When debugging we had to remember these method names, plus the object graph above.

While debugging, we noticed a structure emerging:

Observation #1: We’re just transforming data

Our project involves hierarchical data synchronisation using two web service APIs.

  1. create an API client
  2. pull data from an API
  3. transform its result into a hash
  4. create another client
  5. pull data from another API
  6. transform its result into a hash
  7. compare both hashes
  8. decide whether to create or update new records

…and so on.

Observation #2: Our objects, on their own, only keep state to pass it to the next collaborating object

When we reviewed our work, we noticed that it resembled a chain (an object does its work and passes it to the next object, forming a pipeline).

Observation #3: We’re running out of nouns

During the course of extending our work, we started to find it challenging to name our classes. Our classes’ names started to feel unnatural.

Enter LightService

During Railsconf, we attended a lecture about how to structure a Rails application using service objects. At this time, the speaker introduced his gem called LightService that provides an easy way to structure objects as a pipeline.

LightService provides an organizing structure (using Organizers) to arrange classes as a pipeline of Actions. It uses a Context to pass data along the pipeline.

We went back to work and began to use LightService in our data synchronisation task. This approach gave us a few benefits:

Benefit #1: Clear pipeline structure

By looking at an Organizer, it’s clear to us which sequence of actions participate in a task.


Benefit #2: Actions as a way out of the kingdom of nouns

We can use verbs in our Action classes and not lose clarity of what it does. LightService gives each Action an execute method that takes a Context as its sole parameter.

Benefit #3: Organizers as a way of suppressing detail

At this point, one might ask how we setup our tests. We wrote specs pre-Action, verifying the Action did its work, and then verifying the context that was properly set post-Action.

Testing Organizers involved testing its Actions were executed in the correct sequence.

Once a pipeline has been built, we ran water through the pipe to look for holes by kickstarting a Resque job and requesting data from the APIs.

See Also