📜 ⬆️ ⬇️

This question must be addressed by the architect. Or not?

I have some experience in the implementation of systems based on microservice architecture and I would like to share the questions (and answers) that arise during the implementation of such projects. Unfortunately, I do not have the right to spread about the projects in which I participated, so I invented my own spherical project in a vacuum. In this project, we will meet many standard problems.

I want to immediately note that the implementation will be rudimentary and only serves as a basis for asking questions. In any case, I hope you will find in the article a couple of interesting thoughts and references.

We will see how many interesting points can arise when writing all three classes and ask ourselves whether in this case the architect should decide or the developer can solve this problem himself.
')
image

The main idea of ​​the project


So, imagine that a bright future has arrived and cars with autopilot are running briskly through cities and villages. The question arises: do you need a personal car in such conditions?

I offer you a different approach. See, you go to my site, register, fill out the form. The questionnaire will have something like this: I want every day from Monday to Friday at 8:15 am to leave the house (point A) to get into a Mercedes and get to work (point B). Then in the evening at 18:00 to leave the office (point B) sit in the Audi and drive home (point A). There you can still tick an item: I want to be able to leave work earlier, the car is ready to wait no more than 8 minutes.

You can also call the car at any convenient time, the time of approach is not more than five minutes. The only reservation for such cases, the car you will use no more than five hours a week. This is for badly planned cases or, for example, for a trip to the store.

For all the pleasure you will be asked 150 cu. per month. Sounds good, doesn't it?

Naturally, under the hood, this service will have a lot of logic, since there is a lot to consider. For example, we must be competitive, i.e. it is necessary to offer people a cheaper option. To do this, you can provide joint trips. We can see that the neighbor of our client, it turns out, is also going in the same direction, but 5 minutes later. Can offer them a ride together, and offer a discount for it?

Our service will give us a lot of data from which we can get a lot of interesting information:


Based on this information, we can draw useful conclusions. For example, because we know where and when our customers go, we can predict traffic jams and adjust the route in accordance with this so as not to fall into them.

In order for this to be possible we need to provide for the possibility of collecting and analyzing information.

In general, we are talking about a large and complex project that can be called the fashionable word “cognitive solution” for a business with elements from the world of Internet of Things.

Methodology of development and analysis of requirements


Naturally, you first need to analyze all the requirements for the project, decide which methodology we apply (waterfall, rup, scrum, ...). But in this case we will skip all these stages, since almost all the questions raised in this article will arise in any case, regardless of the chosen methodology.

Language, Framework, Architecture


Initially, I am Java Developer, and therefore the implementation will be on java. Do not blame me.

By the way, the question immediately is whether the choice of a programming language is the task of the architect or is he "above it"?

For those who think that there is no, that the programming language is secondary and, in general, only the command is important, I propose to conduct a small thought experiment. Think about the language in which you would not exactly do such a project, i.e. believe that this would be a uniquely wrong decision. Now imagine that the architect decided to do the project in this language. And when you are angry angrily, he will tell you: this is a minor issue, the main command!

As it was said at the very beginning, we will do it on the basis of microservice architecture. Someone will say that it would be more accurate to start with a monolith and we will agree with him, but we will start right away from microservices.

And what framework we take? If you google a little, it becomes clear that there are no special options, we will do it on the Spring Framework. The reason is simple, Spring Cloud has everything we need.

We will also have all sorts of API Gateways, Config Services, Message Brokers, Docker, Workflow, Rule Engine and many other abstruse words on trivia.

There are two main approaches to the design of microservice.
  • Domain Driven Design
  • Functional driven


Domain Driven Design means defining domain objects and implementing all the necessary actions that your customer requires. For example, a customer of some kind of pharmacy system says: I need to be able to bring a new medicine into the system, delete the old ones, but editing the already introduced medicine should be prohibited. You make the “Medicine” class with all the required fields and implement the named functionality. So you have MedicineService . Those. With this approach, the starting point is the domain objects.

Functional Driven means that the necessary functionality is taken as a point of reference, and what kind of domain objects you will have to involve is a secondary matter.

Personally, I always start with domain objects, and then check whether it is possible in this way to implement the necessary functionality. If not, then I look, maybe you need to add another domain object to the service? In theory, as soon as I had to do it, then it’s time to look towards the “functional” approach.

There are not many services with one domain object, you can immediately start with functionality, but it's easier for me.

Let's define what services we need. To do this, we will see which domain objects we really need, and then we will look at what services we push them.

Let's start with the transport. For example with Car . Although just Car will not do. I will explain. For example, suddenly a person would be comfortable by car to the train station, transfer to the train, go to the city, and then ride the last two kilometers by bike? After all, we also want to ask for money from this nice person? And what if someone wants this very last mile on the mono-wheel? Do not offend the same person, especially if he has money for it? Let's rent him a monowheel! So in the future we may need a lot of classes describing different vehicles.

Let’s take the abstract Vehicle class as the starting point.

public abstract class Vehicle { …. protected String model; protected int wheelNumber; protected Date manufactureYear; protected EngineType engineType; protected Producer producer; } 

As we found out we will have a different Vehicle , let's do a couple:

 public class Car extends Vehicle { public Car() { wheelNumber = 4; } } 

and one more for the poor but sporty:

 public class Bicycle extends Vehicle { public Bicycle() { wheelNumber = 2; } } 

Wonderful. Now we need the one for whom we all do this: our client, he is also a source of our income. Call this entity Customer'om

 public class Customer { private String firstName; private String lastName; private Date birthDay; } 

We also need a contract with the client, which will indicate what kind of vehicle he will receive from us and how much money we will get for it.

 public class Contract { private long customerId; private long vehicleId; } 

Total we have one class hierarchy with Vehicle on top, Customer and Contract . I propose making them VehicleService , ContractService and CustomerService .

What does 'micro' in the word 'microservice' mean?
Previously, I was tormented by the question, but what does “micro” mean in the word “microservice”? In theory, this means "small." But what does small mean?

Often there is an opinion that microservice should be placed in the head of one person, or so that a team of three people and so on can do it. This certainly makes sense, but I, as another option, offer a slightly different view on this question.

When you implement, and it’s better, if you are still thinking of doing some kind of microservice, then ask yourself: if I am mistaken with this service, can I afford to throw it out and write a new one from absolute zero?

If your answer is "yes", then this is microservice. And if not, then no. And what is important, ask yourself this question regularly as it is implemented. If you suddenly have a no answer, start refactoring / crushing / rethinking this monster. Although usually by this time all polymers are already <.. censored ..>

You can look at the rudimentary implementation of services here .

Now let's put the whole thing in a docker. By the way, I was here recently at a meetup on the occasion of the fourth birthday of the docker. An interesting link was presented to us there, it was very bad in some places, I recommend to see

To put our services in the docker, we need a plugin for mavena (see pom.xml docker-maven-plugin) and dockerfile.

We will launch all services through docker-compose, for this, docker-compose.yml is in the root of the project.

Pay attention to the .env file and its contents. More on this file can be found in the documentation . Without this file, my Windows 7 machine failed to initialize MySQL.

What did I do?


Let's start with the pros:


Unfortunately, the advantages have ended

Minuses:

Unfortunately, there are a lot of them, so we’ll only consider a couple of pieces selectively.

Before I begin to understand the shortcomings, I want to mention one small, but extremely important point: the microservice architecture initially assumes that at least two instances of each service work in the system, which share the workload. If this rule is not respected, then, in my opinion, talking about microservice architecture can be immediately stopped. Yes, I know, this is debatable, but my opinion is exactly that.

Unfortunately, redundancy does not guarantee 100% availability of services and therefore, if there is another reasonable way to maintain the system’s performance, then it can and should be used.

Adding a new type of transport to the system


The class diagram is not an architecture.
I often hear the following statement: the class diagram is not an architecture. It can be explained as follows: what and how is there in the module that is done does not interest me, what is important for me is what the modules do and how they communicate with each other. As a rule, these are people who never worked as a programmer, but somehow immediately became architects. What can I say to that? Maybe they are really right, but my experience suggests otherwise. And now we will consider just such a case.


So, to begin with, let us ask ourselves, is the right approach to vehicle management solution we have applied? I will clarify the question. For example, we want to offer our customers a scooter rental.

Now VehicleService looks like this.

image

We need to add a new Scooter entity to the system. We have everything built on inheritance, so the end result will look like this:

image

We will write a new Scooter class in VehicleService , test it, compile it, add it. And if we have dozens of types of vehicles? Every time we will write a new class, test, compile, etc.? Is there another way?

You can, for example, do so. Make the VehicleType class.

 public class VehicleType { private String name; private List<VehicleProperty> properties; …. } 

As you can see, VehicleType has a VehicleProperty :

 public class VehicleProperty<T> { private String name; private T value; private String description; ….. } 

Let's make another Vehicle class:

 public class Vehicle { private VehicleType vehicleType ; private List<VehicleProperty> customProperties; …... } 

Now, if we want to add a scooter to the system, we will first create a VehicleType "Scooter":

 VehicleProperty wheelNumberProperty = new VehicleProperty<Integer>("wheelNumber", 2, "number of wheels"); ……. VehicleType scooterType = new VehicleType("Scooter"); scooterType.addProperty( wheelNumberProperty); …….. 

And if we need to create an instance of a scooter:

 Vehicle scooter1 = new Vehicle(scooterType); ….. 

Thus, we can add as many new types to the system without creating new classes in VehicleService .

image

Do you still remember the last lyrical digression about the class diagram and the question is whether it is the subject of architecture? Here you have two class diagrams that describe two fundamentally different approaches to the implementation of the service. Are they part of the architectural solution? In my opinion, very much, because in this case, the method of implementation even at the class level has far-reaching consequences and can turn the future life of the project into hell.

Hidden business case
In general, the question of changing something in the service requires special attention. Let me explain by example.

I had such a case. At the rally I present our future architecture to the client. Answered questions. It seems everything is good, everyone likes everything, everyone is happy. And here the main client IT specialist asks such a simple question: how quickly can you add a new field to the “ABC” domain object? A simple question, right? I answered simply: add a field - 2 minutes, write tests from a few minutes to a couple of hours, then run all the tests (it can take hours), etc. In general, I could not name any specific figure, and I think that no one can, as long as it has not been done at least once. It seems like I answered correctly, but the feeling that the answer is incorrect did not leave me. Then one day I realized how I had to answer.

Today, my answer is: “How often should this happen?” If this is an exceptional situation, then in principle it does not matter how long the addition of the field lasts, if only this period was adequate from a business point of view. If this happens often, then the question should be asked: is this the Business case? And if so, then this functionality should be initially laid into the system and then the answer should be: 20-30 minutes (this is a lie, of course, but it sounds good), it can take longer if the case is severe.

Another question also arises: how is it that this business case has surfaced just now?
And even more important question, are there any other similar business cases that we have missed?

Next moment. See if our VehicleService falls, then we can neither create a new copy of the bike in the system (for example, we purchased a new batch of bikes and want to add them to the system) or rent a bike. Those. Neither our clients nor our employees in the office can do anything. It would be much better if, even in case of problems in the office, our clients could make orders and bring us money. How can I do that? It looks like we need to divide our VehicleService into two, one for customers and one for our employees.

Remember, I talked about the domain and functional approach to the design of services. The problem with the availability of the service for clients and our employees is an excellent example when we started with the domain approach, got into the problem and move on to the functional approach. We need two services, the domain objects are basically the same, but the functionality is different.

Suppose, again, our VehicleService has fallen. This means that neither cars nor bicycles can be rented. It would not be bad if the service for cars is not available, then the service for bikes would work further. How can I do that? Split a VehicleService into several services, one for each type of vehicle?

In fact, the last two examples of possible problems are not entirely correct, because they can be solved through redundancy, i.e. Should work several instances of the service. But, as I said at the beginning, no redundancy will give 100% availability. That is why we must try to solve the issue of service availability in some other reasonable alternative way.

There was a holiday on our street, a lot of clients came to us who wanted to rent a car from us. The service does not cope, but this is not a problem, we will start another instance and everything is fine again. But now we have two copies of the service, not only for cars, but also for bicycles. Is it bad? I do not know, but certainly not good. It is quite possible that you can live with this, you have to look.

After considering these problems, we can offer another option for implementing the service. We will make a separate service for each type of transport. But this question arises.
Suppose you are a client, have visited the site and want to look at the list of all possible types of transport, i.e. you want to see something like:


Where does this list come from? Probably we need another service VehicleTypesService , which will know what types of vehicles exist in our system. Where does he get this information from? The first thing that comes to mind is to write with pens to the database. If we provide another type of transport to services, then we write a service for it and do not forget to go to the VehicleTypesService and add one more line to it in the database.

And can be different. At the start, each service should knock to VehicleTypesService to inform it of its existence. This solution seems to be looking good, but does not answer the question of what to do if we need not to add, but delete the view of the vehicle. For example, after six months, we realized that nobody uses monowheels, we want to remove it from the system. How do we do this?

And here is another interesting question. As you can see, Vehicle has an EngineType field.

 public abstract class Vehicle { …... protected EngineType engineType; 

In my rudimentary implementation, the EngineType has enums:


And now the actual question itself: how do we create a car with a hybrid power plant?

Will we make EngineType's list instead of EngineType (and then 99% of the machines suddenly have a list with one element)?

 public abstract class Vehicle { …... protected List<EngineType> engineTypes; 

Or maybe add a new type to enum, something like Gibrid ?

Who in this case makes the decision and, accordingly, bears responsibility for it?

Is it possible to say that this is a question of architecture or is this a too “minor” question?

Using the example of the EngineType field, I want to ask one more question: do we really need to know which engine is at the car? After all, with a high degree of probability, our client doesn’t care at all, we pour diesel or gasoline into the tank. But, for example, the ability to take a bicycle with you (i.e., a large trunk or whether there is a special fastener for a bicycle) can be very important.

In fact, the type of engine is of course important, but not for the client, but for us, the company that provides this service. One of the reasons is statistics (for example, for fuel costs or necessary repairs). It will vary greatly for each type of engine. This raises another question: shouldn't domain objects (or their representations) be different for our backend and frontend?

How to answer questions of this kind? I know only one way: you need to talk more often with people from the business, ask them what and where they want to see? Unfortunately, they themselves often do not know the answers to these questions.

Complex queries


Suppose I want to see all clients with their contracts that live on Orange Street in the glorious city of Berlin. Those. I want to see something like this tablet:
Last Name, First NameNumber of contractDate of signing the contractA machine
Pupkin, Vasya1234501.01.2017Audi Q4
........................

As you can see, the table contains data from three microservices: CustomerService , VehicleService and ContractService . How are we going to put them together? In the case of a monolith, the question is solved by one request to the base, and what to do when we have three bases?

image

There are different options for solving this small problem and next time we will discuss them.

Thanks, Cap!
And now a minute of attention. When did you have to ask these questions? Answer: Of course, before writing code. And to answer these questions should including the architect.

How does the decision making and reporting process to the team of programmers
And now a small “stream of consciousness” on the topic: how does the decision-making process and the reporting of these to the programmers team occur at all? I know two theoretical possibilities, and everything else is just their mixes in various proportions:

  1. Uncle comes and shows a presentation with all sorts of colors in the form of diagrams arched with tricky words. Often he makes it clear that he has a lot of experience behind him, he understands what he is doing and so on, and therefore the decision has been made finally and irrevocably. Hearing a murmur from a distant series, quickly declares that it is always open to new ideas. In fact, the architect here is a full-fledged dictator.
  2. The uncle comes, says that he is an “artist” and sees the “picture” like this. At the same time, he directly says that it is not he who will “draw”, but people in the “hall”, and therefore it is in their vital interests to scold the picture and, if possible, offer alternatives. In this case, the architect seeks to disclaim any responsibility.

Personally, I feel closer (again, closer, and not completely satisfied) with the second option. There are probably dozens of reasons for this, consider some of them.

I do not want to be responsible. No seriously. I, like any reasonable person, want to get a lot of money, do nothing and not be responsible for it. But objective reality, unfortunately, shows that this is impossible. Well, or I, at least, have not yet found the desired method.

But for “sharing with another responsibility” there is another reason and it is no less important. When a programmer participates in a discussion (and thus indirectly in a decision), he implements it with a different level of diligence. If a jamb suddenly comes out, he realizes that this is his fault, and not “this idiot architect invented garbage, but let me have it.”

Another reason is incomplete knowledge of the system and its environment. Imagine that after the presentation of the architecture you get the question: “how does this all fit in with the fact that we need to pull data from someone else’s billing system?” And then the architect suddenly finds out that it turns out there is a third-party system with which we need to integrate. And everyone knows this, except the architect. Yes, it happens too.

It is important to explain why this decision was made, and not any other, what are the advantages and mention the disadvantages. In this case, people understand what is happening, they become much more patient.

True, it should be noted here that such a “democracy”, when a decision is made by a bunch of people, has its limits. Moreover, such a “democratic” solution is not always possible. For example, when everyone understands that each of the solutions discussed is bad, and no one knows the good. In the end, if it suddenly turns out that the decision was wrong, the responsibility for this lies with the architect. Rebuffed, they say, I asked everyone, it was a collective decision, unfortunately it will not work. The architect is personally responsible for all decisions, regardless of how they were made.

Lastly, two more thoughts.

First of all, if you think that your architecture is good, it means that you have not shown it to anyone yet. By the way, this also applies to the code.

Secondly, I am a categorical supporter of the opinion that an architect should have practical programming experience so that he knows what it means to implement other people’s desires. Even better, the architect is directly involved in the project, i.e. writes code, or at least this same code revises. The architect must regularly check that the code conforms to accepted architectural solutions.

Once I missed this moment and only at the Sprint Review I learned absolutely by chance what the programmer did not do as he was told. To the question: is it written in black in English, send “event”, why are you sending http request? the answer was received: well, I thought that it would be better. Naturally, it is impossible to control everything, you need to trust your team. But as the Germans say, trust is good, but control is better.

Change history


Once someone clever wants to find out why customers cancel or not extend the contract and go to competitors? Maybe the last time the car did not like / was late? Or maybe a man traveled before with his colleague, who went over to our competitors and lured his companion to them?

To answer such questions, we need to know what happened in the system, i.e. we need a change history.

How this can be done we will discuss in the next article.

Infrastructure problems


Microservice architecture imposes very specific requirements on the infrastructure of the entire application, namely, we need


These and other questions in the next part.

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


All Articles