📜 ⬆️ ⬇️

How David Heinemeyer Hönsson organizes controllers

This article is here because I was looking for answers to some questions. For me, understanding the article was not a cursory reading, I had to translate manually. Perhaps someone will be interested in this topic, but the knowledge of English will be worse than mine. Translation to help you.

The original article can be read on the website Jerome's Adventures in Software.

How DHH (David Heinemeyer Hensson) organizes rails controllers


In a recent full stack radio interview, our guru and savior DHH explained how he organizes the structure of rails controllers in the latest version of Basecamp. Here is a transcript of his holy words:
')
I came to tell you that being almost a fundamentalist in the creation of controllers, I remain an adherent of the REST which constantly rescues me.

Every time I’m unhappy with my controllers, it’s because I’ve got few of them. I try to overload them too much.

So, at Basecamp 3, we went beyond the controllers every time there were even a kind of subresource, which made sense. They could act as filters.

Suppose you have this code and it looks like this:

class BananasController < ApplicationController def index render plain: '\_(:-))_/' end end 

Well, if you apply several filters and a drop-down menu, then it will be something else. Sometimes we take this something and make completely new controllers for it.

Here is the Heuristics that I use specifically for work: whenever I have a desire to add a new action in the controller, this action is understandably not part of one of the five action games by default, that is, I simply satisfy this desire and create a new controller ! So call it.

So let's imagine you have an InboxController (mailbox controller) and an index action in it that shows everything in the mailbox; and you could do another action "Oh, I want to add a new pending action, it would be something like a waiting email or something like that."

And you add this action pendings:

  class InboxesController < ApplicationController def index end def pendings end end 

This is a very general pattern, right? And the sample that I use can explain more. Now I will tell you "no, no, no," and I will make a new controller called Inboxes :: PendingsController and it has one index action:

  class InboxesController < ApplicationController def index end end class Inboxes::PendingsController < ApplicationController def index end end 

And this is what I discovered that freedom, giving you the fact that each controller now has its own field of application with its own filter sets that are applied ...

So, we have a big distribution of the controller and especially distribution of the controller in a name space. Let's assume that we have a MessagesController and also we can have a Messages :: DraftsController controller and also Messages :: TrashesController and here we can have all these sub-controllers and sub-resources within the same controller. In my opinion it's cool.

Amen.

Essentially, he said that the controller should only have standard CRUD actions index, show, new, edit, create, update, destroy. Any other actions should lead to the creation of a controller that has only CRUD actions.

What do I think about this


Next will be my own beliefs. Definitely, there are some differences in opinions. Just say, just do not call me a fanatic. Calm down guys.

In any case, I was happy to know this, I have been using the “DHH way” (David Heinemeier Hensson's way) to organize controllers for more than a year and to date #DHHFanboy. Examples he mentions only about filtering although it is obvious that the examples for simple controller logic are redundant. A common way to use filters in REST is to use query parameters, for example (eg GET / inboxes? State = pending).

In general, I would adhere to the fact that the code should be short and simple (as soon as it becomes long and complex or very mixed in actions and relationships - I would do the same thing as David). But I agree with the main idea of ​​separating controllers, and I have several reasons for this.

It helps to create simpler code.


With this technique you can create as many controllers as you like. Use your personal beliefs, though: if there is a default controller (with CRUD actions) regarding brevity and simplicity (such as scaffolded in rails), then you probably don’t need to extract every index / show /, etc. into your own controller.

The separation technique of the controller becomes better when the controller itself becomes more powerful, even despite the only drawback - CRUD actions. What to do in this case? Just write this code in the controller intended for it.

For example, here our most difficult controller looks like in my current company (we use small models and rather large controllers, YMMV) YMMV - your mileage may vary (you can use the same thing in different ways) . This will allow you to buy products in our API application:

  class Api::V1::PurchasesController < Api::V1::ApplicationController rescue_from Stripe::StripeError, with: :log_payment_error def create load_product load_device load_or_create_user create_order create_payment authorize_payment confirm_address render json: @order, status: :created end private def load_product @product = Product.find_by!(uuid: params[:product_id]) end # … end 

There is only one public CRUD method (create action by default) There is no premature abstraction in “fat” models, no service classes, no observers, nothing, no shit. All here. Conveniently placed in the controller. No need to jump on a bunch of files to understand what is happening. You only need to open this one file in your editor. And since controllers are the entry point for any web application code, you must open this file anyway during coding. #ObviousCode

But you probably ask, “Hospadi, how big this class is, have you pushed everyone into it?” Of course, it’s very big, just like ... big shorter, isn't it? No, it's just 144 lines. And this is the most complex controller. Straight worst of the worst. Of course, we could divide the code into small pieces, but for us this is quite normal (YMMV). The rest of our controllers are much simpler, from 6 to 103 lines, on average one controller - 15 lines. (we have 150 controllers at the moment).

Remember the projects in which you worked, where there are more than 200+ lines of code in controllers and this is only a small part of the request - the rest is scattered between infinite objects of service, models and observers (service objects, observers, and models)? Such a d * rm does not occur here thanks to this technique of separating controllers among simple things, also, as a rule, three .

Indeed, duplication will do less harm than improper abstraction, and this is one of the reasons why I believe that DRY and SRC (including the DHH way) are greatly overvalued and full of despicable drums and they suck balls and and and .... Let's return to our topic!

It makes your code more consistent.


Knowing that there can only be a bunch of CRUD actions in the controller doesn't really help. No more guessing and scrolling in the big controllers to find one strange action. No more interesting as / if the normal action is displayed in routs (route).

I do not like to be surprised when I do a simple organization. I love the monotony of the code, I like the uniform code and the strong “convention over configuration” - this is one of the reasons why I prefer rails to other Ruby frameworks. Everything is organized in the same way, so that you spend less time doing routine decisions, and achieve greater success in areas that are really important for business.

In theory, this also means that you can go from one code to another and be 100% productive in a very short amount of time. In the wild, people get into trouble with “terrible Rails applications.” One company may use architectural templates such as observers (not my cup of tea), another may use additional architecture such as Trailblazer (not my cup of tea, but I have some interesting ideas on this matter), the third uses some other tools, the fourth is generally something of their own, etc.

All this is due to the fact that people are unhappy and unhappy with the so-called “lack of structure” in vanilla Rails applications. Thus, they are looking for an additional structure elsewhere. Guise! The solution is right under the nose. Separating controllers and using only CRUD actions. Simple as twenty-two ways and friendly as a junior developer.

Rails could do a better job of promoting the heuristic separation of controllers. Their documentation briefly says "... you should usually use INVENTIVE routing ..." But the very idea of ​​CRUD actions and RESTful has been clearly traced in their documentation for a long time.

If you are RTFM'ed (RTFM - read the fucking manual) then surely, at least once you thought that adding custom actions (except CRUD) is not very “Rails way” Controller separation is a good topic to think about.

It makes you think of REST


Many people love REST because this architectural style is homogeneous and simple. Once you understand (actually understand) RESTful, it will be easier for you to understand something else.

In theory, at least: authentication of each? ;-) Business logic is obviously different between applications, so you need to understand this, but how do you understand what logic is similar everywhere. That is, you create expenses in Stripe (you take someone’s money), you create an SMS in Twilio (that is, you send it), you receive a repository on Github, etc.

You must first strain your brain a little to use REST using a noun instead of actions: not “pay (pay)”, but “create a payment”, not “add funds to your balance”, but “create capital in balance (“create a fund in a balance)” and so on. Maybe a little strange, but I would pay this price any day of the week than go back to SOAP, WSDL, and other nonsense (ex-Java / JEE developers know what I'm talking about).

Besides, I think that having all the business logic of the interface (not necessarily implementation) dictated by REST, which is created for a cleaner and simpler business logic, you can only have objects with multiple controllers, “no more no less” . Until then, you know that you can express anything with REST, and this should be clear. It will free from restrictions.

Here are some examples of RESTful Rails routes that represent split controllers using only CRUD actions.

  resources :purchases, only: :create resources :costs_calculations, only: :create namespace :company do resource :account_details, only: :update resource :website_details, only: :update resource :contact_details, only: :update end namespace :balance do resources :funds, only: :create end resource :bank_account, only: :update 

For better REST design (especially when the child resources are connected) I usually omit the REST in the writing of the action + resource at the beginning of the timestamp (POST / balance / funds) without worrying about the implementation. Then, when the names are assigned - I am calm. We translate everything into rails routes, which are very convenient, as rails has good REST support.

Conclusion


Splitting your controllers when they have very specific scales and too much logic or too many difficulties can have good effects in your code.

This does not mean "forget about abstraction." It will just pass a little lower. Some items and logic need mixed controllers. Sometimes even split controllers with only one public action look great, etc. This applies to the model's methods, and even - may God forgive me - service objects come into play here (fortunately service objects should be rare if you are careful).

The more your application grows, the more time you will need to spend to figure it out, no matter how clean the code is. But separating controllers makes things easier.

Source: https://habr.com/ru/post/279287/


All Articles