In previous articles, we highlighted the scope of the approach and examined the main methodological principles of Domain Driven Design .
In this article, I would like to identify the main modern approaches to building an enterprise system architecture: Supple, Screaming, Clean and give them their clear interpretation in the form of a complete ready-made solution.
In the following, we will look at each design pattern in detail: let’s define the scope, provide code examples, and highlight recommended practices. As a result, we will write a ready microservice.
In the last article, we dwelt on the fact that DDD includes the practice of implementation through the model. The subject area should be described through your code. Let's try to figure out how to do this.
In his book, Eric Evans presents a number of design patterns recommended for use, and designates this approach as flexible:
In the name of architecture flexibility, many unnecessary constructs were piled up in programs. Extra levels of abstraction and indirect references are more likely to interfere than help in this matter. Look at the architecture that really inspires programmers involved in its refinement, and you will see, as a rule, something very simple. But simple does not mean easy to perform. To create such elements that can be assembled into complex systems and it is not difficult to understand, it is necessary to combine “devotion” to design according to a model with a rather strict architecture style. A certain design skill is needed not only to create something, but even to use ready-made.
Eric Evans, Domain-Driven Design: Tackling Complexity of the Heart of Software
The presented set of design patterns is not a strict architecture or a ready-made solution, but rather food for thought.
Similar thoughts occurred in the minds of many developers and designers of complex systems.
In 2011, there was an article by Robert Martin - Screaming Architecture , which says that your code should not just describe the subject area, but yell about it, preferably with a checkmate.
So what is your application scream? The structure of the package; do they scream: Health Care System, or Accounting System, or Inventory Management System? Or do they scream: Rails, or Spring / Hibernate, or ASP?
Robert C. Martin, September 30, 2011
Robert says that the code of your application should reflect the activity of the application, instead of adjusting to the rules of the framework. The framework structure should not limit your architecture. The application, in turn, should not be tied to the database or http protocol, these are just storage and delivery mechanisms. The bounding box is a tool. You should not become an adept frame maker. Tests of your application are tests of the logic of its operation, and not testing of the http protocol.
A year later, Robert Martin’s next article, The Clean Architecture . In it, the author tells how to make the code scream. Having studied several architectures, he outlines the basic principles:
On Habré already published a very good article Misconceptions Clean Architecture . Its author, Jeevuz , chewed very well the subtleties of understanding this approach. I strongly recommend to get acquainted with it as well as with original materials.
The description of the above approach does not look so unambiguous. As part of the development of the architecture of a number of complex corporate systems, I and my colleagues developed a rather clear interpretation of the described approaches, which I am going to present below.
Before the advent of computers and programming languages, paper workflow was used to build and manage systems with complex business logic. The result of any process was a document that ultimately described a particular business object. As a result, clerical work was reduced to three simple actions :
Document - recording information about the economic activity of a particular real business object.
Please note that the document itself is not a real business object, but only its Model . At the moment, paper documents are being superseded by electronic ones. A document can be a record in a table, a picture, a file, a sent letter, or any other piece of information.
I would not like to use the word document in the future, since it will be more confusing, we will use the concept Entity from DDD terminology. But you can imagine that now your entire system is an electronic document management system that performs four simple Actions .
Action (Action) - the structural unit of the business model; a relatively completed separate act of a perceived goal, arbitrariness and premeditation of the individual activity of a business object, distinguished by the end user.
A good example of Dacevtia is theatrical act. Theater simulates events from real life. The act is a meaningful part of the play. But in order to make the story complete, you need to lose several acts in a strictly defined order. Such an order in our architecture we will call Mode .
Mode (Conduction) - a set of Actions in a certain order, which has a complete meaning, bringing benefit to the end user.
For these Modes of operation, a selective jig or Selector was invented. More specifically, the " US2870278A patent was obtained for the plurality of sequences of operations". We know this device as a "twist" of a washing machine. Architectural "twist" is given at the beginning of the article.
The variability of the approach is manifested in the fact that with such an architecture you can choose any of the four Modes , passing which you will not perform unnecessary Actions .
Starting the washing machine, you can select the mode: wash, rinse or spin. If you choose to wash, then your machine will still rinse the laundry, and then it will wring out. With rinsing included, you are sure to get a spin. Spin - the final action in the process of washing it is the most "simple." In our architecture, the simplest Activity - Representation , and begin with it.
If we talk about a pure representation without accessing the database or an external source, then we give out some static information: html-page, file, directory lying in the form of json'a. We can even issue just a Code response - 200:
Let's write the simplest "Health checker"
module Health class Endpoints < Sinatra::Base get '/check' do; end end end
In the most primitive form, our scheme will look like this:
I ask you to note that in the Sinatra framework, the Endpoints class combines both Router and Controller in one class. Does this not violate the principle of sole responsibility? In fact, Endpoints is not a class, but a layer expressed through a class, and its area of ​​responsibility is at a higher level.
Ok, what about Router and Controller ? They are not represented by a set of classes, but by the name and implementation of a function. A static file is generally a file. One class answers one responsibility, but don’t try to express every responsibility through a class. Proceed from practicality, not from dogmatism.
Business is demanding on the availability of your application. Why would someone need your service if we cannot use it at the right moment? To ensure data integrity, we record a change in the state of a business object after each processing.
To retrieve an object from the repository, no business logic is required. Imagine that we are automating the activities of a hotel chain and we have a magazine of guests at the front desk. We decided to look at the information about the visitor.
module Reception class Endpoints < Sinatra::Base # Show item get '/residents/:id', provides: :json do resident = Repository::Residents.find params[:id] status 200 serialize(resident) end end end
Working with the storage system in the form of a graphic scheme:
As we can see, the communication between the level responsible for storage and the level responsible for data presentation is implemented through the Response model. This model does not belong to any of these layers. In fact, this is a business object and it is located on the layer responsible for the business logic.
If it comes to the fact that the object model changes based on its properties without adding new data, then we can directly access the Interactor layer. The Interactor layer is the key in our application, it describes the entire business logic in the form of separate Use Cases, and it is here that the Entities are modified.
Consider this use case. In our hotel, the visitor is already registered, but we celebrate every visit or departure.
module Reception class Endpoints < Sinatra::Base # Register resident arrival post '/residents/:uid/arrival', provides: :json do result = Interactors::Arrival.call(resident_id: params[:id]) check!(result) do status 201 serialize result.data end end # Register resident departure post '/residents/:uid/departure', provides: :json do result = Interactors::Departure.call(resident_id: params[:id]) check!(result) do status 201 serialize result.data end end end end
Let's stop a little. Why not to do implementation by one method with status
parameter? Interactors Arrival
and Departure
are completely different. If a guest has come to us, then we need to check whether the cleaning has ended, whether there have been any new messages for him, etc. When he leaves, we, on the contrary, must initiate a cleaning if necessary. In turn, we don’t even remember the messages, because if he were in a hotel, we would call him right away. It is all this business logic that we prescribe on the Interactor layer.
But what should we do if we have data from the outside? This is where the Data Collect action connects.
During the first registration of a guest at a hotel, he fills out a registration form. This form is verified. If the data is correct, then a business process is registered. The process returns data — the created “Resident” business model. We present this model to the guest in a readable form:
module Reception class Endpoints < Sinatra::Base # Register new resident post '/residents', provides: [:json] do form = Forms::Registration.new(params) complete! form do check! form.result do status 201 serialize form.result.data end end end end end
Schematically it looks like this:
This approach has a high threshold of entry. Its application requires a great experience from the designer for a clear understanding of the tasks to be solved. Difficulty is also a variety of choice of the necessary tool. But, despite the complexity of the structure, implementation at the code level is incredibly simple and expressive. Although it contains a number of conventions and powers of attorney. In the future, we analyze each design pattern separately, describe how to create it, test it, and designate the scope. And in order not to get confused in their diversity, the full map is offered:
Source: https://habr.com/ru/post/429750/
All Articles