When we started writing a small but real growing project, we noticed that the app shouldn’t only work well but also should be well organized.
Don’t believe that thoughtful architecture is needed only for large projects.
Proper architecture saves a lot of effort, time and money. And often it determines whether your project will survive or not.
To our surprise, it is not so easy to find the answer to the simple question: “How to build a good/beautiful app architecture?”. Despite the fact that there are many books and articles devoted to design patterns and design principles, for example, SOLID principles and how to properly code, equally, there was a feeling that something important was missing.
Criteria for good architecture.
Generally speaking, there is no universal application architecture definition. However, when it comes to practice, it’s clear to most developers which code is good and which one is bad. So what is application architecture which works good? Good application architecture is primarily a profitable architecture that makes the process of developing and maintaining an app easier and more efficient. The app with good architecture is easier to expand and modify, as well as test, debug, and understand. That is, in fact, we can formulate a list of quite reasonable and universal criteria:
System efficiency. First of all, an app, of course, must solve the tasks and perform its functions well, and in various conditions. These include characteristics such as reliability, security, performance, ability to cope with increased load (scalability), etc.
System flexibility. Any application has to be changed over time - requirements change, new ones are added. The faster and more convenient you can make changes to existing functionality, the fewer problems and errors this will cause - the more flexible and competitive the system would be. Therefore, in the application development process, try to evaluate what is obtained in case if you later might need to change it. Ask yourself: “What happens if the current architectural solution turns out to be wrong?”, “How much code will undergo changes in this case?”. Changing one fragment of the system should not affect its other fragments.
System extensibility. The ability to add new entities and functions to the system without violating its basic structure. At the initial stage, it makes sense to lay in the system only the basic and most necessary functionality, but at the same time, the architecture should allow easy addition of further functionality as needed. And so that making the most likely changes requires the least effort.
The requirement that the system architecture be flexible and extensible is so important that it is even formulated as a separate principle - the “Open-Closed Principle” (the second of the five principles of SOLID): Software entities (classes, modules, functions, etc.) must be open for extension, but closed for modification.
Testability. A code that is easier to test will contain fewer bugs and work more reliably. But tests not only improve code quality. Many developers come to the conclusion that the requirement of “good testability” is also a guiding force that automatically leads to a good design and at the same time one of the most important criteria for evaluating its quality.
Reusable. It is desirable to design the system so that its fragments can be reused in other systems.
Well-structured, readable, and understandable code. Accompanying. As a rule, a lot of people work on an app - some leave, new ones come. After writing, to accompany the app, as a rule, is also necessary for people who have not participated in its development. Therefore, good architecture should make it possible for new people to relatively easily and quickly understand the system. The project should be well structured, not contain duplication, have a well-designed code and preferably documentation. And if possible, it is better to use standard, generally accepted solutions familiar to programmers in the system. The more exotic the system, the more difficult it is for others to understand.
And for completeness, the criteria for bad architecture:
It is hard to change because any change affects too many other parts of the system. (Rigidity)
When changes are made, other parts of the system break unexpectedly. (Fragility)
The code is hard to reuse in another application because it is too hard to get it out of the current application. (Immobility)
Front-end and back-end
In explaining what the application architecture is, let’s move away from technical terms and draw an analogy with everyday life. Look at your body.
Everything that is outside (the head and body) is the front and everything inside (the heart, brain and internal organs) is back.
Geniusee is developing a payment service. The Back-end team develops technologies that are responsible for the exchange, transfer, storage, etc., and the Front-end ensures that the user can conveniently interact with the functions of the application.
Now when we’ve figured out the difference between the front and back parts, let's look at two key approaches that modern developers use: API First and Loose Coupling. They allow programmers to easily change the structure of the application. Moreover, they make sure that every single part of the application can be changed without affecting the rest of the parts.
The API First method is responsible for high speed and innovation. The idea is to enter the data and get the API required for the Front-end and Back-end development teams: it allows them to write code and test it at the same time. The advantages of the method are to reduce application development costs, increase speed and reduce risks.
Life example: when you cook pasta Bolognese, you don’t need pasta first, then sauce: you can cook them in parallel. In this case, the food will cook faster, nothing will have time to cool, and friends will be able to evaluate the dish in the state in which it should be (and not as usual). One of the features that the application team loves the API First approach is called Swagger, an open-source framework that helps developers build the architecture, design, and create documentation for their applications. Swagger automatically generates API descriptions for most languages and frameworks, for both Front-end and Back-end commands.
The next approach is called Loose Coupling, in the literal translation - a weak connection. And if in life, an example of Loose Coupling can be a cancellation of the date on Valentine's Day, then on the contrary it helps. To be more precise, this feature simplifies the connection of components on the network.
The Loose Coupling system reduces the risk of accidental changes to individual objects, without changing others - since everything is interconnected in the application, this can lead to breakdowns and vulnerabilities. So, thanks to the possibility of limiting the operation of individual connections, the system helps to find and solve the problem faster, right during testing.
Microservices architecture against a monolith
Thanks to the principles of the API First and Loose Coupling, the application can act as a microservice - an application consisting of independent services that can work independently, scale freely on different devices.
Microservices architectures are better organized, as each microservice has a specific task. Their advantage is also in easy reconfiguration and rebuilding for various purposes. In addition, they are characterized by rapid deployment, fault tolerance, horizontal scaling, low entry threshold and ease of management.
Imagine a smart home where everything can be controlled and controlled with one device. Let's say this device is * core *, and the managed elements are * services *. With the main device, you can open windows, turn on the TV, or even close the curtains. This is how microservice architecture works.
But there is always an alternative, right? The second type of architecture is monolithic architecture. This means that the application is written as one unit of code, whose components are designed to work together, use the same resources and disk space.
Services in such applications are closely related, and if one of them changes, the others may have problems.
Imagine a layered chocolate cake. Each new layer makes the cake even tastier, but you cannot add a strawberry layer to the middle without changing the taste and texture of the cake. We can assume that the cake has a monolithic architecture.
Types of application architectures: best practices
Layered or N-tier architecture
This type of architecture is often associated with legacy systems. It is a traditional approach the application architecture be used to build on-premise and enterprise apps. As you can assume from the name in this type are several layers, three or more, each has its responsibility and makes up the application. These layers are arranged horizontally, which means that they can call into layers below only and help perform logical functions and manage dependencies.
This is a single application stack. In this stack, all the functionality is contained within one application. The interaction between the services and the way they are delivered is strongly dependent on one another. So basically, if you want to implement a single change to this application, you have to change the entire application infrastructure. A single change to the code leads to the re-release of the whole application. That makes it difficult to update this type of application, so most applications built with this architecture may only include general maintenance but not new features. This doubts the agility.
In this approach, the services in the architecture connect to the other accompaniments by application components with the help of communication protocol over a network. The principles here are not dependent on different technologies or vendors. In this architecture, the services can communicate in two ways: they either pass data or several services coordinate an activity.
Here are the most significant values of this style of software design:
Each of these values is on no matter what format (server or cloud computing) service-oriented architecture is based on.
And the benefits of service-oriented architecture are bold:
it promotes interaction;
gives the field for growing;
This type of architecture differs from the request-driven model with the core structure of the solution. Here the core is communication, the capture, processing, and persistence of events. What is an event? Any occurrence or change in state for hardware or software. It is a very convenient approach for creating modern application architectures as there we have minimal coupling. Event producers and event consumers make up an event-driven architecture. Not even knowing the consumer of the event, the event producer senses and represents the event as a message. After the detection, the event is transmitted to the event consumers through event channels.
Application architecture is a very complex topic, and everything written above is just the tip of the iceberg. Nevertheless, mentioned approaches might decrease the chance of failure a lot and bring the expected results to you faster than you even expect.