CMS Trap

At the end of 2013, Maxim Chernyak wrote a wonderful article in which he emphasized the crucial importance of supporting application architecture as simple as possible. I am surprised that there has not yet been a translation on the hub, I suggest that you familiarize yourself with the translation of this article. I also ask you to report all possible typos and inaccuracies in the translation.

Preamble

Many years ago, we had a Ruby on Rails application. It all started with objects. Some were prototypes for other objects. Others demanded many related parts, parts of these parts, etc. How much? Perhaps one prototype is known. These prototypes should have had an interface for administration, but a change in the logic of one prototype could lead to a chain reaction in the remaining parts. Any change of objects and their prototypes passed through a connected network of various models. The complexity of the administration interface quickly skyrocketed. It got to the point where prototypes had the opportunity to be serialized and save fragments of their logic. From that moment, each feature became the subject of a very difficult implementation, and ultimately the application slipped into a state when modification and refinement became almost impossible. It felt like CMS imposed itself as an intermediary between a feature and its implementation, like systems with a heap of high-level abstractions, focused solely on business logic.

Do you think this was the worst part of the project? It was just the smallest viable product in a new startup.

Unplanned CMS

The nature of programming encourages us to pamper ourselves with solving puzzles and modeling abstract concepts. It is a passion that makes us lose sight of impending danger. Thanks to our vague subjective assumptions, we are already on the path 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, failures in business, but it seems we will never talk about this error directly. Somewhere near the cooler, a more experienced colleague notices that you complicate things. Somewhere in the IRC you are ridiculed for asking about a complex system of objects for a project that, most likely, will never see the light of day. However, no one can clearly explain what lies at the heart of this process. These comments are all that we know about this problem, and people will ultimately learn about it in their own skin. That is why I would like to shed some light on this phenomenon. For starters, here's a short list of signs on how, in my opinion, to determine whether you are on the way to this trap.

A CMS trap is a state of a web application in which the development of a CMS interferes with the content creation of this CMS.

If you, like me, are creating 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 move on a different plane. Although these companies may also fall into this trap, for them, most likely, it will not be so critical, even if they pursued this direction intentionally. Here I would like to focus specifically on small companies. The problem will become apparent as soon as you open the doors of your project to clients. They begin to use your product and provide you real analytics and feedback. From this moment on, the project will no longer be controlled by your sixth sense, you will have to rely on real data, indicating how to proceed further, what functionality to implement in the future. This is the time when all your architectural designs pass the test. Reality is merciless, it will not save you from the painful truth, when your promising application architecture dies. You could, you would like to refactor, but it's too late, since you need to create new functionality, and its implementation becomes more and more difficult, down the spiral of declining productivity.

I will say to myself later “Thank you”

“Most of our assumptions have survived to their futility.”
- 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 define the entities and interactions between them. This is what we do best. We are committed to solving fundamental problems in the project. Then, carefully setting out our assumptions, commit the changes. We like to think that we sow wisdom and flexibility in our early decisions, and we will thank ourselves later. It would seem that what could go wrong with all these extension points and well-represented entities? The reality is that, most likely, these early decisions will more likely limit our future developments than give us room. The day is coming when we will meet our old friend, a naive "ourselves in the past", looking at us from the editor, smiling proudly. This well-intentioned person spent hours, days, and weeks sending all our efforts into the abyss of supposed architecture, hardly having an idea of ​​real problems, which we will face after the launch. We are currently stuck in all this “useful” code. It is as if you decide to make a salad, but instead of the individual ingredients laid out in front of you, all you would have was another salad made by a stranger, in which you are now forced to dig in the hope of finding the ingredients that you need.

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

Or in the same vein - imagine that you just returned to your computer and found that your application was reorganized by some ignorant stranger in a way that 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 this is dictated by the needs of the business?
If you apply all this to my personal story, 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 designing. As you might have guessed, I thanked myself very much for being so attentive.

C <RUD

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


Talking about architecture is like talking about code itself. This is not exactly what we cannot count on in the future, just in this matter, it is likely that the chances are not in our favor. The code is easy to write and hard to modify or delete. Each line that we throw with a light heart into the matter will ultimately tease us: “Guess what will go wrong if you touch me? (: trollface) ". Architectural decisions, 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 quality measure that we have is the amount of pain we experience when we work on a new feature, but it’s often too late to change something in the architecture by then. Bad architecture can ruin an entire business, even though

Alert

As in most traps, in our case there is no concrete way to find out that you are on the way to it. The best you can count on is when things around you “warn” you of an impending danger. Below I will list some of them, based on my own experience. If you notice these symptoms at an early stage of development, they should at least arouse your suspicion.

Early conservatism

“A state that has no power to change anything is not able to save itself”
- Edmund Burke


Let's say you come across a new feature and implement it to be real yak shave. You understand that 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 reasons of efficiency or you do it because you are stuck in the heaps of legacy architecture. Maybe your early proposed design decisions get in the way of the current real business needs? Perhaps you created too much in a hurry, 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 excessive complexity, this is the standard practice of an experienced developer. The problem is when there is too much conservatism in the early stages of project development. This should definitely be suspicious.

Drupal Syndrome

“Hmm, we have different pricing rules for different products, we need 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 come up with a way to set some logic through the admin panel, which then needs to be stored in the database. If this did not concern the admin interface, perhaps it was done with just a few lines of code. However, now we are talking about creating price models associated with the rules and the complexity of this. To expand the possibilities of prices that previously may have been implemented using one or two lines of code, now you will have to create migrations, forms, validation and everything else. Do you really need an interface for admin pricing rules right now?

Seed's are weak

How would you implement 10 categories for placing your goods in them? A typical answer involves creating a Category model and then writing a script 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, in addition, do not forget about launching it in production. At every deploy. With every update. And when setting up a new machine. And when doing the 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 can currently 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

“You can't just take and implement business logic like that”
- Boromir


This item is somewhat similar to early conservatism, but there is still a difference. Have you ever been frightened by a trivial task? Ask yourself: will this task be as intimidating if implemented as a separate component, outside the project? If the answer is yes, look under your feet because you can fall into the trap. Functional implementation 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 the developing CMS. For example, in fact, you never had to delete the categories you implemented, but because you implemented them as administrator-edited records in the database, you suddenly think: what happens if these categories are deleted from the database? Your architecture has taken the liberty of making you think of a scenario whose work is not really needed and 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 usually can keep in mind. 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 avoid dynamically generating data during runtime.
Let me explain. Being frightened by the complexity of the system, I found that there is a principle that should become 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 architecture-famous reef on the way to creating simple things.

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

The beauty of this principle is that it is applicable at every level of abstraction, regardless of 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 solved with 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 the product, the current user (loyalty program), order history, coupons and many other things. To avoid getting caught in the CMS trap, I advise you to avoid building these species at an early stage in runtime. Write a pricing scheme class. Use the Strategy template . Make your algorithm code level interchangeable. Define pricing rules in your programming language. Thus, the complexity of this logic will be displayed directly in the code, and will not pull an entire layer of abstraction.

Programming languages ​​already have such great tools as conditions and loops. 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, do not hesitate - let the administrator choose one of them, maybe even “fill in the gaps” by adding key features to your algorithm, but gradually develop this functionality, as necessary. 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. All,

Can this be solved with a static page?

Suppose you are listing objects that a client should see on a page. These objects can be products, photos, files, or something else. Then you, possibly, decided that each element should have a name, description, photo, author or brand. You divided your entities into data fields, and decided to create models supported by the database. This is exactly the moment where I would like to suggest stopping and thinking, do you have a good reason why this should not be a static page? A static template view 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, an interface for administration, etc. In fact, in part, you already have an interface for administration, if you are using github. This is not real-time editing, but better than nothing. Users can easily edit views through a form on GitHub.

This becomes even more important if you are asked to list categories based on specific rules. With a dynamic approach, this will immediately force you to create a network of related models, just to render the template. We note how little you know at this moment about the future needs of the business, and naturally, all this will be your assumption. We also note how fast and easy it is to just sit down and hardcode this page. As in the case of the “strategy” pattern, we can always give this page dynamic content in the future when real needs arise. If you are building a dynamic system of models, most likely you will be limited to it.

Can this be solved with a constant?

Returning to the scenario question, this question is pretty simple. You create categories. These categories are predefined. Instead of adding a model, table, and deployment script, why not just make these categories a constant with an array? The code allows you to store static data without a database. Use this and wait for the moment when you really need to edit categories at run time. When that happens, you can always retrieve categories from the database. If you have categories that never change and some that should change in the admin panel, you can leave the previous categories unaffected. You can leave them in a constant, read them from there, and thus avoid seeds. 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's why I always say: if we don’t know, it's hardcode.

Can this be solved with a row in the database?

Let's say that at the moment the application works very well and you have all the necessary models for the database. You need to display in free form 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. In accordance with this principle, you should not think about modeling this text through classes. First, ask yourself - is it possible just to let the administrator set the text for each product. But wait, you would say. If the value is obtained from other sources, why should the administrator fill them out manually? Should he search and enter them every time? It seems that we have missed something. Well, let’s relax and consider enabling snippets. Maybe, just 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 complex problem has a simple, understandable wrong solution.”
- HL Mencken


While the text above is a good basic principle, it cannot be the solution to all the problems that arise with CMS-like solutions. When you are tasked with developing a very flexible CMS, naturally, you try 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 threatening. However, even in these cases, questions will arise whether to do something more or less dynamic, and I urge every developer to lean towards a less dynamic option. You will do the service not only for yourself in the future, but also for the next developer, who will cut a piece of static HTML and add some dynamic content much more productively than he will try to understand the smoking pile of the proposed architecture,
It is also important to note that I do not blame the expansion of architecture. This is good, to a certain extent, this is the line that we draw on the sand from case to case. I urge you to think about where to draw this line every time you implement something.

Returning to my story, it ended with a year of stagnation and subsequent very reluctant updates of the entire application. In the end, the aforementioned “prototypes” were downgraded 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 an interface for administration with all these “moving” parts at runtime. Despite the fact that we spent a year, I am 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 opposite is unlikely), prefer immutability. Try to make extra efforts 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 tips and tricks: never repeat persistent data, use composition, dependency injection, inheritance - all to make sure that you adhere to a single principle of responsibility and maintain unity of control.
And most importantly, do not get lost in too long reasoning - let the story unfold in a natural way.

Original article: hakunin.com/cms-trap

Also popular now: