| № | Вопрос | Код с комментариями |
| 1 | Что такое микросервисы? | # Микросервисы — архитектурный стиль, где приложение состоит из независимо развертываемых сервисов
# Каждый сервис имеет свою бизнес-способность
# Преимущества: масштабирование, независимый деплой, технологическое разнообразие
|
| 2 | Микросервисы vs монолит — сравнение. | # Монолит: единый процесс, общая БД, простота разработки
# Микросервисы: N процессов, database per service, сложность сетевого взаимодействия
# Когда выбрать микросервисы: >10 разработчиков, >6 месяцев, разные домены
|
| 3 | Принципы микросервисов. | # Single Responsibility — один сервис = одна бизнес-способность
# Loose Coupling — слабая связанность через API
# High Cohesion — высокая связность внутри сервиса
# Smart endpoints, dumb pipes — смысл на концах, транспорт простой
|
| 4 | Conway's Law в микросервисах. | # Organizations design systems that mirror their communication structure
# Команды должны быть выровнены по границам сервисов
# 2-pizza teams: ~6-8 человек на сервис
# Обратный закон Конвея: архитектура формирует организацию
|
| 5 | Bounded Context (DDD). | # Из Domain-Driven Design
# Bounded context = границы модели
# Пример: Order Context vs Payment Context vs Shipping Context
# Внутри контекста — единый язык (Ubiquitous Language)
# Контексты общаются через события или API
|
| 6 | Domain-Driven Design для микросервисов. | # Шаги:
# 1. Event Storming — найти доменные события
# 2. Aggregate — кластеры связанных сущностей
# 3. Bounded Context — границы
# 4. Context Map — карта взаимодействий
# 5. Выделить микросервисы по агрегатам
|
| 7 | Event Storming практика. | # Формат: большая стена, стикеры
# Оранжевые: Events (OrderPlaced, PaymentReceived)
# Синие: Commands (PlaceOrder, ProcessPayment)
# Зеленые: Aggregates (Order, Payment, Inventory)
# Красные: External Systems (PaymentGateway, Shipment)
# Результат: границы микросервисов
|
| 8 | Service granularity — детализация микросервисов. | # Слишком мелкие: DevOps ад, сетевая задержка
# Слишком крупные: почти монолит
# Правило 1: один сервис = одна бизнес-сущность
# Правило 2: команда должна комфортно поддерживать
# Правило 3: сервис должен быть заменяем за 2 недели
# Правило 4: не пересекать bounded contexts
|
| 9 | Database per service принцип. | # Каждый сервис владеет своей БД
# Никакой другой сервис не имеет прямого доступа
# Доступ только через API сервиса-владельца
# Свобода выбора технологии: Postgres, Mongo, Redis
# Проблема: распределенные транзакции (Saga)
|
| 10 | API 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);
}
}
|
| 11 | HTTP/REST взаимодействие. | # REST — Representational State Transfer
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/api/users/{id}")
User getUser(@PathVariable Long id);
}
|
| 12 | gRPC между сервисами. | # 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
|
| 13 | GraphQL для микросервисов. | # GraphQL — единый endpoint, клиент выбирает поля
schema { query: Query }
type Query { order(id: ID!): Order }
type Order { id: ID! user: User payment: Payment }
# Одна схема, несколько resolvers к разным сервисам
|
| 14 | Message 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.*"); }
}
|
| 15 | Kafka для событий. | # 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);
}
|
| 16 | Synchronous vs Asynchronous. | # Synchronous: HTTP/REST, gRPC — проще, ожидаем ответ, блокируется
# Asynchronous: RabbitMQ, Kafka — сложнее, eventual consistency, лучше изоляция
# Правило: синхронно для запросов, асинхронно для событий
|
| 17 | Circuit 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");
}
|
| 18 | Retry 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
|
| 19 | Bulkhead паттерн. | # 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));
}
|
| 20 | Timeout management. | # Таймауты — критичны в микросервисах
@Bean
public RestTemplate restTemplate() {
return new RestTemplateBuilder()
.connectTimeout(Duration.ofSeconds(2))
.readTimeout(Duration.ofSeconds(5))
.build();
}
# Правило: таймаут должен быть < 99-го процентиля ответа
|
| 21 | Kong 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}}'
|
| 22 | Traefik 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"
|
| 23 | Nginx как 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;
}
}
|
| 24 | Rate 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();
}
|
| 25 | Auth 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();
}
|
| 26 | Routing at Gateway — маршрутизация. | spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates: [Path=/api/users/**]
filters: [StripPrefix=1, AddRequestHeader=X-Gateway, true]
|
| 27 | Request 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();
}
|
| 28 | Response 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);
}
}
|
| 29 | API 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]
|
| 30 | Canary 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
|
| 31 | DNS-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
|
| 32 | Consul 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
|
| 33 | etcd как 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())
|
| 34 | Kubernetes 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 подов
|
| 35 | Client-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);
}
|
| 36 | Server-side discovery. | # Балансировщик между клиентом и сервисами
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
type: LoadBalancer
selector: {app: user-service}
ports:
- port: 80
targetPort: 8080
# Плюсы: клиент ничего не знает о discovery
|
| 37 | Health Checks для сервисов. | # Spring Boot Actuator:
management:
endpoints:
web:
exposure:
include: health,info
health:
readinessstate: {enabled: true}
livenessstate: {enabled: true}
# Liveness: сервис жив? Readiness: готов принимать трафик?
|
| 38 | Registry 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
|
| 39 | Third-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
|
| 40 | Service 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
|
| 41 | Spring 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
|
| 42 | Consul 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
|
| 43 | etcd для конфигурации. | # 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
|
| 44 | Environment-based конфигурация. | # Параметры переопределяются переменными окружения
server:
port: ${PORT:8080}
spring:
datasource:
url: ${DB_URL:jdbc:h2:mem:test}
username: ${DB_USER:sa}
password: ${DB_PASS:}
|
| 45 | Externalized 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
|
| 46 | Secrets 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
|
| 47 | Feature 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(); }
|
| 48 | ConfigMap в Kubernetes. | apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: "production"
LOG_LEVEL: "warn"
CACHE_TTL_SECONDS: "3600"
# Pod использует configMapRef для envFrom
|
| 49 | Reload конфигурации без перезапуска. | # 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
|
| 51 | Distributed 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);
}
|
| 52 | OpenTelemetry — стандарт 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
|
| 53 | Logging 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));
}
}
|
| 54 | Structured 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"}
|
| 55 | Centralized 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 (легковеснее)
|
| 56 | Metrics с 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);
}
}
|
| 57 | RED 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]))
|
| 58 | USE 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
|
| 59 | Health 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(); }
}
}
|
| 60 | Status 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}
]
}
|
| 61 | OAuth2 / 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
|
| 62 | OpenID 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")
);
}
}
|
| 63 | mTLS между сервисами. | # 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"
|
| 64 | API 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);
}
}
|
| 65 | Service 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"]
|
| 66 | RBAC — 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); }
}
|
| 67 | Zero 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
|
| 68 | SPIFFE/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"
}
|
| 69 | Secret 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"
|
| 70 | Security 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
|
| 71 | Saga 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);
}
}
}
|
| 72 | Saga 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);
}
}
|
| 73 | CQRS — 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);
}
}
|
| 74 | Event 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()));
}
}
|
| 75 | Shared Database Anti-Pattern. | # Shared Database — несколько сервисов используют одну БД
# Проблемы:
# - Изменения схемы затрагивают все сервисы
# - Race conditions: конкуренция за данные
# - Нет изоляции: один сервис может заблокировать таблицы
# - Трудно масштабировать: БД — единая точка
# Решение: Database per service
|
| 76 | Eventual Consistency. | # Eventual consistency — данные в конечном итоге согласованы
# В микросервисах невозможно обеспечить ACID транзакции
# Принципы:
# 1. Данные могут быть несогласованны короткое время
# 2. Использовать идемпотентность для повторных обработок
# 3. Saga для компенсации ошибок
# Пример: создан заказ -> PENDING -> CONFIRMED -> SHIPPED
|
| 77 | Idempotency — идемпотентность. | # Идемпотентность — повторные запросы дают тот же результат
@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;
}
}
}
|
| 78 | Outbox 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);
}
}
}
|
| 79 | Change 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"
}
}
|
| 80 | Two-Phase Commit vs Saga. | # 2PC — атомарная транзакция через несколько ресурсов
# 1. Prepare: все участники подтверждают готовность
# 2. Commit: все коммитят (или rollback)
# Проблемы: блокировки на prepare, координатор — SPOF
# Saga — альтернатива 2PC, нет блокировок, компенсация при ошибках
# 2PC лучше: малые транзакции, высокая консистентность
# Saga лучше: долгие транзакции, микросервисы, высокая доступность
|
| 81 | Transactional 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"
}
}
|
| 82 | Contract 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();
}
}
|
| 83 | Provider тест — проверка контракта. | @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")));
}
}
|
| 84 | Integration 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));
}
}
|
| 85 | Service 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());
}
}
|
| 86 | End-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());
}
}
|
| 87 | Canary 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
|
| 88 | Chaos 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"
|
| 89 | Performance 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)
}
|
| 90 | Consumer 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
|
| 91 | CI/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 }}
|
| 92 | Blue-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"}}}'
|
| 93 | Canary 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
|
| 94 | Feature 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); }
}
|
| 95 | Rolling 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
|
| 96 | Infrastructure 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
}
}
}
}
}
}
}
|
| 97 | Service 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
|
| 98 | Kubernetes 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();
}
}
|
| 99 | GitOps с 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
|
| 100 | Database 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
|
Microservices — Архитектура микросервисов: вопросы, практические задания и ответы с кодом
Темы
Вопросы и ответы