- Added comprehensive course structure with 11 modules - Included theoretical content, practical examples, and tasks - Multi-language code examples (Java, Rust, Node.js, Python) - Detailed analysis of architectural approaches and trade-offs - Complete caching strategies documentation - Architecture examples from real-world systems (Twitter, Netflix, Uber) - Structured navigation and learning path - Ready for educational use
18 KiB
18 KiB
Коммуникация между сервисами
Навигация
Синхронная коммуникация
HTTP/REST
Преимущества:
- Простота
- Широкая поддержка
- Стандартизация
Недостатки:
- Связанность
- Блокирующие вызовы
- Проблемы с производительностью
Технический пример - HTTP/REST коммуникация:
// Order Service - клиент для Payment Service
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private PaymentServiceClient paymentServiceClient;
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
try {
// Синхронный вызов Payment Service
PaymentRequest paymentRequest = new PaymentRequest(
request.getAmount(),
request.getCurrency(),
request.getPaymentMethod(),
request.getPaymentDetails()
);
PaymentResult paymentResult = paymentServiceClient.processPayment(paymentRequest);
if (paymentResult.isSuccess()) {
// Создаем заказ только после успешной оплаты
Order order = orderService.createOrder(request, paymentResult.getTransactionId());
return ResponseEntity.ok(order);
} else {
return ResponseEntity.badRequest()
.body(new ErrorResponse("Payment failed: " + paymentResult.getErrorMessage()));
}
} catch (PaymentServiceException e) {
// Обработка ошибок коммуникации
return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
.body(new ErrorResponse("Payment service unavailable"));
}
}
}
// HTTP клиент для Payment Service
@Component
public class PaymentServiceClient {
@Autowired
private RestTemplate restTemplate;
private static final String PAYMENT_SERVICE_URL = "http://payment-service:8080";
public PaymentResult processPayment(PaymentRequest request) {
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<PaymentRequest> entity = new HttpEntity<>(request, headers);
ResponseEntity<PaymentResult> response = restTemplate.postForEntity(
PAYMENT_SERVICE_URL + "/api/payments/process",
entity,
PaymentResult.class
);
if (response.getStatusCode() == HttpStatus.OK) {
return response.getBody();
} else {
throw new PaymentServiceException("Payment service returned error: " + response.getStatusCode());
}
} catch (RestClientException e) {
throw new PaymentServiceException("Failed to communicate with payment service", e);
}
}
public PaymentResult refundPayment(String transactionId, BigDecimal amount) {
try {
RefundRequest refundRequest = new RefundRequest(transactionId, amount);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<RefundRequest> entity = new HttpEntity<>(refundRequest, headers);
ResponseEntity<RefundResult> response = restTemplate.postForEntity(
PAYMENT_SERVICE_URL + "/api/payments/refund",
entity,
RefundResult.class
);
if (response.getStatusCode() == HttpStatus.OK) {
RefundResult refundResult = response.getBody();
return new PaymentResult(
null,
refundResult.isSuccess() ? PaymentStatus.REFUNDED : PaymentStatus.FAILED,
transactionId,
refundResult.isSuccess() ? null : "Refund failed"
);
} else {
throw new PaymentServiceException("Refund service returned error: " + response.getStatusCode());
}
} catch (RestClientException e) {
throw new PaymentServiceException("Failed to communicate with refund service", e);
}
}
}
// Конфигурация RestTemplate с таймаутами и retry
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(5000); // 5 секунд на подключение
factory.setReadTimeout(10000); // 10 секунд на чтение
RestTemplate restTemplate = new RestTemplate(factory);
// Добавляем retry логику
restTemplate.setInterceptors(Arrays.asList(new RetryInterceptor()));
return restTemplate;
}
}
// Interceptor для retry логики
public class RetryInterceptor implements ClientHttpRequestInterceptor {
private static final int MAX_RETRIES = 3;
@Override
public ClientHttpResponse intercept(
HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution) throws IOException {
int retryCount = 0;
IOException lastException = null;
while (retryCount < MAX_RETRIES) {
try {
return execution.execute(request, body);
} catch (IOException e) {
lastException = e;
retryCount++;
if (retryCount < MAX_RETRIES) {
try {
Thread.sleep(1000 * retryCount); // Exponential backoff
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new IOException("Retry interrupted", ie);
}
}
}
}
throw lastException;
}
}
GraphQL
Преимущества:
- Гибкость запросов
- Типизация
- Единая точка входа
Недостатки:
- Сложность кэширования
- Потенциальные проблемы с производительностью
Асинхронная коммуникация
Очереди сообщений
Преимущества:
- Слабая связанность
- Отказоустойчивость
- Масштабируемость
Недостатки:
- Сложность отладки
- Потенциальные проблемы с порядком
Технический пример - асинхронная коммуникация с RabbitMQ:
// Order Service - публикует события
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private RabbitTemplate rabbitTemplate;
public Order createOrder(CreateOrderRequest request) {
// Создаем заказ
Order order = new Order();
order.setCustomerId(request.getCustomerId());
order.setItems(request.getItems());
order.setStatus(OrderStatus.CREATED);
order.setCreatedAt(LocalDateTime.now());
order = orderRepository.save(order);
// Публикуем событие о создании заказа
OrderCreatedEvent event = new OrderCreatedEvent(
order.getId(),
order.getCustomerId(),
order.getItems(),
order.getTotalAmount(),
order.getCreatedAt()
);
rabbitTemplate.convertAndSend("order.exchange", "order.created", event);
return order;
}
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException("Order not found"));
if (order.getStatus() == OrderStatus.CREATED) {
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
// Публикуем событие об отмене заказа
OrderCancelledEvent event = new OrderCancelledEvent(
order.getId(),
order.getCustomerId(),
order.getItems(),
order.getTotalAmount(),
LocalDateTime.now()
);
rabbitTemplate.convertAndSend("order.exchange", "order.cancelled", event);
}
}
}
// Notification Service - подписывается на события
@Component
public class OrderEventHandler {
@Autowired
private NotificationService notificationService;
@RabbitListener(queues = "order.created.queue")
public void handleOrderCreated(OrderCreatedEvent event) {
try {
// Отправляем подтверждение заказа
notificationService.sendOrderConfirmation(
event.getCustomerId(),
event.getOrderId(),
event.getTotalAmount()
);
// Отправляем уведомление о статусе заказа
notificationService.sendOrderStatusUpdate(
event.getCustomerId(),
event.getOrderId(),
"Your order has been created and is being processed"
);
} catch (Exception e) {
// Логируем ошибку, но не прерываем обработку
log.error("Failed to send order confirmation for order: " + event.getOrderId(), e);
}
}
@RabbitListener(queues = "order.cancelled.queue")
public void handleOrderCancelled(OrderCancelledEvent event) {
try {
// Отправляем уведомление об отмене
notificationService.sendOrderCancellation(
event.getCustomerId(),
event.getOrderId(),
event.getTotalAmount()
);
} catch (Exception e) {
log.error("Failed to send order cancellation for order: " + event.getOrderId(), e);
}
}
}
// Inventory Service - подписывается на события
@Component
public class InventoryEventHandler {
@Autowired
private InventoryService inventoryService;
@RabbitListener(queues = "order.created.queue")
public void handleOrderCreated(OrderCreatedEvent event) {
try {
// Резервируем товары
for (OrderItem item : event.getItems()) {
inventoryService.reserveItem(item.getProductId(), item.getQuantity());
}
// Публикуем событие о резервировании
InventoryReservedEvent inventoryEvent = new InventoryReservedEvent(
event.getOrderId(),
event.getItems(),
LocalDateTime.now()
);
rabbitTemplate.convertAndSend("inventory.exchange", "inventory.reserved", inventoryEvent);
} catch (InsufficientStockException e) {
// Если товаров недостаточно, отменяем заказ
OrderCancelledEvent cancelEvent = new OrderCancelledEvent(
event.getOrderId(),
event.getCustomerId(),
event.getItems(),
event.getTotalAmount(),
LocalDateTime.now()
);
rabbitTemplate.convertAndSend("order.exchange", "order.cancelled", cancelEvent);
} catch (Exception e) {
log.error("Failed to reserve inventory for order: " + event.getOrderId(), e);
}
}
@RabbitListener(queues = "order.cancelled.queue")
public void handleOrderCancelled(OrderCancelledEvent event) {
try {
// Освобождаем зарезервированные товары
for (OrderItem item : event.getItems()) {
inventoryService.releaseReservation(item.getProductId(), item.getQuantity());
}
} catch (Exception e) {
log.error("Failed to release inventory for order: " + event.getOrderId(), e);
}
}
}
// Конфигурация RabbitMQ
@Configuration
@EnableRabbit
public class RabbitMQConfig {
// Exchange для заказов
@Bean
public TopicExchange orderExchange() {
return new TopicExchange("order.exchange");
}
// Очереди для событий заказов
@Bean
public Queue orderCreatedQueue() {
return new Queue("order.created.queue", true);
}
@Bean
public Queue orderCancelledQueue() {
return new Queue("order.cancelled.queue", true);
}
// Привязки очередей к exchange
@Bean
public Binding orderCreatedBinding() {
return BindingBuilder
.bind(orderCreatedQueue())
.to(orderExchange())
.with("order.created");
}
@Bean
public Binding orderCancelledBinding() {
return BindingBuilder
.bind(orderCancelledQueue())
.to(orderExchange())
.with("order.cancelled");
}
// Exchange для инвентаря
@Bean
public TopicExchange inventoryExchange() {
return new TopicExchange("inventory.exchange");
}
@Bean
public Queue inventoryReservedQueue() {
return new Queue("inventory.reserved.queue", true);
}
@Bean
public Binding inventoryReservedBinding() {
return BindingBuilder
.bind(inventoryReservedQueue())
.to(inventoryExchange())
.with("inventory.reserved");
}
// Конфигурация RabbitTemplate
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
template.setMessageConverter(new Jackson2JsonMessageConverter());
return template;
}
// Конфигурация Jackson для сериализации
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
}
// События (DTO)
public class OrderCreatedEvent {
private Long orderId;
private Long customerId;
private List<OrderItem> items;
private BigDecimal totalAmount;
private LocalDateTime createdAt;
// конструкторы, геттеры, сеттеры
}
public class OrderCancelledEvent {
private Long orderId;
private Long customerId;
private List<OrderItem> items;
private BigDecimal totalAmount;
private LocalDateTime cancelledAt;
// конструкторы, геттеры, сеттеры
}
public class InventoryReservedEvent {
private Long orderId;
private List<OrderItem> items;
private LocalDateTime reservedAt;
// конструкторы, геттеры, сеттеры
}
Потоковая обработка событий
Преимущества:
- Высокая пропускная способность
- Исторические данные
- Real-time обработка
Технологии:
- Apache Kafka
- Apache Pulsar
- AWS Kinesis
Паттерны коммуникации
1. Request-Response
Client → Service A → Service B
Client ← Service A ← Service B
2. Publish-Subscribe
Publisher → Event Bus → Subscriber 1
→ Subscriber 2
→ Subscriber 3
3. Request-Reply
Client → Message Queue → Service
Client ← Message Queue ← Service
4. Event Sourcing
Command → Aggregate → Event Store
Query → Read Model ← Event Store
Обнаружение сервисов
Проблема: Как сервисы находят друг друга в динамической среде?
Решения:
1. Client-Side Discovery
@Service
public class PaymentServiceClient {
@Autowired
private ServiceDiscovery serviceDiscovery;
public PaymentResult processPayment(PaymentRequest request) {
// клиент сам находит сервис
ServiceInstance instance = serviceDiscovery.getInstance("payment-service");
String url = "http://" + instance.getHost() + ":" + instance.getPort();
return restTemplate.postForObject(url + "/payments", request, PaymentResult.class);
}
}
2. Server-Side Discovery
@Service
public class PaymentServiceClient {
public PaymentResult processPayment(PaymentRequest request) {
// сервис находится через load balancer
String url = "http://payment-service/payments";
return restTemplate.postForObject(url, request, PaymentResult.class);
}
}
Инструменты:
- Consul — service discovery и configuration
- Eureka — Netflix service discovery
- Kubernetes — встроенный service discovery