📜 ⬆️ ⬇️

Architectural Pyramid Applications

Programming is a rather young area of ​​knowledge, however, there already exist basic principles of “good code”, which are considered by most developers as axioms. Everyone has heard of SOLID, KISS, YAGNI and other three or four-letter abbreviations that make your code cleaner. These principles influence the architecture of your application, but in addition to them there are architectural styles, methodologies, frameworks, and much more.

Dealing with all this separately, I was interested in the question - how are they interrelated? Trying to build a hierarchy and inspired by Maslow ’s infamous pyramid, I built my own “application architecture” pyramid.

About that from this left - read under a cat.

About the pyramid

Architectural Pyramid
The pyramid is just a convenient visualization for visual representation of the hierarchy of various principles, styles and methodologies. The pyramid consists of levels, and levels of elements. Each level additionally has its own generalizing name. The pyramid should be read bottom-up, from the most basic and general concepts below, to the more specific and specific above. The order of the elements located on the same level does not matter. Elements of the same level are considered as "equal" or "equal".
')

Pyramid levels


Consider each of the levels of the pyramid, consistently rising from its base to the top. Each of the principles mentioned in the article is described in detail many times in books and articles. Therefore, I will not describe them in detail, giving only a brief quote and link. Instead, I will try to explain why the levels are arranged exactly and how all this can be used.

Unconditional restrictions


The base of the pyramid are the objective (physical) restrictions for the application. It can be anything: team size, budget, deadline, country legislation, and even the current level of technology development. The main distinguishing feature of this kind of restrictions - you can not influence them.

Obviously, such restrictions affect the entire project — its architecture, choice of technologies, and the way the team is managed.

Business requirements


Above the unconditional restrictions are the business requirements: functional and / or technical specifications, customer wishes, explicit and implicit functions that the end user expects to receive.

Business requirements introduce additional (and often basic) restrictions on the application architecture. But they can not be contrary to common sense unconditional restrictions. They only further narrow our freedom of choice and therefore are above the absolute requirements.

Difficulty: KISS, YAGNI


It is unlikely that anyone will argue that the complexity of the system is one of the most important factors affecting all other aspects and, ultimately, the success of the project.
Principles to tackle the complexity of application development are KISS ( Keep it simple, stupid ) and YAGNI ( You are not gonna need it ).

The KISS principle calls for simplification:
most systems work best if they are simple and not complicated
And YAGNI does not design ahead of measure:
You won't need it.
Both are very abstract and suitable for any application, which makes them fundamental.

These principles are very important, but at the same time, no one will pay you if, in pursuit of simplicity, you have ignored half of the client’s requirements. Therefore, the principles relating to simplicity have taken pride of place right above the business requirements of the client.

Connectivity: DRY, SRP, ISP, high cohesion


An old joke about an elephant that needs to be eaten in parts is fully suitable for any complex task. Including, for development of large applications. Two principles are responsible for correctly separating an elephant into small, isolated, and accurately formulated subtasks: DRY ( Don't repeat yourself ) and SRP ( The Single Responsibility Principle ).

DRY principle says:
Each piece of knowledge must have a single, consistent and authoritative representation within the system.
And the principle of SRP:
each object must have one responsibility

It may seem that both principles are one and the same, only in different words, but this is not so. In fact, they complement each other. For example, guided only by DRY, you can create one object that sends mail and calculates tax. If there are no other objects anywhere else in the code, with similar functionality, the condition is satisfied. SRP, on the other hand, will force you to share responsibilities by placing them on different objects.

The ISP ( Interface segregation principle ) principle states that:
Clients should not depend on methods that they do not use.
Suddenly, with this principle I have the biggest problems. It is somewhat similar to YAGNI - “the client may or may not need these interface methods.” On the other hand, he has a lot and from SRP - “one interface for one task”. It could be attributed to the generalization “Complexity” and put on the same level as YAGNI and KISS, but these two principles are more abstract.

SRP and ISP are part of another well-known principle - SOLID, and in the beginning I was worried that in my pyramid these principles were separated from the rest. However, in the end, I decided that not all yogurts are equally useful principles are equal to each other. This does not mean that other SOLID principles can be neglected. Just SRP and ISP, in my opinion, a little more generalized than the rest.

High cohesion is a metric that shows how well the code is grouped by functionality. The implementation of the principles of DRY and SRP leads to a code with a strong connectivity, in which the parts performing the same task are located “close” to each other, and the different ones are isolated. Performing an ISP also leads to strong connectivity, although this is not so obvious. By dividing one interface into smaller parts, you group them by functionality, leaving only the most interconnected methods in each interface.

GRASP: high cohesion, loose / low coupling
Above, strong connectivity is called a “metric”, although it can be fully called a principle. High cohesion, along with Low coupling are parts of the GRASP ( General responsibility assignment software patterns ), which is not covered in this article.

Dependencies: IoC, DIP, loose coupling


After we have divided the system into fairly simple components, we can move on to the relationships between these components — dependencies. The complexity and the number of links between the various components of the application also largely affects its architecture. To manage dependencies between components, there are also some principles.

IoC ( Inversion of control ) assumes the presence of some framework that will transfer control to the components of our program at the right time. In this case, the components may not know anything about each other.

DIP ( Dependency inversion principle ) reads:
The modules of the upper levels should not depend on the modules of the lower levels. Both types of modules must depend on abstractions. Abstractions should not depend on the details. Details must depend on abstractions.
Loose coupling is not a principle, but a metric that shows how independent the system components are. Weakly coupled components are independent of external changes and can easily be reused. IoC and DIP are the means to achieve weak connectivity of components in the system.

Extension: OCP, LSP


Requirements for the system being developed may change and be supplemented over time, so it is necessary to lay the possibility of expanding the functionality from the very beginning. But at the same time we should not get too carried away, because we need to fulfill the basic principle of YAGNI. That is, it is necessary to find some balance between the possibility of future expansion and the current complexity of implementation.

The principles related to the extension of the functional are OCP ( Open / closed principle ) and LSP ( Liskov substitution principle ).

OCP determines that
program entities (classes, modules, functions, etc.) should be open for expansion, but closed for changing
A LSP:
Functions that use the base type should be able to use subtypes of the base type without knowing it.
Competent implementation of these principles will allow in the future to change (expand) the functionality of the application without changing the already written code, but creating a new one.

Methodologies: TDD, DDD, BDD


In addition to the principles, there is also a fairly large set of methodologies, for example, BDD ( Behavior-driven development ) and TDD ( Test-driven development ). In the general case, the methodology, in contrast to the principle, determines some process applied to the development of an application.
Methodologies are very diverse, they solve different problems and often, when developing an application, their combination is used. But whichever one you choose, you will have serious problems if you try to apply them to code that violates previous principles. For example: is it easy to apply TDD and write tests for code that violates DIP and contains references to specific implementations?

Architectural styles and frameworks


To make it clear what this is about, a good, but far from exhaustive list of architectural styles can be found in the Microsoft documentation . Often they are rather orthogonal to each other and can be used together. Each of them usually represents a time-tested and successfully implemented approach to building architecture many times.

The frameworks are mentioned here because they force you to use a particular architectural style, whether you like it or not. Actually, this is what distinguishes the framework from the application library. Many good frameworks are built on the principles of IoC, simplify testing and contain opportunities to expand your own functionality. That is, in fact, they are located "on top" of all previous levels and must support and correspond to them.

Libraries and Tools


At the very top of the pyramid are specific application libraries and tools that you use every day for logging, matrix operations, and displaying beautiful pop-up windows. These libraries solve specific subtasks and should not affect the architecture as a whole. The choice of a library should be based on previous levels of the pyramid, and not vice versa.

Purely geometrically, this level is the smallest in size, but ironically it is he who often causes the most controversy, discussion and articles.

Why is all this necessary?


In my opinion, the pyramid can help when designing a system from scratch or making changes to an existing one. For myself, I present this in the form of a check-list with questions for each level of the pyramid, which must be answered consistently.

If changes are made to the existing system, then it is approximately like this:

1. Are my changes realizable given my budget, time and other objective restrictions?
2. Do they conflict with business requirements?
3. Is it enough just what I'm going to do? Are there any easier ways to do this?
4. How can I divide my task into components (subtasks) so that each of the components performs only one action? Do I not duplicate the existing functionality?
5. How will my components relate to the rest of the program? How to reduce the number of connections? Will I be able to reuse my components or replace one component with another in the future?
6. Will I be able to extend the functionality of my components without changing them? Have I laid the possibility of expanding into those components, the probability of which changes are especially great in the future?
7. Do my changes contradict my chosen methodology?
8. Do my changes relate to the best practices of the framework I use? Do they violate the overall architectural style of my code?
9. Can the libraries I use solve the subtask I have set?

Each item in the list corresponds to a certain level of the pyramid. At the same time, the choice made at each level should not contradict the choice made at the previous levels. This is especially important when designing a system from scratch, when the “uncertainty” in terms of future architecture is much higher and wider choice.

For example, the library you choose should not conflict with the framework used. If this happens, you are changing the library, not the framework. The framework should support (or at least make it possible) the use of the methodology chosen at the previous stage, and so on.

In general, understanding the hierarchy focuses you on more basic principles (lower steps). In addition, the study of the links between the elements of the pyramid allows you to better understand the picture “as a whole”, to generalize and systematize your knowledge. For a deeper understanding of any new principle, it will be useful to try to incorporate it into this system, to understand at what level it should be located.

Conclusion


This article is quite subjective and does not pretend to a comprehensive description of all existing principles and methodologies. Moreover, already at the time of writing the first draft, a couple of principles moved from their familiar places to other levels. Perhaps, over time, the pyramid will be supplemented with new elements or even levels. If it seems to you that something is out of place in it or you want to supplement it, I will be glad for constructive criticism and suggestions in the comments.

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


All Articles