📜 ⬆️ ⬇️

CMS trap

At the end of 2013, Maxim Chernyak wrote a wonderful article in which he emphasized the crucial importance of supporting the application architecture as simple as possible. Surprised that the Habré still has not been translated, I suggest to get acquainted with the translation of this article. Also please report all possible typos and inaccuracies of the translation.

Preamble

Many years ago we had a Ruby on Rails application. It all started with objects. Some acted as prototypes for other objects. Others required many related parts, parts of these parts, etc. How much? Perhaps one of the prototypes is known. These prototypes were supposed to have an interface for administration, but a change in the logic of one prototype could lead to a chain reaction in the remaining parts. Any changes to objects and their prototypes ran through a connected network of various models. The complexity of the administration interface quickly skyrocketed. It came to the moment when prototypes had the opportunity to be serialized and save fragments of their logic. From this point on, each feature became the subject of a very difficult implementation, and ultimately the application went down to a state when modification and refinement became almost impossible. There was a feeling that CMS was imposing itself as an intermediary between features and its implementation, like systems with a pile of high-level abstractions focused exclusively on business logic.

Do you think this was the worst part of the project? It was still the only minimally viable product in a new startup.

Unplanned CMS

The nature of programming calls us to pamper yourself with solving puzzles and modeling abstract concepts. It is a passion that makes us lose sight of the looming danger. Thanks to our vague subjective assumptions, we are already on our way to the trap of creating overly complex systems. Trapped CMS. We suffer from the consequences of falling into this trap, such as “burnout”, loss of enthusiasm, deadlines, business failures, but it seems that we will never talk directly about this error. Somewhere around the cooler, a more experienced colleague notices that you complicate things. Somewhere in IRC you are ridiculed for asking about a complex system of objects for a project that most likely will not see the light. However, no one can clearly explain what underlies this process. These comments are all that we know about this problem, and people will eventually find out about it the hard way. That is why I would like to shed some light on this phenomenon. To begin with, here is a small list of signs of how, in my opinion, to determine whether you are on your way to this trap.
')
The CMS trap is the state of the web application in which CMS development interferes with content creation of this CMS.

If you, like me, create a startup, you should know that this trap is especially dangerous at an early stage of development. Only a small percentage of companies play a long game, and over time, their problems are already moving to another plane. Although these companies may also fall into this trap, for them, most likely, it will not be so critical anymore, even if they pursued this direction intentionally. Here I would like to focus on small companies. The problem will become apparent as soon as you open the doors of your project to customers. They start using your product and provide you with real analytics and feedback. From this point on, the project will no longer be controlled by your sixth sense, you will have to rely on real data, showing how to proceed, what functionality to implement in the future. This is the time when all your architectural decisions pass the test. The reality is merciless, it does not save you from the painful truth, when the sunset of your promising application architecture occurs. You could, you would like to refactor, but it is too late, since you need to create new functionality, and its implementation becomes more and more difficult, down the spiral of falling productivity.

I will say to myself later, "Thank you."

"Most of our assumptions have survived to its worthlessness"
- Marshall McLuhan


Simply put, we love system design. As soon as we form some understanding of the problem, we rush to our /(?:whiteboards|moleskines|mindmaps|editors)/ and begin to identify the entities and interactions between them. This is what we do best. We undertake the solution of fundamental problems in the project. Then, carefully setting out our assumptions, commit the change. We like to think that we sow wisdom and flexibility in our early decisions, and we will thank ourselves later. It would seem, what could go wrong with all these points of expansion and well-presented entities? The reality is that, most likely, these early decisions will most likely limit our future developments, rather than give us scope. The day is coming when we will meet our old friend, the naive “past self”, looking at us from the editor, smiling proudly. This well-meaning person spent hours, days and weeks sending all our efforts into the abyss of the proposed architecture, hardly having an idea of ​​the real problems that we will encounter after launch. We are currently stuck in all this "useful" code. It’s as if you decide to make a salad, but instead of the individual ingredients laid out in front of you, all you would have is another salad prepared by a stranger, in which you now have to dig, hoping to find the ingredients that you need.

In programming, you are in the past - none other than a stranger with a bunch of bugs.

Or in the same vein - imagine that you have just returned to the computer and found that your application was reorganized by some ignorant stranger in such a way that it has little to do with the real purpose of the project. This is not very different from when we work with parts of the system modeled in the past. Do you want to deal with all this garbage, or just want to move forward, in accordance with how it is dictated by the needs of the business?
If I apply all this to my personal history, then in the end I realized that with each new feature I spent more and more time figuring out how to fit it into the existing framework structure, instead of devoting time to design. As you may have guessed, I was very grateful that I was so attentive.

C <RUD

“It’s harder to read code than to write it”
- Joel Spolsky


Talking about architecture is like talking about code itself. This is not exactly what we cannot expect in the future, just in this matter, it is likely that the odds are not in our favor. Code is easy to write and difficult to change or delete. Every line that we throw into the case with a light heart will ultimately tease us: “Guess what goes wrong if you touch me? (: trollface) ". Architectural solutions, as well as code, are easy to create and very difficult to modify. And if in the code this problem is mitigated by testing, then in the architecture nothing saves us. The only measure of quality that we have is the amount of pain we experience when working on a new feature, but changing something in architecture by that time is often too late. Bad architecture can ruin the whole business, despite the fact that all the code will be 100% covered with tests.

Alert

As in most traps, in our case there is no concrete way to know that you are on your way to it. The best thing you can count on when things around you "warn" you about the impending danger. Below I will list some of them, basing on my own experience. If you notice these signs early in your development, they should at least make you suspicious.

Early conservatism

“A state that cannot change anything cannot save itself”
- Edmund Burke


Suppose you are faced with a new feature and are implementing it to be real yak shave . You understand that the implementation requires a lot of refactoring, and you are looking for ways to avoid it. There is a fine line between what you do it for - you do it for efficiency reasons or you do it because you are stuck in the heaps of legacy architecture. Maybe your early proposed design decisions are getting in the way of the current real business needs? Perhaps you have created in a hurry too much, and now it is only a matter of time - when will you fall into the trap and your project will remain paralyzed? Do not get me wrong, often, conservatism is a healthy defense against unnecessary complexity, this is the standard practice of an experienced developer. The problem is when conservatism is too much precisely in the early stages of project development. This should be suspicious.

Drupal syndrome

“Hmm, we have different pricing rules for different products, we have to find a solution for the administrator so that he can set these rules in the admin panel. Maybe we should store the code in the database and then run it? ”


This is a classic sign that you are on your way to a trap. You are trying to think of a way to set some logic through the admin panel, which should then be stored in the database. If this did not apply to the admin interface, perhaps this was done with just a few lines of code. However, we are now talking about creating price models associated with the rules and about the complexity of this. To expand the possibilities of prices that previously may have been implemented using one or two lines of code, you now have to create migrations, forms, validation, and everything else. Do you really need an interface for pricing rules in the admin right now?

Seeds are weak

How would you implement 10 categories for placing your products in them? A typical answer involves creating a Category model, and then writing a script (seed) that will expand the 10 prescribed categories associated with the products. Then you will need to make sure that each developer has deployed this script. Of course, besides, do not forget to start it in production. With every delay. With every update. And when setting up a new car. And when performing tests. And, of course, if you made changes to this very scenario.
If, at an early stage, your application relies on many such scenarios, you are already on a slippery slope. Things that at the moment can be described by a constant should not be modeled as database entities, but I will come back to this later.

All roads lead to Mordor

“It’s not so easy to take and implement business logic”
- Boromir


This point is somewhat similar to early conservatism, but there is still a difference. Have you ever been afraid of a trivial task? Ask yourself: will this task be just as frightening if it is implemented as a separate component, outside the project? If the answer is yes, look under your feet, because you may fall into the trap. The implementation of the functional in well-designed systems should not be more difficult than its implementation in isolation - as a separate component.

"Phantom pains"

Sometimes the CMS trap can be recognized by the presence of “phantom pains” that arise from the hidden consequences of a developing CMS. For example, in fact, you never needed to delete the categories you implemented, but because you implemented them as database records edited by the administrator, you suddenly think: what will happen if these categories are deleted from the database? Your architecture has taken the liberty to make you think about scenarios, whose work is not really needed and is not real. Ultimately, this is called "phantom pain."

Prevention

All of the above symptoms have something in common. They are all the result of early assumptions that lead to system complexity. In this case, it is useful to answer the question “What is a complex system?” And “How to write programs without making erroneous early assumptions?”.
In the context of this article, let's say that a complex system is a network of nodes, which consists of more nodes than you can usually keep in your head. Obviously, to get a system with a low level of complexity, you need to reduce the number of nodes and connections. As for the last question, this is exactly what led me to solve it. To write programs without such problems, you must first of all avoid dynamic data generation during runtime.
Let me explain. Being frightened by the complexity of the system, I discovered that there is a principle, which should be fundamental, in all decisions made. Let's call this principle keep it static, stupid (make it static) , which seems appropriate, because in reality it is nothing more than an architecturally known reef, on the way to creating simple things.

Creating unchanged things is the architectural equivalent of avoiding premature optimization.

The beauty of this principle is that it is applicable at every level of abstraction, no matter what we are talking about - views, code, or databases. The idea itself is simple - if in doubt, make it static. This is easier to understand with a concrete example, at various levels of a typical Rails application.

Can this be resolved using a class?

Earlier in this article I mentioned pricing rules. This is a common task, where each product can be calculated according to different pricing rules. The price may depend on the quantity of goods, the current user (loyalty program), order history, coupons and many other things. To avoid falling into the CMS trap, I advise you to avoid building these types at an early stage in runtime. Write a pricing scheme class. Use the Strategy pattern. Make your algorithm replaceable at the code level. Define pricing rules in your programming language. Thus, the complexity of this logic will be displayed directly in the code, and will not pull a whole layer of abstraction.

Programming languages ​​already have such wonderful tools as conditions and cycles. Why redefine them at a higher level of abstraction? These tools are more than enough to build complex pricing logic directly by writing code. If you have several pricing algorithms recorded as plug-in objects, feel free to let the administrator choose one of them, maybe even “fill in the blanks” by adding key features to your algorithm, but develop this functionality gradually, as needed. Create your own administration interface over time, add more and more flexibility at runtime to your strategy objects. Remember, you can always make static / hard-coded things dynamic, but on the contrary - not always. Everything that is regulated at the time of execution adds complexity to each task, and increases the chances of errors that you cannot foresee, even in seemingly unrelated parts of your application.

Can this be resolved with a static page?

Suppose you list on a certain page objects that a client should see. These objects can be products, photos, files, or something else. Then you, perhaps, decided that each element should have a name, description, photo, author or brand. You have divided your entities into fields with data, and decided to create models supported by the database. This is exactly the moment where I would like to offer to stop and think if you have enough reason why this should not be a static page? A static template representation means that in order to change your objects, you must change the template and update it, yes, but it also means that you do not have to write controllers, migrations, forms, administration interface, etc. In fact, in part, you already have an administration interface if you use GitHub. This is not real-time editing, but better than nothing. Users can easily edit submissions via a form on GitHub.

This becomes an even more important issue if you are asked to list categories based on certain rules. With a dynamic approach, it immediately forces you to create a network of related models, just to render a template. Notice how little you know at this moment about the future needs of the business, and naturally, all this will be your assumption. Note also how quick and easy it is to sit down and hard-code this page. Just like in the case of the strategy pattern, we can always give this page dynamic content in the future when real needs arise. If you build a dynamic system of models, most likely you will limit it to it.

Can this be solved with a constant?

Returning to the issue of scripts, this question is quite simple. You create categories. These categories are predefined. Instead of adding a model, table, and scenario for deployment, why not just make these categories a constant with an array? The code allows you to store static data without a database. Use it, and wait for the moment when you really need to edit categories at runtime. When this happens, you can always retrieve categories from the database. If you have categories that never change, and some that should change in the admin area, you can leave the previous categories alone. You can leave them in a constant, read them from there, and thus avoid seed'ov. This is actually my little secret. I do not like the data of such scenarios. Now our application works out of the box on the machine of any new developer. If you download our code to your dev-computer, the application will simply start and work. That is why I always say: if we don’t know, then hardcode.

Can this be solved using a string in the database?

Suppose at the moment the application works very well and you have all the necessary models for the database. You need to display a free form of text that may differ from one entity to another (for example, different text for each product), and may also contain different interpolations from different sources. According to this principle, you should not think about modeling this text through classes. First, ask yourself - is it possible to simply let the administrator set the text for each product. But wait, you would say. If the value comes from other sources, why should the administrator fill them in manually? Should he search and enter them every time? It seems we have missed something. Well, relax and consider the inclusion of the ability to add snippets. It may be worthwhile to simply add a free-form text field that provides some pre-filled text for the administrator. When you think that you need to structure the data to save something very “flexible”, it is better to use plain text with canned snippets.

Summarizing

“Every difficult problem has a simple, well-understood, wrong solution.”
- HL Mencken


While the text above is a good basic principle, it cannot be the solution to all problems that arise with CMS-like solutions. When you have been instructed to develop a very flexible CMS, naturally you are trying to do something similar. When you are asked to develop an application with something like Drupal, you are in a completely different area where the CMS trap can be constantly threatened. However, even in these cases, questions will arise as to whether something should be done more or less dynamic, and I encourage each developer to lean toward a less dynamic option. You will do the service not only for yourself in the future, but also for the next developer who cuts a piece of static HTML much more productively and adds some dynamic content than trying to understand the steaming pile of the proposed architecture, with a lot of moving parts.
It is also important to note that I do not condemn the expansion of architecture. This is good, to a certain extent, this is the line that we draw on the sand from time to time. I urge you to think about where to draw this line each time you implement something.

Returning to my story, it ended with a one-year stagnation and subsequent very reluctant updates of the entire application. In the end, the aforementioned “prototypes” were reduced to immutable classes and over time began to be declarative in nature, thanks to the naturally evolving internal DSL. Looking at these files today, it’s hard to imagine how I would implement the administration interface with all these “moving” parts at runtime. Despite the fact that we spent a year, I'm still glad that we gritted our teeth and refactored. It was painful, but now this mistake is far behind.

If you do not know exactly what you are doing (and the reverse is unlikely), prefer immutability. Try an extra effort to determine which parts of your business logic can be left in the code. If in doubt, leave it in the code.When doing this, make sure that you follow the advice and recommendations: never repeat permanent data, use composition, add dependencies, inheritance — all to make sure that you follow the same principle of responsibility and maintain unity of control.
And most importantly, do not get confused in too long arguments - let the story unfold naturally.

Original article: hakunin.com/cms-trap

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


All Articles