📜 ⬆️ ⬇️

MVC and Model 2. Component Knowledge and Responsibilities

For a long time I studied the MVC pattern. More than a year and a half has passed since I first met him, and during all this time I could not arrange in my head the areas of responsibility of the three components of the pattern of components.

MVC is a sophisticated but stunningly elegant architectural solution. I have no idea what modern applications would have become without this pattern.

On the Internet, all the information is scattered in some pieces, and now, after a year and a half of dating and endless research, I can finally say: yes, I know this pattern up and down.
')
I decided to collect all the missing information in one place. This was the reason for writing the article.

tl; dr: read the result. The rest, please get comfortable.


According to the “gang of four”, MVC is nothing more than a strategy + linker + observer. The controller and the view are registered with the model as observers. The representation registers the controller as an object of the strategy, realizing its behavior through the composition. The model is engaged in alerting observers when the state changes. A view puts inward other views that usually depend on their models and present a different behavior to the user through other controllers. This is not a HMVC. This is a classic of the genre.

In web development, MVC is the most popular pattern. Today, the concept of MVC-framework is quite ordinary, and we are accustomed to assume that people around know about these frameworks enough to properly divide the code into three or more components. Unfortunately, due to the pervasiveness of MVC frameworks and their attempts to provide their users with ease of learning, developing and maintaining applications, the basics of the underlying pattern have been lost.

Let's start with the fact that MVC, as it is, cannot be implemented in a client-server application due to the lack of a constant runtime during the change of state of models. I speak now about the observer. There is no point in updating the view after changing the model, because we get the view on the client, and the model lies far away on the server. At one time, the Model 2 pattern, based on good old MVC, was developed to solve this problem in Java Web Applications.

The essence of the pattern is to fine-tune MVC under the http protocol and the realities of the Internet. This pattern considered the controller as an object accepting a request from the user. The controller must parse the request, on the basis of it, instantiate a certain model and display a specific view by forwarding the model and other data to the view. The presentation itself is a regular JSP. The model was used to communicate with the database and display the rows of the table in the object (ORM). Looks damn familiar, right? And no you observers and linkers.

If you look closely, the difference in these two patterns is huge. Forwarding data from the controller to the view is already a gross violation of the MVC pattern. In the classical implementation, the representation itself took data from the model directly or through the controller, but more often the controller was used to process user actions on the model using the user interface. That is, the controller, I repeat, is the behavior of the view. Subordinate. Not the master.

Unfortunately, this approach is not suitable for the http protocol. Connections to the server are broken, the application stops working, and in the case of PHP it dies until the next request.

Model 2, like MVC, has all three patterns, only their implementation differs somewhat from the classical object implementation described by the “gang of four”.

The linker remained, but it is realized through the creation of complex template files, be it JSP, PHP, ERB, and so on. But there are almost no methods for working with it. It is unlikely that in current applications we will be able to work with V as with the connection part-whole .

The strategy also did not disappear anywhere, but everyone forgot about it, giving the controllers a new role - request handlers. In most current applications, a controller is a god who decides what, where and how he does it. The controller calls the model, the controller saves the model, the controller writes to the model, the controller generates the view, the controller ... well, you understand. The controller does everything. From the usual behavior of the controller has become a GodObject.

Observer. This is the most difficult. Everyone knows how an observer works. Everyone knows how difficult and sometimes stupid it is to implement a classic observer in the context of a single http request. This pattern was designed to update the data on the screen, and without web sockets, long pooling and other magic is very difficult to implement. In model 2, both the controller and the view are observers, only now the reading of state changes occurs after the http request, and not all the time.

Very messy. However, if this is the way to talk about MVC and Model 2, many things themselves fall into place.

Next, I will try to describe the responsibilities of each component. I am not confined to a specific language, therefore in the text there can be combinations of the type “class / structure”, which are relevant for different languages ​​at the present time. I want the article to be useful not only ruby ​​(railsl) / php developers. Everything written is also relevant for Golang with its structures, python, C #, Java, and, of course, JavaScript.

The article was written in several approaches, please do not judge duplicate information.

Models


What can be said about new models? Everything is perfectly said about them and more than once, for example, in this wonderful article, the very essence of the model is perfectly revealed. A model is a state, it is business logic, it is a single entry point to a table / Mongo document / file. If you want to keep the state between user requests, the model is responsible for this. It is the model that must have fields, if we talk about it in the context of the object. Model cannot be stateless. The model is responsible for the validation and relevance of the data.

If there are entities that can describe any application, without a UI, of course, then these are models.

Views and Controllers


In the classic MVC view is a separate object that deals with the display of information on the screen. If we talk about CLI applications, then this is some kind of stdout.println (). Web applications use model 2 instead of classic MVC, which means that the view is simply a file with markup and embedded template of the template. At first glance, it seems that a simple template does not have much weight in the system. In fact, it is the view that determines what actions are available to the user.

Representation is part of two patterns: strategy and linker. The behavior (behavior) of the view is implemented in the controller. It turns out that the controller is only part of a certain presentation. And it can be replaced by another controller if we need a different behavior for a particular view.

In fact, there is a lot of magic that slips through the fingers of “inattentive travelers”.

Greedy and lazy controllers

Because of the concept of “fat models, thin controllers”, I noticed a tendency to simplify the actions of the controllers to one line - return render ('view'). As a result, most actions and data sampling are carried out directly in the view. This approach is argued that the less data is transferred to the view, the more independent the views and controllers from each other become. Simplifications come to the point of absurdity when models begin to be manipulated directly in template files. I do not understand why everyone is so afraid of writing code in controllers. If I pass on a dozen data to the view, it does not mean that my view should use all of them. Imagine that you have collected some things, stuck them in a box and mailed a specific person. You no longer need these things, and it’s absolutely not important what the recipient will do to them. He can choose a couple of them and throw the rest away. I even came up with the term: “greedy and lazy controllers”.

This article says that the action of the controller in the terminology of the framework is the controller in the terminology of MVC. Despite the fact that the article itself is amazing, I strongly disagree with this statement. Action is a user action on a view. This is the defining part of the behavior. If we rewrite the action, then the behavior will change, but we will be introduced to the code of the finished class, but I do not like to change the code of the classes for nothing. If we replace the controller, it will also lead to a change in the behavior of the presentation, but without changing the existing strategy objects. Take, for example, Angular.js. It clearly shows where the controller starts, and where its methods are. Any controller method somehow interacts with the submission via the directives ng-click, ng-submit, and so on. From which we can conclude: the controller is a full-fledged class, and its methods (actions) are the presentation actions through which the user interacts with our application. If we replace the controller in the ng-controller directive, then the behavior of our view will change depending on the implementation of the methods (actions) of the particular controller.

The second statement that really scares me is that the controllers are the most reusable parts of the application. What? For me, the controller is only a one-time code. Actions (actions) can be reused, I agree. Especially CRUD actions, in which you can simply specify the class / structure of the model with which this action should be associated. Duplication of code in controllers is a normal practice, because they are thin and only call the methods of the models in which all the logic is sewn up. If I have to write the same (or almost the same) action for different controllers two times in a row, I will do it without thinking. If I have to call the same model methods in different controllers, I will do it. The controller can be discarded and rewritten, as, in principle, and representation.

The model is hard to throw away and not necessary. The interface of the model, as a rule, does not change during the development of the application or during the transfer of the model from the application to the application, but is only supplemented. Controllers and views in almost every application are their own. Try to transfer the controller from one framework to another, and you will understand what I am talking about. A model can be transferred from a web application to a mobile or desktop without (or almost without) changes in the code right along with preference tests and courtesans.

The controller should be easy to refactor. Perhaps the removal of identical actions into separate objects is exactly the same reuse of the controller, but it is not so critical as to give it much attention. Certainly not 50 action classes for all occasions.

Controller like behavior

Let's take our favorite mp3 player (I mean, the application in the smartphone). The presentation here is a button to go to the next song, stop, play and playlist. With the stop and loss button everything is clear, I doubt that their behavior will change depending on the change of the controller, but the transition buttons to the composition can also be the forward / rewind buttons of the current composition. So, having the same idea, we can change its behavior simply by replacing the controller.

You can imagine that on the lock screen of the phone while playing music, the rewind buttons change the composition, as it is convenient, and in the application itself - rewind the composition forward / backward. Also, we can change controllers in runtime through events. The finger hold event on the rewind button activates the controller with the current composition rewind, quick touch - activates the controller of the transition to different compositions. Reminds a router, right? In both controllers, we have a duplicate implementation of the play and stop actions, but this is quite acceptable for the pattern strategy. And both controllers use the same view .

In MVC, we can replace not only the controllers of the view, but also the views of one controller. A good example is the display of files in your favorite file manager: tile, list, table, etc. Reminds showcases of goods in online stores, right? For a second, imagine your file manager as a Data Provider, View, and Behavior (controller). And you will see how MVC is applied around us in the real world.

The same principles work on a media center with a single interface, but different collections of data (photos, music, video). Each collection can change both the presentation and the behavior; moreover, we can mix several behavior (controllers) for the same representation. For example, in the case of video, behaviors will be suitable for both images (show action) and music (stop, play, rewind actions).

Widgets


Widgets are V with all the consequences.

More often than desired, widgets are taken as a separate component of the system. They say that an object with a connected view, almost like a controller, means that it is almost as responsible as a controller . Yes and no. Often, they are almost taken quite literally and the poor widget is shoved with all the work with the model, that is, all the work of the organizer except filtering requests. Almost a controller , right?

Widget - god

I thought about the question of responsibility and knowledge of the widget when I was combing GitHub in search of a convenient user-module for Yii. Before that, it seemed to me that the widget is a custom template, possibly with plug-in JavaScript, such as a drop-down menu, a Pjax container, or a GridView from the same Yii. Based on this, we get a class that accepts data as a model / collection of models and displays it / them according to its own internal rules (convention) and settings (configuration). Or does not work at all with the model, but only adds design / behavior to the user interface.

I was shocked when I saw that in widgets people create, write, produce other manipulations with the model directly. On the one hand, when we have the LoginForm widget and the LoginForm model, it would seem logical to use the class for validation and user login into the system through model manipulations. This is just a couple of lines. Maybe somewhere in a different Universe or in a small project it is, but in medium projects or in whole modules it creates multiple code duplication and mixing the responsibility of different components by their nature.

One example of such a design is the bitrix, where all the logic and views are embedded in widgets, which for some unknown reason are called components.

After similar examples on the web for various frameworks, I wondered - “all of a sudden this is the right way”. And then a suspicion crept into his head. If we shoved all the logic into widgets, then the controllers would only deal with displaying a specific view. But this is a gross violation of the pattern. If the widget is an element of V , then in no case should we manipulate the model in it. Even instantiation of models is recommended to be done in controllers, what to say about saving.

On the other hand, the concept of drop in and it's work does not fit in with the mandatory configuration of the widget. Besides, I really like convention over configuration, which, in my opinion, is so far best represented out of the box in Rails. As a result, I made the following conclusions: a model can be instantiated in a widget only if the instance is not transferred to the widget, and the widget is the same for a particular model, for example, LoginForm or NewsLine (in the news feed, it will be correct to create a DataProvider based on the sample) . But the widget in no case should be able to write to the model or save the model: this is the task of the controller.

All of the above seems quite logical, until it comes to processing user input. If after entering the data in LoginForm we need to authorize the user and stay on the page, then how will we do it without processing right in the widget? Obviously, a separate controller is needed for this, but the controller takes the user away from the page, unless an explicit redirect is specified in it. For the most part, the following three scenarios are used on the Internet:

  1. The user is transferred to the login page, where errors are shown (incorrect password) or authorization and display of the personal account takes place.
  2. The user is transferred to the login page, where errors are displayed (wrong password) or authorization and redirection to the previous / main page takes place with (or without) a display of the intermediate view (recall the phpBB forums and the like).
  3. The user is authorized by means of AJAX request, remains on the page. All blocks that depend on authorization (such as a basket) are updated via some kind of Pjax.reload or by simply reloading the page.


In the first two cases, the widget is used only as a facade over the present view and processing. This option is the place to be. In the last example, the widget is implicitly connected to the controller, which can be replaced via the configuration.

In this case, the widget is a minimal logic - what template to show. Show the form if the user is not authorized; show greeting if authorized; no more if else in templates and no manipulations in v .

All three scenarios are easily realizable, they allow to avoid duplication of code, but each of them is more difficult to manipulate the model directly in the widget, the main thing is not to be tempted by the simplicity of the wrong approach .
This is practically no different from the ancient method of using separate php files for each “page” of the application.


Need more widgets!

Due to the large number of ready-made small solutions, frameworks cause a degradation of developers. I noticed for myself how I start using some pre-installed widgets and solve the problems of these widgets so that they somehow fit into the design of my application, while writing a few of my lines of code in two or three languages ​​would be much easier.

Overvalued PJAX is popped everywhere, even where a regular AJAX request would suffice. Almost no one writes their JavaScript components in an attempt to invest in the framework of the framework. I’ve seen many times on GitHub how developers are discussing the javascript needed to reload Pjax or other work related to it, and that “we should shove more settings into the standard framework widget so as not to be distracted by JS, this is inconvenient!”. But creating your own widget like PjaxReloader takes about 10 minutes along with testing. Honestly, I will spend more time on the discussion.

$js = "jQuery(document).on('pjax:success', '#$id', function() { jQuery.pjax.reload('$reloadSelector'); });"; $view->registerJs($js); 


A widget is an object of one duty. On good, all objects in the PLO must be responsible for one duty, but in the real world this is not always the case. The list of comments on the article and the form for adding a comment are different widgets. One may include the other (optional), but not mix with it. After all, do not forget that V is a linker. You can merge small widgets into one large one. Several large one huge. And it does not go beyond the pattern.

Total


The model stores state and business logic. He knows everything about his subject area, she doesn’t give a damn what controller causes it and what representation it represents. The model can be instantiated outside the application as a separate and independent class. The model is easily amenable to TDD, so all the application logic must be stored in models (remember, components, services, repositories, etc. are also models).

A view knows absolutely everything about the model, in addition, how exactly it saves its states (database, file, REST, etc.). The view knows which fields the model stores, which fields store properties formatted for the view. The only thing that the representation should not know about the model is the methods of recording, modifying and saving. A view has the right to instantiate the necessary objects and models, as well as collect Data Provider's, but in order to avoid duplication of code, it is better to take it to factories, helpers, and widgets.

The controller knows nothing about the model or the presentation. The controller knows what method to call the model, but it does not represent what is actually happening with the model at the moment. The controller asks to do certain actions on the model, but it should not be aware of errors (I mean runtime errors and exceptions) that the model generates, and in no case should it intercept and process them. The controller can offer (but this is not necessary) the models to be displayed through a specific view (after all, the controller is part of one or more views) and transfer to the view everything that it generated during its operation, but it does not have to know what need a view to display. If he did not send something, the submission should adequately respond to it and simply do not show a piece of himself or throw an exception if it is a question of the application being developed. The only exception the controller can handle is NotFound 404! The remaining errors of type 403 are generated in filters (request interceptors) or middleware.

The controller should at a minimum be used (or not be used at all) for reading data . Reading can be organized by widgets and similar objects.

Widgets have no right to manipulate the model and write to the database, however, they can read the data from the database / file by means of abstractions, helpers, factories, classes of models and give them to the template. Widgets can instantiate a model within themselves if the model instance has not been passed to it from the top view (linker pattern) or from the controller. Widgets can collect Data Provider's, as this is a read operation.

In conclusion, I just want to quote from my favorite article on Habré. I gave a link at the very beginning of the article, in the section on models.
Some people will just laugh at all these fools who argue about the need for good independent domain models and continue to write confusing code. Let them laugh. After all, they will have to maintain and test their mess.


And may the composition be with you! All good!

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


All Articles