# თავი 13: მიკროსერვისების არქიტექტურა (Microservices Architecture) --- ## 13.1 მონოლითიდან განაწილებულ სისტემებამდე ყოველი აპლიკაცია, რომელიც ამ კურსში ავაშენეთ, იყო **მონოლითი** — ერთიანი Spring Boot აპლიკაცია, რომელიც აერთიანებს მომხმარებლის ინტერფეისს (UI), ბიზნეს ლოგიკას და მონაცემთა წვდომას ერთ სადეპლოიო (deployable) JAR ფაილში. კონტროლერები, სერვისები და რეპოზიტორები იზიარებენ ერთსა და იმავე პროცესს, ერთსა და იმავე მეხსიერებას და ერთსა და იმავე მონაცემთა ბაზას. ეს არ არის ნაკლი. მონოლითების შექმნა, ტესტირება და დეპლოიმენტი მარტივია. აპლიკაციების უმეტესობისთვის ისინი სწორ არჩევანს წარმოადგენენ. მაგრამ მონოლითებს აქვთ ლიმიტები. კოდის ბაზის ზრდასთან ერთად, გადახდების მოდულში შეტანილი ცვლილება მოითხოვს მთლიანი აპლიკაციის ხელახალ დეპლოიმენტს. ძიების ფუნქციონალის მასშტაბირება (scaling) ნიშნავს ყველაფრის მასშტაბირებას. შეტყობინებების სისტემაში მეხსიერების გაჟონვა (memory leak) აზიანებს შეკვეთების დამუშავების პროცესსაც. როდესაც აპლიკაცია აღწევს გარკვეულ ზომას — გუნდის წევრების რაოდენობის, ტრაფიკის ან კომპლექსურობის კუთხით — ეს შეზღუდვები მნიშვნელოვანი ხდება. **მიკროსერვისები** არის არქიტექტურული სტილი, რომელიც აგვარებს ამ შეზღუდვებს აპლიკაციის პატარა, დამოუკიდებელ სერვისებად დაშლით. თითოეული სერვისი ეშვება საკუთარ პროცესში, ფლობს საკუთარ მონაცემებს და სხვა სერვისებთან ურთიერთობს ქსელის საშუალებით. ეს თავი წარმოგიდგენთ კონცეფციებს, კომპრომისებს და Spring Cloud-ის ინსტრუმენტებს, რომლებიც მიკროსერვისების გამოყენებას შესაძლებელს ხდის. ჩვენ ნულიდან ავაშენებთ მუშა მულტი-სერვისულ სისტემას, რათა დავინახოთ, თუ როგორ უკავშირდებიან ეს ნაწილები ერთმანეთს. --- ## 13.2 მონოლითი მიკროსერვისების წინააღმდეგ | ასპექტი | მონოლითი | მიკროსერვისები | | --- | --- | --- | | **დეპლოიმენტი** | ერთიანი ერთეული — ხდება მთლიანი აპლიკაციის დეპლოიმენტი. | თითოეული სერვისის დეპლოიმენტი ხდება დამოუკიდებლად. | | **მასშტაბირება** | ხდება მთლიანი აპლიკაციის მასშტაბირება. | ხდება ინდივიდუალური სერვისების მასშტაბირება. | | **ტექნოლოგია** | ერთი ტექნოლოგიური სტეკი. | თითოეულ სერვისს შეუძლია გამოიყენოს განსხვავებული სტეკი. | | **მონაცემთა მართვა** | საზიარო მონაცემთა ბაზა. | თითოეული სერვისი ფლობს საკუთარ მონაცემებს. | | **კომუნიკაცია** | პროცესშიდა მეთოდების გამოძახებები. | ქსელური გამოძახებები (HTTP, messaging). | | **შეცდომების იზოლაცია** | ერთ ბაგს შეუძლია მთელი სისტემის გათიშვა. | შეცდომები ლოკალიზებულია თითოეულ სერვისში. | | **კომპლექსურობა** | დასაწყისში მარტივია, ზრდასთან ერთად რთულდება. | უფრო მაღალი ოპერაციული კომპლექსურობა პირველივე დღიდან. | | **ტესტირება** | უფრო მარტივია end-to-end ტესტირება. | მოითხოვს ინტეგრაციულ და კონტრაქტის ტესტებს. | | **დეველოპმენტის სიჩქარე** | თავდაპირველად სწრაფია, იკლებს ზრდასთან ერთად. | ნელი დასაწყისი, უკეთესად მასშტაბირდება გრძელვადიან პერსპექტივაში. | ### როდის გამოვიყენოთ მონოლითი დაიწყეთ მონოლითით, როდესაც გუნდი პატარაა (1–5 დეველოპერი), მოთხოვნები იცვლება, დომენი ჯერ კიდევ კარგად არ არის შესწავლილი, ან გჭირდებათ პროდუქტის სწრაფად გაშვება. მონოლითი არ არის მხოლოდ გარდამავალი ეტაპი — ის არის ლეგიტიმური, მუდმივი არქიტექტურა მრავალი აპლიკაციისთვის. ### როდის განვიხილოთ მიკროსერვისები განიხილეთ მიკროსერვისები, როდესაც გუნდი საკმარისად დიდია იმისთვის, რომ დამოუკიდებელ დეპლოიმენტს ჰქონდეს მნიშვნელობა, როდესაც კონკრეტულ კომპონენტებს აქვთ ძალიან განსხვავებული მოთხოვნები მასშტაბირებაზე, როდესაც დომენი კარგად არის გასაგები და შეიძლება სუფთად დაიშალოს, ან როდესაც გჭირდებათ სისტემის ნაწილების დამოუკიდებელი დეპლოიმენტი სხვადასხვა სიხშირით. ყველაზე მნიშვნელოვანი რჩევა ამ თავში: **არ დაიწყოთ მიკროსერვისებით.** დაიწყეთ კარგად სტრუქტურირებული მონოლითით. დაშალეთ იგი მხოლოდ მაშინ, როდესაც მონოლითისგან გამოწვეული პრობლემები აჭარბებს განაწილებული სისტემების (distributed systems) სირთულეს. ნაადრევი დაშლა არის ერთ-ერთი ყველაზე ძვირადღირებული არქიტექტურული შეცდომა, რაც გუნდმა შეიძლება დაუშვას. --- ## 13.3 მონოლითის დაშლა თუ გადაწყვეტთ სისტემის დაშლას, მთავარი გამოწვევა სწორი საზღვრების პოვნაა. Domain-Driven Design (DDD) გვაწვდის სასარგებლო კონცეფციებს: * **Bounded Context (შემოსაზღვრული კონტექსტი)** — თითოეული მიკროსერვისი უნდა წარმოადგენდეს ბიზნეს დომენის თვითკმარ არეალს მკაფიო საზღვრებით. შეკვეთა არის შეკვეთა, პროდუქტი არის პროდუქტი და მათ მართავენ სხვადასხვა სერვისები. * **Single Responsibility (ერთი პასუხისმგებლობა)** — თითოეული სერვისი კარგად აკეთებს ერთ კონკრეტულ საქმეს. * **Data Ownership (მონაცემთა ფლობა)** — თითოეული სერვისი ფლობს და მართავს საკუთარ მონაცემებს. არანაირი საზიარო მონაცემთა ბაზები. განვიხილოთ მონოლითური ელექტრონული კომერციის (e-commerce) აპლიკაცია, რომელიც დაშლილია სერვისებად: | სერვისი | პასუხისმგებლობა | მონაცემთა ბაზა | | --- | --- | --- | | User Service | ავთენტიფიკაცია, პროფილები | Users DB | | Product Service | კატალოგი, ძიება, კატეგორიები | Products DB | | Order Service | კალათა, შეკვეთის გაფორმება, ისტორია | Orders DB | | Payment Service | გადახდების დამუშავება | Payments DB | | Notification Service | ელ-ფოსტა, SMS, push შეტყობინებები | Notifications DB | დაშლა მიჰყვება ეტაპობრივ სტრატეგიას, რომელიც ცნობილია როგორც **Strangler Fig Pattern** — ხდება თითო მოდულის გამოყოფა, შიდა მეთოდების გამოძახებების ჩანაცვლება REST-ით ან messaging-ით, ცალკე მონაცემთა ბაზის დაყენება და ტრანზიციის განმავლობაში სისტემის შეუფერხებლად მუშაობის შენარჩუნება. --- ## 13.4 სერვისთაშორისი კომუნიკაცია მონოლითში კომპონენტები ერთმანეთს უკავშირდებიან მეთოდების გამოძახების საშუალებით — რაც სწრაფი, საიმედო და ტიპების მიმართ უსაფრთხოა. მიკროსერვისების არქიტექტურაში კომპონენტები ერთმანეთთან კომუნიკაციას ამყარებენ ქსელის მეშვეობით. ეს იწვევს დაყოვნებას, შეცდომების ახალ რეჟიმებს და სერიალიზაციის ზედნადებ ხარჯებს. არსებობს ორი მიდგომა: ### სინქრონული კომუნიკაცია (REST) სერვისები ერთმანეთს პირდაპირ იძახებენ HTTP-ის გამოყენებით. გამომძახებელი ელოდება პასუხს. ეს მარტივია და შესაფერისია მაშინ, როდესაც გამომძახებელს შედეგი დაუყოვნებლივ სჭირდება: ```java @Service public class OrderService { private final RestClient restClient; public OrderService(RestClient.Builder builder) { this.restClient = builder .baseUrl("http://product-service") .build(); } public ProductDTO getProduct(Long productId) { return restClient.get() .uri("/api/products/{id}", productId) .retrieve() .body(ProductDTO.class); } } ``` სინქრონული კომუნიკაციის გაგება და დებაგინგი მარტივია, მაგრამ ის ქმნის მჭიდრო კავშირს — თუ product service გათიშულია, order service-იც წყვეტს მუშაობას. ### ასინქრონული კომუნიკაცია (Messaging) სერვისები ურთიერთობენ შეტყობინებების ბროკერის საშუალებით (ActiveMQ, RabbitMQ, Kafka — როგორც ეს მე-12 თავშია განხილული). პროდიუსერი აგზავნის შეტყობინებას და არ ელოდება პასუხს: ```java @Service public class OrderService { private final JmsTemplate jmsTemplate; public OrderService(JmsTemplate jmsTemplate) { this.jmsTemplate = jmsTemplate; } public OrderDTO createOrder(OrderRequest request) { Order saved = orderRepository.save(new Order(request)); jmsTemplate.convertAndSend("order-events", new OrderCreatedEvent(saved.getId(), saved.getUserId())); return new OrderDTO(saved); } } ``` ასინქრონული კომუნიკაცია უზრუნველყოფს სუსტ კავშირს, უკეთეს გამძლეობას (შეტყობინებები რიგში დგება, თუ მომხმარებელი გათიშულია) და უკეთეს მასშტაბირებას (რამდენიმე მომხმარებელი პროცესს პარალელურად ამუშავებს). ამის ფასია კომპლექსურობა და საბოლოო კონსისტენტურობა (eventual consistency). ### როდის რომელი გამოვიყენოთ | გამოყენების შემთხვევა (Use Case) | მიდგომა | | --- | --- | | მონაცემების მოძიება, რომელიც გამომძახებელს დაუყოვნებლივ სჭირდება. | სინქრონული (REST) | | ფონური სამუშაოს ინიცირება (ელ-ფოსტა, ლოგირება). | ასინქრონული (Messaging) | | მოვლენის შესახებ რამდენიმე სერვისის შეტყობინება. | ასინქრონული (Messaging) | | მომხმარებლის წინაშე არსებული ოპერაციები, რომლებიც მოითხოვენ დაუყოვნებლივ პასუხს. | სინქრონული (REST) | --- ## 13.5 Spring Cloud-ის ეკოსისტემა Spring Cloud უზრუნველყოფს ინსტრუმენტებს განაწილებული სისტემების საერთო გამოწვევებისთვის. ის დაშენებულია Spring Boot-ზე და ინტეგრირდება იმ ინფრასტრუქტურულ კომპონენტებთან, რომლებიც მიკროსერვისებს სჭირდებათ. Spring Cloud **2025.1** (კოდური სახელი "Oakwood") არის გამოშვება, რომელიც თავსებადია Spring Boot 4-თან და Spring Framework 7-თან. ამ თავში მოყვანილი ყველა მაგალითი იყენებს ამ ვერსიას. დაამატეთ BOM თქვენს `pom.xml`-ში: ```xml org.springframework.cloud spring-cloud-dependencies 2025.1.0 pom import ``` ძირითადი კომპონენტები: | კომპონენტი | დანიშნულება | | --- | --- | | **Eureka** (Service Discovery) | სერვისები არეგისტრირებენ საკუთარ თავს და პოულობენ ერთმანეთს სახელის მიხედვით. | | **Spring Cloud Gateway** | შესვლის ერთიანი წერტილი, რომელიც ამისამართებს მოთხოვნებს სწორ სერვისთან. | | **Spring Cloud Config** | ცენტრალიზებული კონფიგურაცია, შენახული Git-ში, მიეწოდება ყველა სერვისს. | | **Resilience4j** | Circuit breakers, retries და rate limiting შეცდომებისადმი ტოლერანტობისთვის. | | **Spring Cloud LoadBalancer** | კლიენტის მხარეს არსებული load balancing, როდესაც არსებობს სერვისის რამდენიმე ინსტანსი. | --- ## 13.6 მუშა მაგალითი: კალკულატორის სისტემა იმის სანახავად, თუ როგორ უკავშირდებიან მიკროსერვისები ერთმანეთს პრაქტიკაში, ჩვენ ავაშენებთ მინიმალურ სამ-სერვისიან სისტემას. ბიზნეს ლოგიკა განზრახ ტრივიალურია — ძირითადი არითმეტიკა — ასე რომ, შეგიძლიათ ფოკუსირდეთ იმაზე, თუ როგორ ესაუბრებიან სერვისები ერთმანეთს და არა იმაზე, თუ რას ითვლიან ისინი. არქიტექტურა: ``` ┌──────────────────┐ │ discovery-server │ (Eureka registry — პორტი 8761) └──────────────────┘ ▲ ▲ │ │ register register │ │ ┌───────┴──┐ ┌─┴────────────┐ │calculator │ │ math-facade │ │ service │ │ service │ │(პორტი 8081)│ │ (პორტი 8082) │ └───────────┘ └───────────────┘ │ გამოძახებები service discovery-ის გავლით │ ┌─────┴─────┐ │ calculator │ │ service │ └────────────┘ ``` **discovery-server** — Eureka-ს რეესტრი, სადაც სერვისები არეგისტრირებენ საკუთარ თავს. **calculator-service** — stateless სერვისი, რომელიც ასრულებს არითმეტიკულ ოპერაციებს. **math-facade-service** — მომხმარებლის წინაშე არსებული სერვისი, რომელიც იღებს მოთხოვნებს, იძახებს კალკულატორის სერვისს service discovery-ის საშუალებით და ინახავს გამოთვლების ისტორიას. ### 13.6.1 Discovery სერვერი შექმენით Spring Boot აპლიკაცია Eureka Server dependency-ით: ```xml org.springframework.cloud spring-cloud-starter-netflix-eureka-server ``` ```java @SpringBootApplication @EnableEurekaServer public class DiscoveryServerApplication { public static void main(String[] args) { SpringApplication.run(DiscoveryServerApplication.class, args); } } ``` ```properties server.port=8761 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false ``` Eureka Server არ არეგისტრირებს საკუთარ თავს (ის *არის* რეესტრი). გაუშვით ის პირველ რიგში — ის უზრუნველყოფს დაფას (dashboard) `http://localhost:8761` მისამართზე, სადაც შეგიძლიათ ნახოთ, რომელი სერვისებია დარეგისტრირებული. ### 13.6.2 კალკულატორის სერვისი შექმენით მეორე Spring Boot აპლიკაცია web starter-ით და Eureka კლიენტით: ```xml org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-netflix-eureka-client ``` ```properties spring.application.name=calculator-service server.port=8081 eureka.client.service-url.defaultZone=http://localhost:8761/eureka/ ``` ```java @RestController @RequestMapping("/math") public class CalculatorController { private static final Logger log = LoggerFactory.getLogger(CalculatorController.class); @GetMapping("/add") public int add(@RequestParam int a, @RequestParam int b) { log.info("calculator-service ამუშავებს: {} + {}", a, b); return a + b; } @GetMapping("/subtract") public int subtract(@RequestParam int a, @RequestParam int b) { log.info("calculator-service ამუშავებს: {} - {}", a, b); return a - b; } } ``` `@EnableEurekaClient` ანოტაცია საჭირო არ არის — Spring Boot ავტომატურად აკონფიგურირებს Eureka-ს კლიენტს, როდესაც dependency classpath-ზეა. `spring.application.name` property კრიტიკულია: ეს არის **ლოგიკური სახელი**, რომელსაც სხვა სერვისები იყენებენ ამ სერვისის მოსაძებნად. ### 13.6.3 Math Facade სერვისი შექმენით მესამე Spring Boot აპლიკაცია web starter-ით, Eureka კლიენტით და load balancer-ით: ```xml org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.cloud spring-cloud-starter-loadbalancer ``` ```properties spring.application.name=math-facade-service server.port=8082 eureka.client.service-url.defaultZone=http://localhost:8761/eureka/ ``` **RestClient-ის კონფიგურაცია** — ეს არის საკვანძო ინტეგრაციის წერტილი. იმისათვის, რომ `RestClient`-მა დაარეზოლვოს (resolve) Eureka-ს სერვისის სახელები (როგორიცაა `http://calculator-service`) მყარად ჩაწერილი (hardcoded) URL-ების ნაცვლად, `RestClient.Builder` უნდა იყოს `@LoadBalanced`: ```java @Configuration public class RestClientConfig { @Bean @LoadBalanced public RestClient.Builder restClientBuilder() { return RestClient.builder(); } @Bean public RestClient restClient(RestClient.Builder builder) { return builder.baseUrl("http://calculator-service").build(); } } ``` `@LoadBalanced` არის გადამწყვეტი ანოტაცია. მის გარეშე `http://calculator-service` აღქმული იქნებოდა როგორც ლიტერალური ჰოსტის სახელი და დასრულდებოდა DNS შეცდომით. ამ ანოტაციით, Spring Cloud LoadBalancer აკავებს (intercepts) მოთხოვნას, ეკითხება Eureka-ს `calculator-service`-ის ინსტანსების შესახებ და ამისამართებს მოთხოვნას ერთ-ერთ მათგანთან. **კონტროლერი (The controller):** ```java @RestController @RequestMapping("/api") public class FacadeController { private final RestClient restClient; private final List history = new CopyOnWriteArrayList<>(); public FacadeController(RestClient restClient) { this.restClient = restClient; } @GetMapping("/calculate") public String calculate(@RequestParam int a, @RequestParam int b, @RequestParam(defaultValue = "add") String op) { Integer result = restClient.get() .uri(uriBuilder -> uriBuilder .path("/math/" + op) .queryParam("a", a) .queryParam("b", b) .build()) .retrieve() .body(Integer.class); String record = String.format("%d %s %d = %d", a, op, b, result); history.add(record); return "შედეგი calculator-service-დან: " + record; } @GetMapping("/history") public List getHistory() { return history; } } ``` ### 13.6.4 სისტემის გაშვება გაუშვით სამივე აპლიკაცია შემდეგი თანმიმდევრობით: 1. **discovery-server** (დაელოდეთ რამდენიმე წამი მის ინიციალიზაციას). 2. **calculator-service**. 3. **math-facade-service**. შემოწმება (Verify): 1. გახსენით `http://localhost:8761` — Eureka-ს დაფაზე (dashboard) გამოჩნდება რეგისტრირებული `CALCULATOR-SERVICE` და `MATH-FACADE-SERVICE`. 2. გამოიძახეთ `http://localhost:8082/api/calculate?a=10&b=25` — ფასადი იძახებს კალკულატორს service discovery-ის გავლით და აბრუნებს შედეგს. 3. შეამოწმეთ calculator-service-ის კონსოლი — ის ალოგირებს (logs) მოთხოვნას, რაც ადასტურებს, რომ HTTP გამოძახებამ იმოგზაურა ორ ცალკეულ JVM პროცესს შორის. 4. გამოიძახეთ `http://localhost:8082/api/history` — ფასადი ინარჩუნებს თავის საკუთარ მდგომარეობას (state) დამოუკიდებლად. ეს არის მიკროსერვისების არსი: დამოუკიდებელი აპლიკაციები, რომლებიც ურთიერთობენ ქსელის მეშვეობით და დინამიურად პოულობენ ერთმანეთს რეესტრის გავლით. --- ## 13.7 API Gateway კალკულატორის მაგალითში, კლიენტი პირდაპირ იძახებს ფასადის სერვისს. რეალურ სისტემაში, სადაც ათობით სერვისია, თითოეული სერვისის URL-ის გახსნა კლიენტებისთვის არაპრაქტიკულია. **API Gateway** უზრუნველყოფს შემოსვლის ერთიან წერტილს (single entry point), რომელიც მოთხოვნებს ამისამართებს (routes) სწორ ბექენდ სერვისთან. Spring Cloud Gateway უზრუნველყოფს მარშრუტიზაციას (routing), დატვირთვის განაწილებას (load balancing), ავთენტიფიკაციას, მოთხოვნების სიხშირის შეზღუდვასა (rate limiting) და მოთხოვნების ტრანსფორმაციას: ```xml org.springframework.cloud spring-cloud-starter-gateway org.springframework.cloud spring-cloud-starter-netflix-eureka-client ``` ```yaml server: port: 8080 spring: cloud: gateway: routes: - id: calculator-service uri: lb://calculator-service predicates: - Path=/math/** - id: math-facade-service uri: lb://math-facade-service predicates: - Path=/api/** ``` `lb://` პრეფიქსი ეუბნება gateway-ს, გამოიყენოს Eureka სერვისების აღმოსაჩენად (service discovery) და მოახდინოს დატვირთვის განაწილება (load balance) ინსტანსებს შორის. კლიენტები ახლა აგზავნიან ყველა მოთხოვნას `http://localhost:8080`-ზე — gateway ამისამართებს `/math/` მოთხოვნებს კალკულატორის სერვისთან და `/api/` მოთხოვნებს ფასადის სერვისთან. --- ## 13.8 ცენტრალიზებული კონფიგურაცია მიკროსერვისების სისტემაში მრავალ სერვისზე `application.properties`-ის მართვა შეცდომების გამომწვევია. Spring Cloud Config უზრუნველყოფს კონფიგურაციის ცენტრალიზებულ სერვერს, რომელიც ინახავს კონფიგურაციას Git რეპოზიტორში და აწვდის მას ყველა სერვისს გაშვებისას (startup). ### Config სერვერი ```java @SpringBootApplication @EnableConfigServer public class ConfigServerApplication { public static void main(String[] args) { SpringApplication.run(ConfigServerApplication.class, args); } } ``` ```yaml server: port: 8888 spring: cloud: config: server: git: uri: https://github.com/your-org/config-repo default-label: main ``` ### Config კლიენტი თითოეულ სერვისს შემოაქვს თავისი კონფიგურაცია config სერვერიდან: ```yaml spring: application: name: calculator-service config: import: configserver:http://localhost:8888 ``` Config სერვერი აწვდის ფაილს სახელად `calculator-service.yml` Git რეპოზიტორიიდან. როდესაც კონფიგურაცია იცვლება, სერვისებს შეუძლიათ განახლება გადატვირთვის (restarting) გარეშე. ეს ინარჩუნებს გარემოსთვის სპეციფიკურ (environment-specific) ყველა მნიშვნელობას ერთ ადგილას, ვერსიის კონტროლისა და აუდიტის შესაძლებლობით — ეს იგივე სარგებელია, რაც მე-5 თავში განვიხილეთ properties ფაილებისთვის, თუმცა ამჯერად ცენტრალიზებულია მთელ სისტემაში. --- ## 13.9 მდგრადობა Circuit Breaker-ებით (Resilience with Circuit Breakers) განაწილებულ სისტემაში შეცდომები გარდაუვალია. Circuit breaker (ავტომატური ამომრთველი) ხელს უშლის კასკადურ შეცდომებს (cascading failures), აჩერებს რა გამოძახებებს გათიშული სერვისისკენ და უზრუნველყოფს სარეზერვო (fallback) პასუხს. Circuit breaker-ს აქვს სამი მდგომარეობა: **დახურული (closed)** (ნორმალური — მოთხოვნები გადის), **ღია (open)** (პრობლემური — მოთხოვნები დაუყოვნებლივ უარყოფილია fallback-ით მას შემდეგ, რაც შეცდომების ზღვარი მიიღწევა) და **ნახევრად ღია (half-open)** (აღდგენა — რამდენიმე სატესტო მოთხოვნა დაიშვება, რათა შემოწმდეს, აღდგა თუ არა სერვისი). ```xml org.springframework.cloud spring-cloud-starter-circuitbreaker-resilience4j ``` გამოიყენეთ circuit breaker ფასადის სერვისიდან კალკულატორის გამოძახებაზე: ```java @Service public class CalculatorClient { private final RestClient restClient; public CalculatorClient(RestClient restClient) { this.restClient = restClient; } @CircuitBreaker(name = "calculatorService", fallbackMethod = "addFallback") public int add(int a, int b) { return restClient.get() .uri(uriBuilder -> uriBuilder .path("/math/add") .queryParam("a", a) .queryParam("b", b) .build()) .retrieve() .body(Integer.class); } public int addFallback(int a, int b, Throwable throwable) { // აბრუნებს უსაფრთხო დეფოლტ (default) მნიშვნელობას, როდესაც კალკულატორის სერვისი მიუწვდომელია return 0; } } ``` დააკონფიგურირეთ circuit breaker-ის ქცევა `application.yml`-ში: ```yaml resilience4j: circuitbreaker: instances: calculatorService: sliding-window-size: 10 failure-rate-threshold: 50 wait-duration-in-open-state: 10s permitted-number-of-calls-in-half-open-state: 3 ``` ეს კონფიგურაცია ხსნის circuit-ს მას შემდეგ, რაც ბოლო 10 გამოძახებიდან 50% წარუმატებელი აღმოჩნდება, ელოდება 10 წამს სატესტო მოთხოვნების დაშვებამდე და უშვებს 3 სატესტო გამოძახებას ნახევრად ღია (half-open) მდგომარეობაში. თუ სატესტო გამოძახებები წარმატებულია, circuit იხურება და ნორმალური ტრაფიკი განახლდება. Resilience4j ასევე უზრუნველყოფს **retry**-ს (წარუმატებელი გამოძახებების ავტომატური გამეორება), **rate limiter**-ს (გამოძახების სიხშირის შეზღუდვა), **bulkhead**-ს (პარალელური გამოძახებების შეზღუდვა) და **time limiter**-ს (დაყოვნებების ანუ timeouts-ის დაყენება). ეს პატერნები (patterns) აუცილებელია განაწილებულ სისტემებში, სადაც ქსელური შეცდომები, ნელი სერვისები და რესურსების ამოწურვა რუტინას წარმოადგენს და არა იშვიათ გამონაკლისს. --- ## 13.10 სრული არქიტექტურა აი, როგორ გამოიყურება სრული მიკროსერვისების არქიტექტურა Spring Cloud-ის ყველა კომპონენტით: ``` ┌──────────────┐ │ Clients │ └──────┬───────┘ │ ┌──────┴───────┐ │ API Gateway │ │ (Spring Cloud│ │ Gateway) │ └──────┬───────┘ │ ┌─────────────────┼──────────────────┐ │ │ │ ┌──────┴──────┐ ┌──────┴──────┐ ┌───────┴──────┐ │User Service │ │Order Service │ │Product │ │(Spring Boot) │ │(Spring Boot) │ │Service │ └──────┬──────┘ └──────┬──────┘ └───────┬──────┘ │ │ │ │ ┌──────┴───────┐ │ │ │Message Broker│ │ │ │ (ActiveMQ) │ │ │ └──────────────┘ │ │ │ ┌──────┴──────┐ ┌───────┴──────┐ │Eureka Server│ │Config Server │ │ (Registry) │ │ (Git-based) │ └─────────────┘ └──────────────┘ ``` მოთხოვნის ნაკადი (request flow): კლიენტი აგზავნის მოთხოვნას **API Gateway**-ში, რომელიც ეკითხება **Eureka**-ს მიზნობრივი სერვისის აღმოსაჩენად და შესაბამისად ამისამართებს მას. სერვისები ერთმანეთს იძახებენ **RestClient**-ის (სინქრონული) ან **message broker**-ის (ასინქრონული) გამოყენებით. **Resilience4j** circuit breaker-ები იცავენ სისტემას კასკადური შეცდომებისგან. ყველა სერვისი იღებს თავის კონფიგურაციას **Config Server**-იდან გაშვების პროცესში. --- ## შეჯამება ამ თავში გაგაცანით მიკროსერვისების არქიტექტურა და Spring Cloud-ის ეკოსისტემა. **მონოლითი მიკროსერვისების წინააღმდეგ** არ არის არჩევანი კარგსა და ცუდს შორის. მონოლითები უფრო მარტივია, უფრო სწრაფია დეველოპმენტისთვის და უფრო მარტივად იტესტება. მიკროსერვისები უზრუნველყოფენ დამოუკიდებელ დეპლოიმენტს, დამოუკიდებელ მასშტაბირებას და შეცდომების იზოლაციას — განაწილებული სისტემის კომპლექსურობის ხარჯზე. დაიწყეთ მონოლითით; დაშალეთ მაშინ, როდესაც ამის საჭიროება აშკარაა. **სერვისის საზღვრები** მიჰყვება Domain-Driven Design-ის პრინციპებს: შემოსაზღვრულ კონტექსტებს (bounded contexts), ერთიან პასუხისმგებლობასა (single responsibility) და მონაცემთა ფლობას (data ownership). Strangler Fig Pattern საშუალებას იძლევა მოხდეს სისტემის ეტაპობრივი დაშლა. **სერვისთაშორისი კომუნიკაცია** არის ან სინქრონული (REST და `RestClient`) ან ასინქრონული (messaging JMS-ის, RabbitMQ-ის ან Kafka-ს მეშვეობით). სინქრონული უფრო მარტივია; ასინქრონული უფრო მდგრადია (resilient). სისტემების უმეტესობა იყენებს ორივეს. **Service Discovery** Eureka-ს გამოყენებით საშუალებას აძლევს სერვისებს დარეგისტრირდნენ და იპოვონ ერთმანეთი ლოგიკური სახელებით და არა მყარად ჩაწერილი (hardcoded) URL-ებით. `@LoadBalanced` ანოტაცია `RestClient.Builder`-ზე რთავს სერვისის სახელის რეზოლვინგს (resolution). **API Gateway** Spring Cloud Gateway-ით უზრუნველყოფს შემოსვლის ერთიან წერტილს კლიენტებისთვის, ამუშავებს მარშრუტიზაციას, დატვირთვის განაწილებას და სხვა გადამკვეთ ფუნქციონალებს (cross-cutting concerns). **ცენტრალიზებული კონფიგურაცია** Spring Cloud Config-ით ინახავს ყველა სერვისის კონფიგურაციას Git რეპოზიტორიაში, რაც უზრუნველყოფს მათ თანმიმდევრულ, ვერსიებზე კონტროლირებად მართვას. **Circuit Breakers** Resilience4j-ით ხელს უშლიან კასკადურ შეცდომებს პრობლემური სერვისებისკენ გამოძახებების შეჩერებით და სარეზერვო (fallback) პასუხების უზრუნველყოფით. **Spring Cloud 2025.1** არის ვერსია, რომელიც თავსებადია Spring Boot 4-სა და Spring Framework 7-თან. --- ## რესურსები * [Spring Cloud-ის ოფიციალური დოკუმენტაცია (Official Documentation)](https://spring.io/projects/spring-cloud) * [Spring Cloud Netflix Eureka](https://spring.io/projects/spring-cloud-netflix) * [Spring Cloud Gateway](https://spring.io/projects/spring-cloud-gateway) * [Spring Cloud Config](https://spring.io/projects/spring-cloud-config) * [Resilience4j დოკუმენტაცია](https://resilience4j.readme.io/) * [Microservices Patterns — კრის რიჩარდსონი (Chris Richardson)](https://microservices.io/patterns/) * [მარტინ ფაულერი (Martin Fowler): Microservices](https://martinfowler.com/articles/microservices.html) * [Baeldung: Spring Cloud სერია](https://www.baeldung.com/spring-cloud-series) --- ## ლაბორატორიული დავალება: დააპროექტეთ და ააშენეთ მინიმალური მიკროსერვისების სისტემა ამ კვირაში ფოკუსირება ხდება კონცეფციებსა და მომუშავე დემო-ვერსიაზე, ვიდრე სრულყოფილ საწარმოო (production) სისტემაზე. ლაბორატორიულ დავალებას აქვს ორი ნაწილი. ### ნაწილი 1: არქიტექტურის დიზაინი აირჩიეთ ერთ-ერთი შემდეგი აპლიკაციის სცენარი: ონლაინ წიგნის მაღაზია (მომხმარებლები, კატალოგი, შეკვეთები, მიმოხილვები, შეტყობინებები), საკვების მიტანის პლატფორმა (მომხმარებლები, რესტორნები, მენიუები, შეკვეთები, მიტანის თრექინგი) ან უნივერსიტეტის მართვის სისტემა (სტუდენტები, კურსები, ჩარიცხვა, ქულები, შეტყობინებები). თქვენ მიერ არჩეული სცენარისთვის: 1. **განსაზღვრეთ 4–5 მიკროსერვისი** და აღწერეთ თითოეულის პასუხისმგებლობა. 2. **დახატეთ არქიტექტურის დიაგრამა**, სადაც ნაჩვენები იქნება თითოეული სერვისი და მისი მონაცემთა ბაზა, API Gateway, სერვისების რეესტრი (Eureka) და კომუნიკაციის გზები — მონიშნეთ, რომელია სინქრონული REST და რომელი ასინქრონული messaging. 3. **განსაზღვრეთ 3–4 REST API ენდფოინთი (endpoint)** თითოეული სერვისისთვის (method, path, request/response). 4. **გამოავლინეთ მინიმუმ ერთი სცენარი**, სადაც ასინქრონული messaging უფრო შესაფერისია, ვიდრე სინქრონული REST და ახსენით რატომ. წარმოადგინეთ დიაგრამა და აღწერილობები დოკუმენტის სახით. ### ნაწილი 2: მინიმალური იმპლემენტაცია ააშენეთ ორი მინიმალური Spring Boot სერვისი, რომლებიც დაუკავშირდებიან ერთმანეთს REST-ის საშუალებით: 1. **Service A** (მაგ., Order Service) — ააშკარავებს (exposes) `POST /api/orders` ენდფოინთს. 2. **Service B** (მაგ., Product Service) — ააშკარავებს `GET /api/products/{id}` ენდფოინთს. როდესაც Service A იღებს მოთხოვნას შეკვეთაზე, ის იძახებს Service B-ს `RestClient`-ის გამოყენებით პროდუქტის დეტალების მისაღებად და შემდეგ აბრუნებს გაერთიანებულ პასუხს. **მოთხოვნები:** * ორი განცალკევებული Spring Boot პროექტი, თითოეული საკუთარი `pom.xml` და `application.properties` ფაილით. * თითოეული სერვისი ეშვება სხვადასხვა პორტზე. * Service A იძახებს Service B-ს `RestClient`-ის მეშვეობით. * შეცდომის სწორი დამუშავება (error handling), თუ Service B მიუწვდომელია. **ბონუსი:** * დაამატეთ Eureka Server და დაარეგისტრირეთ ორივე სერვისი მასზე. გამოიყენეთ ლოგიკური სერვისების სახელები მყარად ჩაწერილი (hardcoded) URL-ების ნაცვლად. * დაამატეთ circuit breaker Resilience4j-ის გამოყენებით Service A-ს მიერ Service B-ს გამოძახებისას, შესაბამისი სარეზერვო (fallback) მეთოდით.