I believe the point of adopting Rails is to maximize the development productivity, namely to build an application very quick with small amount of code. But if you try to do everything within the original MVC, you will easily end up messing up with fat controllers or fat models and lose the maintainability and readability of your codebase. That being said, adopting a whole rich layered architecture with Rails defeats the purpose of using Rails. (I saw someone use DDD on Rails, but it was not Rails anymore…) This is what I think is a pragmatic pattern enough to be both agile and maintainable.
What is maintainable code?
This style went well in my previous project. Our code became beautifully loose-coupled. “Went well” here means, you can keep model single-responsible and re-usable and controller well-readable even to non-techies. There are lots of items here, but keep YAGNI in your mind and choose only what you need.
This is where domain logic resides. For most cases, logics that interact with non-ruby world. (Persistence(ActiveRecord), File system, Outside service and so on.) Cannot have more than one domain, for example, cannot have DB and File System functionality together. Add logics which only relates to the model domain. Use DI if necessary instead of directly having other models inside. Otherwise system complexity goes up drastically. When you have to handle other models, use Concerns to enclose those model related logic All methods need to be reusable, free from any procedures and of course single responsible
Responsible for input processing, sessions, delegating jobs to model or service and passing the results to view for rendering. They say controllers’ main responsibility is to control the flow. What exactly does that mean? I think it means that you should be able to tell what-to-dos (not how-to-dos) just by looking at the controller. Just write what-to-dos here so that you can tell what happens in each action. How-to-do should reside in lower layers.
Responsible for serializing Ruby objects into HTML, JSON or something else. When your application is beautifully Restful, you can use a model serializer instead of Jbuilder/Rabl.
Helper methods consumed by views reside here as Rails originally intended. Think if decorator(below) is more suitable when you add a method here.
This is where business logic(BL) resides. If BL is complicated enough such case as when it’s related to multiple models or if BL is procedural we should use it. It would be ideal that services expose similar API as ActiveModel::Model for controllers. There is a naming issue though. Some people try to put third party web service domain logic here which should go to models. But the naming of “Service” is common already for this layer…
The logic with related another model resides here or if some logic can be grouped in the model, we can extract it as concern (such as authentication in User model.) Also logics with associated model can be modularized as concern
Logics that belong to a certain model but are consumed by views will come here. Aka presentors. Use Active Decorator
HTML Widgets. Use Cells
When your application is RESTful. Callbacks (Optional) Extract callback methods from model. We can use the same callback file for multiple models, too.
Extract validators that inherits ActiveModel::Validator from models. (Caveats: Validators are singletons. Do not keep states.) Socials Aka Policies (Optional) Not sure who called it policy first, but social modules are to get access token from third party and also to get necessary social graph information. This could be a part of model by nature, but it makes sense to separate this to avoid too many number of models.
-But more importantly-
You should not start with all of the above. You don’t need to separate validations at first. But more importantly, you should always keep things loose-coupled as much as possible so that you can easily separete things later into reasonable modules.