This is an entry written on a weekend, in a rush, just as an attempt to raise awareness about a common practice in the ruby world.
I am a big fan of object oriented design. Also a big fan of SOLID principles, which apply to OOD. Those principles help take OO advantages to the full. I’d say the biggest advantages are encapsulation and composability.
Class methods (aka static methods) can’t take full advantage of neither, as they are basically functions that live on a namespace (better than a plain function, that uses the global namespace). In ruby/rails community, one common pattern for service objects is based on class methods. Though they vary, they are basically like:
One base class may be defined.
Then a service extends that service
And then it used like so:
The “interface” here is defined by call(id, params)
. call
is not super meaningful, as all services will implement it. The difference is in the arguments. Maybe. And yes, the class name help understand what function will do.
Not too bad. But imagine there is a change. Each account can connect to a cloud service for storing images. The application support several flavors of cloud. Each one with a different API. And now, the account Updator should be able to upload a logo to the cloud.
The account service should be changed as so:
Then the service should now be called like this:
The problem now is that the interface changed. Not only that, but the caller needs to inject all the dependencies the service needs. That means the caller needs more knowledge of the service internals. That means the encapsulation got weaker. Also it is no longer “Liskov Substitutable” or “Opened/Closed”.
There are other ways to work around that, like in that specific example, the
Account
class could find the correct cloud service based on the account id. But that is just a contrived example to illustrate one of the downsides of class methods. Other changes could apply. Like the service could eventually become dependent on a cache layer, other APIs, email/notification and so on.
How to solve it? Ditch class methods in favor of SOLID services. Like so:
Always call like this:
Need a cache service? Inject it to the service constructor. Callers don’t need to know.
Does it matter?
All that is just code and code serves the purpose of building a product. It can totally work without it. We don’t need SOLID, OO, or any kind of practice. I agree.
But with the “it works, lets ship it” mindset, we can dismiss just about any code review suggestion, other than bug fixes.
Undescriptive variable name? Typo? It does not matter. It works. The tests are green.
But there is always some kind of bar, that is why we have code reviews, static analyzers and that kinda a stuff. My goal here is to set the bar a little higher by applying those principles.