Go back
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.
- create an API client
- pull data from an API
- transform its result into a hash
- create another client
- pull data from another API
- transform its result into a hash
- compare both hashes
- 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