One Way To Structure Your Code
I worked with different project sizes (in terms of LoC) along my way. And I have seen a lot of projects with either bloated controllers or models … or both. This is widely known as fat x skinny y. But how about skinny x skinny y? The method you’re about to read about how to structure code to avoid this, was not entirely mine. I read about something similar somewhere long ago and loved it right away. But sadly I can’t remember where I read it. Even sadder I have seen it IRL way to rarely. But maybe this is just my opinion, so judge for yourself.
The idea is to move code out of controllers and models that does not belong there measured by very strict and almost inquisitiony standards. In models just keep the really-general-every-instance-of-this-must-have stuff. In controllers only “controlling” code is allowed.
A few examples:
- There’s a method in your Ruby model to create an admin user? That’s a paddlin’.
- In a controller (Phoenix, Rails, …) there is a block of code getting records from the Database via a model and mapping the data to something to pass to the template? That’s a paddlin’.
- There’re callbacks in a model? You better believe that’s a paddlin’.
But where do I put the code?
Services
A service is just a Ruby or Elixir module (or equivalent in your language) with a bunch of methods/functions. I used services whenever I did not need to store state along the way of computing the outcome.
For example I had this Ruby code to get a consecutive list of successful and aborted registrations.
The code does not belong in the users
model because it’s purpose is too specific.
If the code was in the model the call would still look very much the same (User.successful_registration
vs. Services::Registrations.successful
).
But the code and tests are easier to read and maintain (for both the user-model and the registrations-service).
Processes
I use services almost always to compute stuff with no side effects. Processes on the other hand are almost always more complex and involve more steps along the way. Their main purpose is the side effects and not the return value. If you’re an Elixir (or any other functional language) developer than obviously processes must be implemented some other way. But for the sake of the example I stick to Ruby for this one.
This particular process was used (very much) like this in a Rails controller:
The benefit here is that all the code in order to transform the input data and create the message is encapsulated in a class. Again the code and tests are easier to read and maintain (for both the message-model and the message-create-process).
Of course processes can call services or other processes or the other way around.
Conclusion
So far this concept worked out great for me and the teams I worked with. By having to give services and processes names you start to think more modular and concepts get more visible instead of one gooey flow of data through controllers, models, and views.