Розподіл відповідальності між запитами та командами (CQRS) у середовищі мікросервісів

текст перекладу

Вступ

У середовищі мікросервісів CQRS (Command Query Responsibility Segregation) є архітектурним патерном, що розділяє операції читання та запису в сервісі на різні моделі. Це особливо корисно в системах, що обробляють складні операції або потребують високої масштабованості та продуктивності. Розділяючи команди (записи/оновлення) від запитів (читання), можна оптимізувати систему для високої пропускної здатності при записах та низької затримки при читаннях.

pic

1. Чому використовувати CQRS у мікросервісах?

Виклики традиційних CRUD моделей

У традиційних системах на основі CRUD:

  • Операції читання та запису використовують одну й ту ж модель даних, що може призводити до неефективності.
  • Масштабування читання та запису незалежно є складним.
  • Одночасні оновлення можуть спричинити конкуренцію і невідповідності.
  • Продуктивність знижується при обробці складних запитів та великих обсягів даних.

Рішення CQRS:

  • Розділення моделей команд та запитів:
    • Команда: Обробляє операції створення, оновлення та видалення.
    • Запит: Обробляє операції читання.
  • Незалежні сховища даних: Команди та запити можуть використовувати різні бази даних, оптимізовані під свої конкретні завдання.
  • Завершена консистентність: Моделі читання та запису не завжди можуть бути синхронізовані миттєво, але вони сходяться з часом.

2. Компоненти CQRS

Команди (Модель запису)

  • Обробляють операції, що змінюють стан (створення, оновлення, видалення).
  • Можуть включати логіку валідації.
  • Зазвичай публікують події домену для інформування інших сервісів про зміни стану.

Запити (Модель читання)

  • Обробляють тільки операції читання.
  • Зазвичай оптимізовані для швидкого отримання даних за допомогою денормалізованих представлень або кешування.
  • Можуть обслуговуватись спеціалізованими базами даних для читання.

3. Потік CQRS у середовищі мікросервісів

Ось як зазвичай працює CQRS у середовищі мікросервісів:

  1. Команда: Клієнт надсилає запит на оновлення даних (наприклад, "створити замовлення").
  2. Сервіс запису: Сервіс, який обробляє команду, обробляє запит і оновлює базу даних.
  3. Публікація події: Після успішного оновлення публікується подія (наприклад, "OrderPlacedEvent") до брокера повідомлень (Kafka, RabbitMQ).
  4. Обробка події та оновлення для читання:
  • Сервіс читання (або окремий мікросервіс) підписується на подію.
  • Модель читання оновлюється (можливо, в іншій базі даних).

5. Запит: Клієнт надсилає запит на читання даних (наприклад, "отримати статус замовлення").

6. Сервіс читання: Сервіс читання отримує та повертає останні дані.

4. Приклад CQRS у мікросервісах на Java

Варіант використання: Керування замовленнями в електронній комерції

1. Залежності проекту

Додайте наступні залежності до build.gradle або pom.xml:

implementation 'org.springframework.boot:spring-boot-starter-web'  
implementation 'org.springframework.kafka:spring-kafka'  
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'  
implementation 'org.springframework.data:spring-data-redis'  
implementation 'org.springframework.boot:spring-boot-starter-cache'

2. Шар команд (Сервіс запису)

OrderCommandService.java

package com.atk.cqrs.command;  

import org.springframework.kafka.core.KafkaTemplate;  
import org.springframework.stereotype.Service;  

@Service  
public class OrderCommandService {  

 private final KafkaTemplate kafkaTemplate;  

 public OrderCommandService(KafkaTemplate kafkaTemplate) {  
 this.kafkaTemplate = kafkaTemplate;  
 }  

 public void placeOrder(String orderId, double amount) {  
 // Зберігаємо в базі даних для запису  
 System.out.println("Замовлення розміщено: " + orderId);  

 // Публікуємо OrderPlacedEvent  
 kafkaTemplate.send("order-events", "Замовлення розміщено для ID: " + orderId);  
 }  
}

текст перекладу
## Шар запитів (Сервіс читання)

## OrderQueryService.java

package com.atk.cqrs.query;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class OrderQueryService {

@Cacheable("orders")
public String getOrderStatus(String orderId) {
// У реальному світі отримуємо з оптимізованої для читання бази даних або кешу
System.out.println("Отримання статусу замовлення для: " + orderId);
return "Статус замовлення для " + orderId + ": Очікується";
}
}
```

4. Шар контролера

OrderController.java

package com.atk.cqrs.controller;  

import com.atk.cqrs.command.OrderCommandService;  
import com.atk.cqrs.query.OrderQueryService;  
import org.springframework.web.bind.annotation.*;  

@RestController  
@RequestMapping("/orders")  
public class OrderController {  

 private final OrderCommandService orderCommandService;  
 private final OrderQueryService orderQueryService;  

 public OrderController(OrderCommandService orderCommandService, OrderQueryService orderQueryService) {  
 this.orderCommandService = orderCommandService;  
 this.orderQueryService = orderQueryService;  
 }  

 @PostMapping  
 public String placeOrder(@RequestParam String orderId, @RequestParam double amount) {  
 orderCommandService.placeOrder(orderId, amount);  
 return "Замовлення успішно розміщено!";  
 }  

 @GetMapping("/{orderId}")  
 public String getOrderStatus(@PathVariable String orderId) {  
 return orderQueryService.getOrderStatus(orderId);  
 }  
}

5. Проектування бази даних для CQRS

  • База даних для запису: Оптимізована для записів, зазвичай нормалізована (наприклад, PostgreSQL, MySQL).
  • База даних для читання: Оптимізована для читання, зазвичай денормалізована (наприклад, Redis, Elasticsearch, MongoDB).

Приклад:

  • Модель запису: Зберігає детальні записи замовлень з нормалізованими зв'язками.
  • Модель читання: Зберігає зведені дані для швидкого доступу (наприклад, тільки ID замовлення та статус).

6. Пропагування подій (за допомогою Kafka)

Kafka виступає як потік подій для пропагування подій домену.

  • Продюсер: Сервіс запису публікує події (OrderPlacedEvent).
  • Споживач: Сервіс читання підписується на події та оновлює свою базу даних для читання.

Конфігурація Kafka

spring:  
 kafka:  
 bootstrap-servers: localhost:9092  
 consumer:  
 group-id: "order-group"  
 auto-offset-reset: earliest

7. Переваги CQRS у мікросервісах

  1. Незалежне масштабування: Масштабування шарів для читання та запису незалежно.
  2. Оптимізація продуктивності: Використання окремих моделей даних і баз даних, оптимізованих для конкретних завдань.
  3. Покращена підтримка: Розділення читання і запису спрощує проектування сервісів.
  4. Повторне відтворення подій: Можливість повторної обробки подій, якщо моделі читання потребують відновлення.

8. Виклики CQRS

  1. Збільшена складність: Потрібно управляти кількома моделями даних та потоками подій.
  2. Завершена консистентність: Дані в моделі для читання можуть відставати від моделі для запису.
  3. Обробка збоїв: Потрібно надійно обробляти помилки та повторно відправляти події для пропагування.

9. Коли використовувати CQRS

CQRS корисний у таких сценаріях:

  • Застосунки з високими навантаженнями на читання/запис, які потребують незалежного масштабування.
  • Системи з складними запитами для читання.
  • Системи, які потребують обробки завершеної консистентності.

Висновок

Патерн CQRS — потужний підхід до досягнення масштабованості, підтримуваності та продуктивності в середовищі мікросервісів. Розділяючи моделі читання і запису, ви можете проектувати свої сервіси для обробки високого потоку запитів та надання даних з низькою затримкою. Хоча він вносить додаткову складність, CQRS окупається в системах великого масштабу, де важливі гнучкість та стійкість до збоїв. Ви можете розширити приклад, додавши кешування Redis для швидших запитів, споживачів подій Kafka для створення переглядів для читання або Elasticsearch для повнотекстових запитів. Все залежить від вашої уяви. 🍕

Перекладено з: Command Query Responsibility Segregation (CQRS) in Microservices

Leave a Reply

Your email address will not be published. Required fields are marked *