Skip to content

Microservices #38

Description

@abzalimovrrr

Microservices — Архитектура микросервисов: вопросы, практические задания и ответы с кодом

100 вопросов по архитектуре микросервисов — с практическими заданиями, примерами кода и комментариями.


Темы

РазделОписание
1–10Основы микросервисовЧто такое микросервисы, vs монолит, принципы, Conway's Law, bounded context, DDD, event storming, granularity, database per service, API composition
11–20Межсервисное взаимодействиеHTTP/REST, gRPC, GraphQL, RabbitMQ, Kafka, sync vs async, circuit breaker, retry+backoff, bulkhead, timeout
21–30API GatewayKong, Traefik, Nginx, rate limiting, auth, routing, request transformation, response aggregation, versioning, canary routing
31–40Service DiscoveryDNS-based, Consul, etcd, Kubernetes DNS, client-side, server-side, health checks, registry pattern, self-registration, third-party, Istio
41–50КонфигурацияSpring Cloud Config, Consul KV, etcd, environment-based, externalized config, Vault, feature flags, ConfigMap, hot reload, форматы
51–60ObservabilityJaeger tracing, OpenTelemetry, correlation ID, structured logging, ELK/Loki, Prometheus metrics, RED, USE, health API, status pages
61–70SecurityOAuth2/JWT, OIDC, mTLS, API keys, service account, RBAC, Zero Trust, SPIFFE/SPIRE, secret rotation, security scanning
71–80Data ManagementSaga choreography, Saga orchestration, CQRS, event sourcing, shared DB anti-pattern, eventual consistency, idempotency, outbox, CDC, 2PC vs Saga
81–90TestingPact contract testing, provider tests, Testcontainers, WireMock, E2E tests, canary testing, chaos engineering, Gatling, consumer-driven contracts
91–100Deploy & ProductionCI/CD per service, blue-green, canary release, feature flags, A/B testing, rolling update, Terraform, Istio service mesh, K8s operators, ArgoCD GitOps

Вопросы и ответы

ВопросКод с комментариями
1Что такое микросервисы?
# Микросервисы — архитектурный стиль, где приложение состоит из независимо развертываемых сервисов
# Каждый сервис имеет свою бизнес-способность
# Преимущества: масштабирование, независимый деплой, технологическое разнообразие
2Микросервисы vs монолит — сравнение.
# Монолит: единый процесс, общая БД, простота разработки
# Микросервисы: N процессов, database per service, сложность сетевого взаимодействия
# Когда выбрать микросервисы: >10 разработчиков, >6 месяцев, разные домены
3Принципы микросервисов.
# Single Responsibility — один сервис = одна бизнес-способность
# Loose Coupling — слабая связанность через API
# High Cohesion — высокая связность внутри сервиса
# Smart endpoints, dumb pipes — смысл на концах, транспорт простой
4Conway's Law в микросервисах.
# Organizations design systems that mirror their communication structure
# Команды должны быть выровнены по границам сервисов
# 2-pizza teams: ~6-8 человек на сервис
# Обратный закон Конвея: архитектура формирует организацию
5Bounded Context (DDD).
# Из Domain-Driven Design
# Bounded context = границы модели
# Пример: Order Context vs Payment Context vs Shipping Context
# Внутри контекста — единый язык (Ubiquitous Language)
# Контексты общаются через события или API
6Domain-Driven Design для микросервисов.
# Шаги:
# 1. Event Storming — найти доменные события
# 2. Aggregate — кластеры связанных сущностей
# 3. Bounded Context — границы
# 4. Context Map — карта взаимодействий
# 5. Выделить микросервисы по агрегатам
7Event Storming практика.
# Формат: большая стена, стикеры
# Оранжевые: Events (OrderPlaced, PaymentReceived)
# Синие: Commands (PlaceOrder, ProcessPayment)
# Зеленые: Aggregates (Order, Payment, Inventory)
# Красные: External Systems (PaymentGateway, Shipment)
# Результат: границы микросервисов
8Service granularity — детализация микросервисов.
# Слишком мелкие: DevOps ад, сетевая задержка
# Слишком крупные: почти монолит
# Правило 1: один сервис = одна бизнес-сущность
# Правило 2: команда должна комфортно поддерживать
# Правило 3: сервис должен быть заменяем за 2 недели
# Правило 4: не пересекать bounded contexts
9Database per service принцип.
# Каждый сервис владеет своей БД
# Никакой другой сервис не имеет прямого доступа
# Доступ только через API сервиса-владельца
# Свобода выбора технологии: Postgres, Mongo, Redis
# Проблема: распределенные транзакции (Saga)
10API Composition паттерн.
# Композиция данных из нескольких сервисов
@RestController
public class OrderCompositionController {
    @GetMapping("/api/v1/orders/{id}")
    public OrderComposite getOrder(@PathVariable String id) {
        Order order = orderClient.getOrder(id);
        Payment payment = paymentClient.getByOrderId(id);
        Shipping shipping = shippingClient.getByOrderId(id);
        return new OrderComposite(order, payment, shipping);
    }
}
11HTTP/REST взаимодействие.
# REST — Representational State Transfer
@FeignClient(name = "user-service")
public interface UserClient {
    @GetMapping("/api/users/{id}")
    User getUser(@PathVariable Long id);
}
12gRPC между сервисами.
# gRPC: protobuf, HTTP/2, бинарный протокол
syntax = "proto3";
service UserService {
    rpc GetUser (GetUserRequest) returns (User);
    rpc ListUsers (ListUsersRequest) returns (stream User);
}
message User { string id = 1; string name = 2; string email = 3; }
# Преимущества: быстрее REST, типизация, streaming, codegen
13GraphQL для микросервисов.
# GraphQL — единый endpoint, клиент выбирает поля
schema { query: Query }
type Query { order(id: ID!): Order }
type Order { id: ID! user: User payment: Payment }
# Одна схема, несколько resolvers к разным сервисам
14Message Broker — RabbitMQ.
# RabbitMQ — AMQP брокер сообщений
@Configuration
public class RabbitConfig {
    @Bean public Queue orderQueue() { return new Queue("order.created", true); }
    @Bean public TopicExchange exchange() { return new TopicExchange("order.exchange"); }
    @Bean public Binding binding(Queue q, TopicExchange e) {
        return BindingBuilder.bind(q).to(e).with("order.*"); }
}
15Kafka для событий.
# Kafka — распределенный event streaming
@Autowired private KafkaTemplate<String, OrderEvent> kafka;
public void publishOrderCreated(Order order) {
    OrderEvent event = new OrderEvent(order.getId(), order.getStatus());
    kafka.send("order.events", order.getId(), event);
}
@KafkaListener(topics = "order.events", groupId = "payment-service")
public void handleOrderEvent(OrderEvent event) {
    log.info("Received: {}", event);
}
16Synchronous vs Asynchronous.
# Synchronous: HTTP/REST, gRPC — проще, ожидаем ответ, блокируется
# Asynchronous: RabbitMQ, Kafka — сложнее, eventual consistency, лучше изоляция
# Правило: синхронно для запросов, асинхронно для событий
17Circuit Breaker паттерн.
# Circuit Breaker: CLOSED -> OPEN (после ошибок) -> HALF_OPEN -> CLOSED
@CircuitBreaker(name = "userService", fallbackMethod = "getDefaultUser")
public User getUser(String id) { return userClient.getUser(id); }
public User getDefaultUser(String id, Throwable t) {
    log.warn("Fallback for user {}", id, t);
    return new User(id, "unknown", "N/A");
}
18Retry with exponential backoff.
# Retry с экспоненциальной задержкой
@Retry(name = "paymentService", fallbackMethod = "paymentFallback",
       maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2.0))
public PaymentResponse processPayment(PaymentRequest req) {
    return paymentClient.charge(req);
}
# Задержки: 1s, 2s, 4s + jitter для избежания Thundering Herd
19Bulkhead паттерн.
# Bulkhead — изоляция потоков/connection pool по сервисам
@Bulkhead(name = "paymentService", type = Type.THREADPOOL,
          threadPoolSize = 4, queueCapacity = 10)
public CompletableFuture<Payment> processPayment(PaymentRequest req) {
    return CompletableFuture.supplyAsync(() -> paymentService.charge(req));
}
20Timeout management.
# Таймауты — критичны в микросервисах
@Bean
public RestTemplate restTemplate() {
    return new RestTemplateBuilder()
        .connectTimeout(Duration.ofSeconds(2))
        .readTimeout(Duration.ofSeconds(5))
        .build();
}
# Правило: таймаут должен быть < 99-го процентиля ответа
21Kong Gateway конфигурация.
# Kong — OpenSource API Gateway
curl -i -X POST http://localhost:8001/services \
  -H "Content-Type: application/json" \
  -d '{"name":"user-service","url":"http://user-svc:8080"}'
curl -i -X POST http://localhost:8001/services/user-service/routes \
  -d '{"paths":["/api/users"]}'
# Rate limiting:
curl -X POST http://localhost:8001/routes/user-route/plugins \
  -d '{"name":"rate-limiting","config":{"minute":100,"hour":1000}}'
22Traefik Gateway конфигурация.
# Traefik — cloud-native reverse proxy
services:
  traefik:
    image: traefik:v2.10
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
    ports:
      - "80:80"
  user-service:
    image: user-service
    labels:
      - "traefik.http.routers.users.rule=PathPrefix(`/api/users`)""
      - "traefik.http.services.users.loadbalancer.server.port=8080"
23Nginx как API Gateway.
# Nginx как API Gateway
upstream user-service {
    server user-app-1:8080 weight=3;
    server user-app-2:8080 weight=2;
    server user-app-3:8080 backup;
}
server {
    listen 80;
    location /api/users {
        proxy_pass http://user-service;
        proxy_set_header X-Request-ID $request_id;
        limit_req zone=apilimit burst=20 nodelay;
    }
}
24Rate Limiting per service.
# Rate Limiting — защита от перегрузок
# Bucket4j Java пример:
@Bean
public Map<String, Bucket> buckets() {
    Map<String, Bucket> buckets = new HashMap<>();
    buckets.put("user-service", Bucket.builder()
        .addLimit(Bandwidth.simple(1000, Duration.ofMinutes(1)))
        .build());
    return buckets;
}
@GetMapping("/api/users/{id}")
public User getUser(@PathVariable String id,
                    @RequestHeader("X-Api-Key") String apiKey) {
    Bucket bucket = buckets.get(apiKey);
    if (bucket != null && bucket.tryConsume(1)) return userService.getUser(id);
    throw new RateLimitExceededException();
}
25Auth at Gateway — аутентификация на шлюзе.
# Spring Cloud Gateway JWT:
@Bean
public SecurityWebFilterChain gatewaySecurity(ServerHttpSecurity http) {
    http.oauth2ResourceServer().jwt();
    http.authorizeExchange()
        .pathMatchers("/api/public/**").permitAll()
        .pathMatchers("/api/users/**").hasRole("USER")
        .anyExchange().authenticated();
    return http.build();
}
26Routing at Gateway — маршрутизация.
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates: [Path=/api/users/**]
          filters: [StripPrefix=1, AddRequestHeader=X-Gateway, true]
27Request Transformation — трансформация запросов.
@Bean
public RouteLocator customRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("user-service", r -> r
            .path("/api/v1/**")
            .filters(f -> f
                .stripPrefix(2)
                .addRequestHeader("X-Internal", "true")
                .removeRequestHeader("X-Original-Token")
                .retry(3))
            .uri("lb://user-service"))
        .build();
}
28Response Aggregation — агрегация ответов.
@RestController
public class AggregationController {
    @Autowired private WebClient webClient;
    @GetMapping("/api/dashboard/{userId}")
    public Mono<Dashboard> getDashboard(@PathVariable String userId) {
        Mono<User> user = webClient.get()
            .uri("http://user-service/api/users/" + userId)
            .retrieve().bodyToMono(User.class);
        Mono<List<Order>> orders = webClient.get()
            .uri("http://order-service/api/orders?userId=" + userId)
            .retrieve().bodyToFlux(Order.class).collectList();
        return Mono.zip(user, orders, Dashboard::new);
    }
}
29API Versioning at Gateway.
# Стратегии: URL (/v1/), Header (Accept: vnd.v1), Query (?version=1)
spring:
  cloud:
    gateway:
      routes:
        - id: user-v1
          uri: lb://user-service-v1
          predicates: [Path=/api/v1/users/**]
          filters: [StripPrefix=1]
30Canary Routing — канареечное развертывание.
# Istio VirtualService canary:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: user-service
spec:
  hosts: [user-service]
  http:
  - route:
    - destination:
        host: user-service
        subset: v1
      weight: 95
    - destination:
        host: user-service
        subset: v2
      weight: 5
31DNS-based Service Discovery.
# DNS-based discovery: контейнеры регистрируют A/SRV записи
# Используется в Kubernetes CoreDNS: service.namespace.svc.cluster.local
# dig SRV user-service.svc.cluster.local
# Просто, но нет health checks, долгое обновление TTL
32Consul Service Discovery.
# Consul — service registry + health checks + KV store
{
  "service": {
    "name": "user-service",
    "tags": ["api", "v1"],
    "port": 8080,
    "check": {
      "http": "http://localhost:8080/actuator/health",
      "interval": "10s",
      "timeout": "2s"
    }
  }
}
# curl http://consul:8500/v1/health/service/user-service?passing=true
33etcd как registry.
# etcd — распределенный key-value store для discovery
etcdctl put /services/user-service/instance-1 '{"address":"10.0.0.1","port":8080}' --lease 30s
etcdctl watch /services/user-service/ --prefix
# Go клиент:
resp, err := c.Get(ctx, "/services/"+name, clientv3.WithPrefix())
34Kubernetes DNS для discovery.
# Kubernetes DNS: service.namespace.svc.cluster.local
apiVersion: v1
kind: Service
metadata:
  name: user-service
spec:
  selector:
    app: user-service
  ports:
  - port: 80
    targetPort: 8080
  clusterIP: None  # headless — DNS возвращает все IP подов
35Client-side discovery.
@LoadBalanced
@Bean
public RestTemplate restTemplate() { return new RestTemplate(); }
@Autowired private RestTemplate restTemplate;
public User getUser(String id) {
    return restTemplate.getForObject("http://user-service/api/users/" + id, User.class);
}
36Server-side discovery.
# Балансировщик между клиентом и сервисами
apiVersion: v1
kind: Service
metadata:
  name: user-service
spec:
  type: LoadBalancer
  selector: {app: user-service}
  ports:
  - port: 80
    targetPort: 8080
# Плюсы: клиент ничего не знает о discovery
37Health Checks для сервисов.
# Spring Boot Actuator:
management:
  endpoints:
    web:
      exposure:
        include: health,info
  health:
    readinessstate: {enabled: true}
    livenessstate: {enabled: true}
# Liveness: сервис жив?  Readiness: готов принимать трафик?
38Registry Pattern — Self Registration.
@SpringBootApplication
@EnableEurekaClient
public class UserServiceApplication {}
# application.yml:
eureka:
  client:
    serviceUrl:
      defaultZone: http://eureka:8761/eureka/
  instance:
    lease-renewal-interval-in-seconds: 10
    lease-expiration-duration-in-seconds: 30
39Third-Party Registration.
# Registrator — следит за Docker событиями
docker run -d --name registrator --net host \
  -v /var/run/docker.sock:/tmp/docker.sock \
  gliderlabs/registrator:latest consul://localhost:8500
# Контейнеры с SERVICE_ переменными регистрируются автоматически
docker run -d -e "SERVICE_NAME=user-service" myapp
40Service Mesh Discovery (Istio).
# В Istio discovery через Pilot + xDS протокол
# Pilot получает данные из K8s registry и отправляет Envoy
# ServiceEntry для внешних сервисов:
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
  name: external-db
spec:
  hosts: [db.example.com]
  ports:
  - number: 5432
    name: tcp
    protocol: TCP
  resolution: DNS
41Spring Cloud Config Server.
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {}
# application.yml:
spring:
  cloud:
    config:
      server:
        git:
          uri: https://github.com/org/config-repo
          default-label: main
          search-paths: '{application}'
server:
  port: 8888
42Consul KV для конфигурации.
# Consul KV — распределенное key-value хранилище
consul kv put config/user-service/production/db/host db.example.com
consul kv put config/user-service/production/db/port 5432
# Spring Cloud Consul Config:
spring:
  cloud:
    consul:
      config:
        enabled: true
        prefixes: config
        default-context: application
43etcd для конфигурации.
# etcd — распределенный key-value
etcdctl put /config/user-service/db.host "db.example.com"
etcdctl put /config/user-service/db.port "5432"
etcdctl watch /config/user-service/ --prefix
44Environment-based конфигурация.
# Параметры переопределяются переменными окружения
server:
  port: ${PORT:8080}
spring:
  datasource:
    url: ${DB_URL:jdbc:h2:mem:test}
    username: ${DB_USER:sa}
    password: ${DB_PASS:}
45Externalized Configuration.
# 12-factor app: config в переменных окружения
apiVersion: v1
kind: ConfigMap
metadata:
  name: user-service-config
data:
  application.yml: |
    server:
      port: 8080
    feature-flags:
      new-checkout: true
46Secrets Management (HashiCorp Vault).
# Vault — централизованное управление секретами
vault kv put secret/user-service/db username=admin password=s3cret!
# Spring Cloud Vault:
spring:
  cloud:
    vault:
      host: vault.example.com
      port: 8200
      scheme: https
      authentication: TOKEN
      token: ${VAULT_TOKEN}
      kv:
        enabled: true
        backend: secret
        default-context: user-service
47Feature Flags (LaunchDarkly).
import com.launchdarkly.sdk.LDUser;
import com.launchdarkly.client.LDClient;
LDClient client = new LDClient("sdk-key-123");
LDUser user = new LDUser.Builder("user-1").build();
boolean newCheckout = client.boolVariation("new-checkout-flow", user, false);
if (newCheckout) { return processNewCheckout(); }
else { return processLegacyCheckout(); }
48ConfigMap в Kubernetes.
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  APP_ENV: "production"
  LOG_LEVEL: "warn"
  CACHE_TTL_SECONDS: "3600"
# Pod использует configMapRef для envFrom
49Reload конфигурации без перезапуска.
# Spring Cloud Bus + RabbitMQ
# POST /actuator/busrefresh/config-server
# Config Server публикует RefreshRemoteApplicationEvent
@RestController
public class ConfigReloadController {
    @Autowired private ContextRefresher refresher;
    @PostMapping("/reload")
    public Set<String> reload() { return refresher.refresh(); }
}
50Файлы конфигурации — форматы.
# YAML — читаемый, сложные структуры
service:
  name: user-service
  port: 8080
# JSON — строгий, универсальный
{"service": {"name": "user-service", "port": 8080}}
# TOML — простой, для .env-like
[service]
name = "user-service"
port = 8080
51Distributed Tracing (Jaeger).
# OpenTelemetry + Jaeger:
otel:
  service: user-service
  traces:
    exporter: jaeger
    jaeger:
      endpoint: http://jaeger:14250
      protocol: grpc
@WithSpan("get-user")
public User getUser(String id) {
    Span span = Span.current();
    span.setAttribute("user.id", id);
    span.addEvent("fetching from DB");
    return userRepository.findById(id);
}
52OpenTelemetry — стандарт observability.
# OpenTelemetry Collector:
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
processors:
  batch:
    timeout: 1s
    send_batch_size: 1024
exporters:
  jaeger:
    endpoint: jaeger:14250
  prometheus:
    endpoint: 0.0.0.0:8889
53Logging Correlation ID.
@Component
public class CorrelationFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String correlationId = exchange.getRequest().getHeaders()
            .getFirst("X-Correlation-Id");
        if (correlationId == null) {
            correlationId = UUID.randomUUID().toString();
        }
        exchange.getRequest().mutate()
            .header("X-Correlation-Id", correlationId);
        MDC.put("correlationId", correlationId);
        return chain.filter(exchange).then(Mono.fromRunnable(MDC::clear));
    }
}
54Structured Logging (JSON).
# Logback JSON encoder (logstash):
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
    <fieldNames>
        <timestamp>@timestamp</timestamp>
        <message>message</message>
        <level>level</level>
    </fieldNames>
</encoder>
# Вывод: {"@timestamp":"2026-06-28T10:30:00","level":"INFO","message":"User created","correlationId":"abc-123"}
55Centralized Log Aggregation (ELK).
# Filebeat shipping логов:
filebeat.inputs:
  - type: container
    paths:
      - /var/log/containers/*.log
    json.keys_under_root: true
output.elasticsearch:
  hosts: ["elasticsearch:9200"]
  index: "logs-%{+yyyy.MM.dd}"
# Альтернатива: Grafana Loki (легковеснее)
56Metrics с Prometheus.
# Spring Boot Actuator + Micrometer:
management:
  endpoints:
    web:
      exposure:
        include: prometheus,health
  metrics:
    tags:
      application: ${spring.application.name}
# Кастомные метрики:
@RestController
public class OrderController {
    private final Counter orderCreated = Counter.builder("orders.created")
        .description("Total orders created")
        .register(Metrics.globalRegistry);
    @PostMapping("/orders")
    public Order createOrder(@RequestBody Order order) {
        orderCreated.increment();
        return orderService.create(order);
    }
}
57RED Metrics (Rate, Errors, Duration).
# RED: Rate (RPS), Errors (error rate), Duration (latency)
@Timed(value = "api.requests", extraTags = {"service", "user-service"}, histogram = true)
@GetMapping("/api/users/{id}")
public User getUser(@PathVariable String id) { return userService.getUser(id); }
# PromQL:
# Rate: rate(api_requests_seconds_count[1m])
# Errors: rate(api_requests_seconds_count{status=~"5.."}[1m])
# Duration: histogram_quantile(0.99, rate(api_requests_seconds_bucket[5m]))
58USE Metrics (Utilization, Saturation, Errors).
# USE методология — для ресурсов (CPU, Memory, Disk, Network)
# CPU Utilization: rate(node_cpu_seconds_total{mode="user"}[1m])
# CPU Saturation: node_load1 / node_cpu_count
# Memory Utilization: 1 - node_memory_MemAvailable / node_memory_MemTotal
# Disk Utilization: rate(node_disk_io_time_seconds_total[1m])
# Правило: если USE не в порядке — проверяем RED
59Health Check API.
@RestController
@RequestMapping("/health")
public class HealthController {
    @GetMapping
    public ResponseEntity<HealthResponse> health() {
        HealthResponse response = new HealthResponse("UP");
        response.addComponent("database", checkDatabase());
        response.addComponent("redis", checkRedis());
        for (Map.Entry<String, String> comp : response.getComponents().entrySet()) {
            if (!"UP".equals(comp.getValue())) {
                return ResponseEntity.status(503).body(response);
            }
        }
        return ResponseEntity.ok(response);
    }
    private String checkDatabase() {
        try { ds.getConnection().close(); return "UP"; }
        catch (Exception e) { return "DOWN: " + e.getMessage(); }
    }
}
60Status Pages — дашборд состояния.
apiVersion: v1
kind: ConfigMap
metadata:
  name: status-page-config
data:
  services: |
    {
      "services": [
        {"name": "user-service", "url": "http://user-service/health", "critical": true},
        {"name": "order-service", "url": "http://order-service/health", "critical": true},
        {"name": "payment-service", "url": "http://payment-service/health", "critical": true}
      ]
    }
61OAuth2 / JWT аутентификация.
# OAuth2 + JWT — де-факто стандарт для микросервисов
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://auth.example.com
          jwk-set-uri: https://auth.example.com/.well-known/jwks.json
62OpenID Connect (OIDC).
# OIDC — надстройка над OAuth2 для аутентификации
@RestController
public class UserController {
    @GetMapping("/me")
    @PreAuthorize("isAuthenticated()")
    public Map<String, Object> me(@AuthenticationPrincipal Jwt jwt) {
        return Map.of(
            "sub", jwt.getSubject(),
            "name", jwt.getClaimAsString("name"),
            "email", jwt.getClaimAsString("email"),
            "roles", jwt.getClaimAsStringList("roles")
        );
    }
}
63mTLS между сервисами.
# Mutual TLS — двусторонняя проверка сертификатов
# В Istio mTLS включен по умолчанию:
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
spec:
  mtls:
    mode: STRICT
# Сгенерировать сертификаты:
openssl req -newkey rsa:2048 -nodes -keyout service.key \
  -x509 -days 365 -out service.crt \
  -subj "/CN=user-service/O=MyCompany"
64API Keys для сервисов.
@Configuration
public class ApiKeyFilter extends OncePerRequestFilter {
    private static final Map<String, String> API_KEYS = Map.of(
        "payment-service", "key-payment-abc",
        "notification-service", "key-notif-xyz"
    );
    @Override
    protected void doFilterInternal(HttpServletRequest req,
            HttpServletResponse res, FilterChain chain) {
        String apiKey = req.getHeader("X-API-Key");
        String serviceName = req.getHeader("X-Service-Name");
        if (apiKey == null || !apiKey.equals(API_KEYS.get(serviceName))) {
            res.setStatus(401);
            res.getWriter().write("Invalid API Key");
            return;
        }
        chain.doFilter(req, res);
    }
}
65Service Account — сервисные аккаунты.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: user-service-account
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: user-service-role
rules:
- apiGroups: [""]
  resources: ["pods", "services"]
  verbs: ["get", "list"]
66RBAC — Role-Based Access Control.
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
    @Bean
    public RoleHierarchy roleHierarchy() {
        RoleHierarchyImpl h = new RoleHierarchyImpl();
        h.setHierarchy("ADMIN > MODERATOR > USER > GUEST");
        return h;
    }
}
@RestController
public class AdminController {
    @GetMapping("/admin/users")
    @PreAuthorize("hasRole('ADMIN')")
    public List<User> getAllUsers() { return userService.findAll(); }
    @GetMapping("/users/{id}")
    @PreAuthorize("hasRole('USER') and #id == authentication.name")
    public User getUser(@PathVariable String id) { return userService.findById(id); }
}
67Zero Trust архитектура.
# Zero Trust — Never trust, always verify
# 1. Все сети считаются враждебными
# 2. Каждый запрос аутентифицирован и авторизован
# 3. Минимальные привилегии
# 4. Постоянная верификация
# Network policies в K8s:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: user-service-policy
spec:
  podSelector:
    matchLabels:
      app: user-service
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: api-gateway
    ports:
    - port: 8080
68SPIFFE/SPIRE — identity для сервисов.
# SPIFFE — Secure Production Identity Framework
# Идентичность: spiffe://cluster.local/ns/default/sa/user-service
# SPIRE agent конфигурация:
agent {
  data_dir = "/run/spire"
  log_level = "INFO"
  server_address = "spire-server:8081"
  trust_domain = "cluster.local"
}
69Secret Rotation — ротация секретов.
# Vault может автоматически ротировать пароли
vault secrets enable database
vault write database/config/postgres \
    plugin_name=postgresql-database-plugin \
    allowed_roles="user-service-role" \
    connection_url="postgresql://{{username}}:{{password}}@postgres:5432/users"
vault write database/roles/user-service-role \
    db_name=postgres \
    creation_statements="CREATE USER \"{{name}}\" WITH PASSWORD \"{{password}}\" VALID UNTIL \"{{expiration}}\"" \
    default_ttl="1h" \
    max_ttl="24h"
70Security Scanning в CI/CD.
# GitHub Actions Security Scan:
name: Security Scan
on: [push, pull_request]
jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: SAST (Semgrep)
        uses: semgrep/semgrep-action@v1
      - name: Container Scan
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: user-service:${{ github.sha }}
          format: sarif
          exit-code: 1
71Saga Pattern — Choreography.
# Choreography Saga — каждый сервис публикует события
@Service
public class OrderSagaService {
    @Autowired private KafkaTemplate<String, OrderEvent> kafka;
    @Transactional
    public Order createOrder(OrderRequest req) {
        Order order = orderRepository.save(new Order(req));
        kafka.send("orders", new OrderCreatedEvent(order.getId(), req.getAmount()));
        return order;
    }
    @KafkaListener(topics = "payment-events", groupId = "order-service")
    public void onPaymentEvent(PaymentEvent event) {
        if (event instanceof PaymentFailedEvent) {
            orderRepository.updateStatus(event.getOrderId(), OrderStatus.FAILED);
        }
    }
}
72Saga Pattern — Orchestration.
# Orchestration Saga — центральный координатор
@Component
public class OrderOrchestrator {
    @Autowired private PaymentClient paymentClient;
    @Autowired private InventoryClient inventoryClient;
    @SagaSaga
    public void processOrder(Order order) {
        try {
            inventoryClient.reserve(order.getItems());
            paymentClient.charge(order.getAmount());
            order.setStatus(OrderStatus.COMPLETED);
        } catch (PaymentException e) {
            inventoryClient.release(order.getItems());
            order.setStatus(OrderStatus.FAILED);
        }
        orderRepository.save(order);
    }
}
73CQRS — Command Query Responsibility Segregation.
# CQRS — разделение команд (запись) и запросов (чтение)
@RestController
@RequestMapping("/api/orders")
public class OrderController {
    @PostMapping
    @PreAuthorize("hasRole('USER')")
    public CompletableFuture<Void> createOrder(@RequestBody CreateOrderCommand cmd) {
        return commandBus.dispatch(cmd);
    }
    @GetMapping("/{id}")
    public OrderDto getOrder(@PathVariable String id) {
        return queryService.getOrder(id);
    }
}
74Event Sourcing — хранение событий.
# Event Sourcing — храним все изменения как события
@Entity
public class Event {
    @Id @GeneratedValue private Long id;
    private String aggregateId;
    private String aggregateType;
    private String eventType;
    @Lob private String eventData;
    private LocalDateTime createdAt;
}
@Service
public class OrderEventSourcingService {
    @Autowired private EventStore eventStore;
    @Transactional
    public void applyEvent(OrderEvent event) {
        eventStore.save(new Event(event.getAggregateId(), "order",
            event.getType(), event.toJson(), LocalDateTime.now()));
    }
}
75Shared Database Anti-Pattern.
# Shared Database — несколько сервисов используют одну БД
# Проблемы:
# - Изменения схемы затрагивают все сервисы
# - Race conditions: конкуренция за данные
# - Нет изоляции: один сервис может заблокировать таблицы
# - Трудно масштабировать: БД — единая точка
# Решение: Database per service
76Eventual Consistency.
# Eventual consistency — данные в конечном итоге согласованы
# В микросервисах невозможно обеспечить ACID транзакции
# Принципы:
# 1. Данные могут быть несогласованны короткое время
# 2. Использовать идемпотентность для повторных обработок
# 3. Saga для компенсации ошибок
# Пример: создан заказ -> PENDING -> CONFIRMED -> SHIPPED
77Idempotency — идемпотентность.
# Идемпотентность — повторные запросы дают тот же результат
@RestController
public class PaymentController {
    private final Set<String> processedKeys = ConcurrentHashMap.newKeySet();
    @PostMapping("/api/payments")
    public ResponseEntity<PaymentResult> charge(
            @RequestBody PaymentRequest req,
            @RequestHeader("Idempotency-Key") String key) {
        if (!processedKeys.add(key)) {
            return ResponseEntity.status(409)
                .body(new PaymentResult("DUPLICATE", "Already processed"));
        }
        try {
            PaymentResult result = paymentGateway.charge(req);
            return ResponseEntity.ok(result);
        } catch (Exception e) {
            processedKeys.remove(key);
            throw e;
        }
    }
}
78Outbox Pattern.
# Outbox Pattern — гарантированная отправка событий
@Transactional
public void createOrder(Order order) {
    orderRepository.save(order);
    outboxRepository.save(new OutboxMessage("order.created",
         new OrderCreatedEvent(order.getId(), order.getUserId())));
}
@Component
public class OutboxPublisher {
    @Scheduled(fixedDelay = 1000)
    @Transactional
    public void publishOutbox() {
        List<OutboxMessage> messages = outboxRepository
            .findTop100ByPublishedFalseOrderByCreatedAtAsc();
        for (OutboxMessage msg : messages) {
            kafka.send(msg.getTopic(), msg.getPayload());
            msg.setPublished(true);
            outboxRepository.save(msg);
        }
    }
}
79Change Data Capture (CDC).
# CDC — отслеживание изменений в БД через WAL
{
  "name": "order-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "postgres",
    "database.port": "5432",
    "database.user": "debezium",
    "database.dbname": "orders",
    "database.server.name": "orders-db",
    "table.include.list": "public.orders",
    "plugin.name": "pgoutput",
    "transforms": "unwrap",
    "transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState"
  }
}
80Two-Phase Commit vs Saga.
# 2PC — атомарная транзакция через несколько ресурсов
# 1. Prepare: все участники подтверждают готовность
# 2. Commit: все коммитят (или rollback)
# Проблемы: блокировки на prepare, координатор — SPOF
# Saga — альтернатива 2PC, нет блокировок, компенсация при ошибках
# 2PC лучше: малые транзакции, высокая консистентность
# Saga лучше: долгие транзакции, микросервисы, высокая доступность
81Transactional Outbox + Kafka Connect.
# Kafka Connect + Outbox через Debezium
{
  "name": "order-outbox-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "postgres",
    "table.include.list": "public.outbox",
    "tombstones.on.delete": "false",
    "transforms": "outbox",
    "transforms.outbox.type": "io.debezium.transforms.outbox.EventRouter",
    "transforms.outbox.table.field.event.id": "id",
    "transforms.outbox.table.field.event.key": "aggregate_id",
    "transforms.outbox.table.field.event.type": "event_type",
    "transforms.outbox.table.field.event.payload": "payload"
  }
}
82Contract Testing с Pact.
# Consumer-Driven Contract Testing
@ExtendWith(PactConsumerTestExt.class)
@PactTestFor(providerName = "user-service", port = "8080")
public class UserServiceConsumerTest {
    @Pact(consumer = "order-service")
    public V4Pact createPact(PactDslWithProvider builder) {
        return builder
            .given("user exists with id 123")
            .uponReceiving("a request for user 123")
                .path("/api/users/123")
                .method("GET")
            .willRespondWith()
                .status(200)
                .body(new PactDslJsonBody()
                    .stringType("id", "123")
                    .stringType("name", "John"))
            .toPact();
    }
}
83Provider тест — проверка контракта.
@Provider("user-service")
@PactBroker(url = "https://pact-broker.example.com")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserServiceProviderTest {
    @TestTemplate
    @ExtendWith(PactVerificationInvocationContextProvider.class)
    void pactVerificationTestTemplate(PactVerificationContext context) {
        context.verifyInteraction();
    }
    @State("user exists with id 123")
    void userExists() {
        when(userRepository.findById("123"))
            .thenReturn(Optional.of(new User("123", "John", "john@example.com")));
    }
}
84Integration Tests с Testcontainers.
@SpringBootTest
@Testcontainers
class UserServiceIntegrationTest {
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
    @Container
    static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine").withExposedPorts(6379);
    @DynamicPropertySource
    static void configure(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.redis.host", redis::getHost);
        registry.add("spring.redis.port", () -> redis.getMappedPort(6379));
    }
}
85Service Virtualization (WireMock).
@SpringBootTest
@WireMockTest(httpPort = 8081)
class OrderServiceTest {
    @Test
    void shouldProcessPayment() {
        stubFor(post(urlEqualTo("/api/payments"))
            .withRequestBody(matchingJsonPath("$.amount"))
            .willReturn(aResponse()
                .withStatus(200)
                .withBody("{"status":"SUCCESS","transactionId":"txn-123"}")));
        Order order = orderService.createOrder(new OrderRequest(100.00));
        assertNotNull(order.getTransactionId());
    }
}
86End-to-End Testing.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class OrderE2ETest {
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
    @Container
    static GenericContainer<?> kafka = new KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:7.4"));
    @Autowired private TestRestTemplate rest;
    @Test
    void fullOrderFlow() {
        User user = rest.postForObject("/api/users", new CreateUserRequest("john@test.com"), User.class);
        Item item = rest.postForObject("/api/items", new CreateItemRequest("Laptop", 1200.00), Item.class);
        Order order = rest.postForObject("/api/orders",
            new OrderRequest(user.getId(), List.of(new OrderItem(item.getId(), 1))), Order.class);
        assertEquals("PENDING", order.getStatus());
    }
}
87Canary Testing.
# Flagger — автоматический canary deploy
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: user-service
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: user-service
  analysis:
    interval: 30s
    threshold: 10
    maxWeight: 50
    stepWeight: 5
    metrics:
    - name: request-success-rate
      threshold: 99
      interval: 1m
88Chaos Engineering (Chaos Monkey).
# Chaos Mesh — намеренное внесение сбоев
apiVersion: chaos-mesh.org/v1alpha1
kind: PodChaos
metadata:
  name: user-service-kill
spec:
  action: pod-kill
  mode: one
  selector:
    namespaces: [default]
    labelSelectors:
      app: user-service
  scheduler:
    cron: "@every 5m"
---
apiVersion: chaos-mesh.org/v1alpha1
kind: NetworkChaos
metadata:
  name: network-delay
spec:
  action: delay
  delay:
    latency: "3000ms"
    jitter: "500ms"
  duration: "30s"
89Performance Testing (Gatling).
class OrderSimulation extends Simulation {
  val httpProtocol = http
    .baseUrl("https://api.example.com")
  val scn = scenario("Create Order")
    .exec(http("create_order")
      .post("/api/orders")
      .body(StringBody("""{"userId": "${userId}"}"""))
      .check(status.is(200)))
  setUp(
    scn.inject(
      rampUsersPerSec(10).to(100).during(60)
    )
  ).protocols(httpProtocol)
}
90Consumer Contract Tests.
# Pact Broker — хранилище контрактов
# CI/CD: Consumer публикует контракт, Provider проверяет
# can-i-deploy проверяет совместимость:
pact-broker can-i-deploy \
  --pacticipant user-service \
  --version 1.2.3 \
  --to-environment production \
  --broker-base-url https://pact-broker.example.com
91CI/CD pipelines per service.
name: user-service CI/CD
on:
  push:
    branches: [main]
    paths: ["services/user-service/**"]
jobs:
  test:
    steps:
    - uses: actions/checkout@v4
    - run: cd services/user-service && ./gradlew test
  build-and-push:
    needs: test
    steps:
    - uses: docker/build-push-action@v5
      with:
        context: services/user-service
        tags: registry.example.com/user-service:${{ github.sha }}
  deploy-staging:
    needs: build-and-push
    steps:
    - run: kubectl set image deployment/user-service app=registry.example.com/user-service:${{ github.sha }}
92Blue-Green Deployment.
# Blue-Green — две полных среды, переключение трафика
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service-blue
spec:
  replicas: 3
  selector: {matchLabels: {app: user-service, version: blue}}
  template:
    metadata: {labels: {app: user-service, version: blue}}
    spec:
      containers:
      - name: app
        image: registry.example.com/user-service:1.0.0
# Переключение:
kubectl patch service user-service -p '{"spec":{"selector":{"version":"green"}}}'
93Canary Release.
# Flagger canary на Istio
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: user-service
spec:
  service:
    port: 80
    hosts: [api.example.com]
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
    - name: request-success-rate
      threshold: 99
    webhooks:
    - name: loadtest
      url: http://flagger-loadtester:8080
94Feature Flags для A/B testing.
# OpenFeature для A/B тестов
Client client = OpenFeatureAPI.getInstance().getClient();
public Order processCheckout(Order order, User user) {
    EvaluationContext ctx = new ImmutableContext(user.getId());
    boolean useNewCheckout = client.getBooleanValue("new-checkout", false, ctx);
    if (useNewCheckout) { return newCheckoutService.process(order); }
    else { return legacyCheckoutService.process(order); }
}
95Rolling Update стратегия.
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 1
  minReadySeconds: 30
  template:
    spec:
      containers:
      - name: app
        image: registry.example.com/user-service:2.0.0
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 10
          periodSeconds: 5
96Infrastructure as Code (Terraform).
# Terraform модуль для сервиса:
resource "kubernetes_deployment" "service" {
  metadata {
    name      = var.service_name
    namespace = var.namespace
    labels    = { app = var.service_name }
  }
  spec {
    replicas = var.replicas
    selector { match_labels = { app = var.service_name } }
    template {
      spec {
        container {
          image = var.image
          name  = var.service_name
          port  { container_port = var.container_port }
          dynamic "env" {
            for_each = var.env_vars
            content {
              name  = env.key
              value = env.value
            }
          }
        }
      }
    }
  }
}
97Service Mesh (Istio).
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
metadata:
  name: istio-install
spec:
  profile: default
  components:
    ingressGateways:
    - name: istio-ingressgateway
      enabled: true
      k8s:
        service:
          type: LoadBalancer
  meshConfig:
    accessLogFile: /dev/stdout
    enableTracing: true
    defaultConfig:
      tracing:
        sampling: 10.0
98Kubernetes Operators для микросервисов.
@Controller
public class MicroserviceReconciler implements Reconciler<Microservice> {
    @Override
    public Result reconcile(Microservice resource, Context context) {
        String name = resource.getMetadata().getName();
        Deployment deployment = new DeploymentBuilder()
            .withNewMetadata().withName(name).endMetadata()
            .withNewSpec()
                .withReplicas(resource.getSpec().getReplicas())
                .withNewSelector()
                    .addToMatchLabels("app", name)
                .endSelector()
                .withNewTemplate()
                    .withNewSpec()
                        .addNewContainer()
                            .withName(name)
                            .withImage(resource.getSpec().getImage())
                        .endContainer()
                    .endSpec()
                .endTemplate()
            .endSpec()
            .build();
        return Result.success();
    }
}
99GitOps с ArgoCD.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: user-service
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/company/gitops-repo
    targetRevision: HEAD
    path: services/user-service/overlays/production
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: microservices
spec:
  generators:
  - git:
      repoURL: https://github.com/company/gitops-repo
      revision: HEAD
      directories:
      - path: services/*/overlays/production
  template:
    metadata:
      name: '{{path.basename}}'
    spec:
      source:
        repoURL: https://github.com/company/gitops-repo
        path: '{{path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: production
100Database Migration стратегии.
# Database Migration — управление изменениями схемы БД
# Каждый сервис управляет своей схемой независимо
# Инструменты: Flyway, Liquibase
# V1__create_users.sql:
CREATE TABLE users (
    id UUID PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    name VARCHAR(255),
    created_at TIMESTAMP DEFAULT NOW()
);
CREATE UNIQUE INDEX idx_users_email ON users(email);
# V2__add_status.sql:
ALTER TABLE users ADD COLUMN status VARCHAR(50) DEFAULT 'active';
# Правила:
# - Миграции только вперед (не редактировать опубликованные)
# - Каждый сервис имеет свою migration directory
# - Тестировать миграции на staging перед production

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions