If we take an application and separate some of its components, we can see classes, methods, interfaces, each and every one of them with a function, which together allow our application to do everything we need, for example, an application to place orders in a small pizza restaurant (no, they do not want to be in Uber Eats) their development should not have a high degree of complexity because basically this store is not considering having so many orders in short periods of time.
But what if this same pizza restaurant gets a great Head of Marketing who managed to attract many customers and generate more orders than they can recieve, this produces a large flow of information, requests, database queries, entry/deletion of user information, something that happens daily in retail or banking companies (it is even worse in CyberDay), our application will stop working as expected and will certainly not have the required scalability, so if we do not want our monolith application to collapse, one way to solve these problems is by defining the architecture, which by the way, is not something that can be changed from one day to the next and choosing a type of architecture of the application will depend on many factors, I know, probably is not the best example of the small pizza store, since they will never have to make such a decision, but I trust you can follow the idea. So, there is no absolute answer about what architecture to use when developing, like everything in life... it depends (how philosophical). So, for robust and complex applications, the microservices architecture is usually used with a huge flag with the quote: "Divide to succeed".
Microservices are a way to develop applications in a faster, more scalable way and with shorter equipment and development cycles and is based on the development of services that work independently, communicated through REST APIs. A monolith application is basically an application that contains the entire program, while Microservices seek to separate all the functionalities in one application, so that each microservice has a specific function. For example, if a microservice A needs another microservice B, the first A is to register a user or a login, and the second B is to access a DataBase, they are independent microservices with different tasks, but at the same time, they need to communicate to work.
In order for the entire microservices ecosystem to interact with each other, they have to register, and this is done automatically on the Eureka server (or Discovery server). On this server, which in turn is a microservice, the name of the microservice, its port, location, and all the required information are registered, and when one microservice requires interacting with another, it will first go to the Eureka server, which will provide the name and address of this service so that it can be used. Think of it as a party from other times, when a guest (microservice) arrived at the party (microservices ecosystem), this was announced by the host (Eureka) and all the other guests knew that a new one had arrived and had information about who it was. Yes, it sounds a bit or quite nerdy, but this way you will never forget it again.
The microservices architecture has a series of dependencies/libraries that allow its optimal development and improve different functionalities, one of them are the Ribbon/LoadBalancer libraries, which are responsible for load balancing, the first is only older than the second mentioned. When I need to scale my application, and build more than one instance of a microservice, this load balance library gives me the most optimal instance to use. We also have Hystrix/Resillience4j both libraries related to error handling, for example if a microservice performs an operation that interacts with 3 more Microservices, I can use these libraries for error handling in case one falls, to avoid a cascading effect that affects the entire application.
Feign is an alternative library to using RestTemplate, since both are used to allow communication between microservices and is based on the use of notations.
The Zuul Library (also developed by Netflix), is an implementation of what is known as API Gateway (although Spring Cloud GateWay, is the most used API currently), and seeks to centralize all the information and records of the Microservices, in a single access point (port), allowing access to all microservices registered through an endpoint, which is formed by a common path, and a specific path of the microservice. In this way, access to microservices is from a single point.
As I mentioned earlier, we have the possibility to handle errors in case they occur and to be prepared so that our application can continue to work in case something fails (and here is my best advice and also free: my life quote is “Wait for the best, prepare for the worst”).
CircuitBreaker, is a pattern implemented by libraries such as Resilience4j or Hystrix and has 3 states, open, closed and semi-open state. When an application works normally, this circuit its closed (deactivated), when an error occurs or some microservice of the ecosystem fails, the circuit goes to an open state, in other words, it is "activated", and the microservice with failure is isolated from the rest of the microservices. Finally, the semi-open state is a "test" state and the microservice is being evaluated through an error threshold if it can be incorporated back with the other microservices. If the microservice exceeds this error threshold, it returns to the open state, otherwise it will go to a closed state and can be incorporated into the rest of the microservices ecosystem.
Another important component is the configuration server, and this microservice is the one that contains all the information and configuration files of each microservice, this configuration server is the first thing you need to boot, even before Eureka (discovery server). And information from this server can be stored either locally or remotely in a Git repository. In other words, first you need to get the information of each microservice from this repo, and only then you will just allow the registration of the microservices on the Eureka server. This configuration server also configures the environment or environment in case you have several, such as production, development.
Having so many microservices that perform a lot of functions and are in constant operation, like knowing if a microservice doesn't want to work on a Monday, or gets sick and feels bad, or just doesn't want to start. How can we see the "health" of our Microservices ecosystem? Because after all they are like little workers. Well, for this we have traceability tools that allow us to see how microservices work.
Sleuth is basically a traceability dependency and Zipkin is a Monitoring server with its graphical interface to be able to consult the status of the microservices ecosystem.
Sleuth allows you to provide unique IDs for request flows. The developer uses this ID to find or identify the flow of a complete request. There are 2 types of ID, Trace and Span. Trace is an ID for a complete flow (from request to response), and so the developer can find the logs of all the microservices involved in the flow. Span ID is a unique ID for only one Microservice in the flow, this is to identify the logs of a specific microservice.
This generates a kind of record that is then passed to the Zipkin monitoring server, through messaging brokers such as rabbitMQ so that Zipkin can then display this information on its interface.