Микросервисная архитектура
Микросервисы представляют собой небольшие, изолированные сервисы, развертываемые независимо друг от друга. Каждый из них реализует отдельную бизнес-функцию и взаимодействует с другими через сетевые протоколы.
Микросервисы не просто делят систему на модули — они предполагают полную автономность каждого компонента. Каждый сервис — это мини-приложение, которое:
- реализует отдельную бизнес-функцию,
- управляется своей командой,
- развертывается независимо.
Чтобы сделать такой подход жизнеспособным, нужны определенные технические и организационные условия. Ниже — основные принципы и практики, лежащие в основе микросервисной архитектуры.
От SOA к микросервисам
Переход от SOA к микросервисам стал логичным продолжением поиска архитектур, способных выдерживать рост сложности, нагрузки и скорости изменений. Несмотря на схожие принципы — разбиение по бизнес-доменам, слабую связанность, независимость компонентов — микросервисный подход радикально переосмысливает реализацию этих идей.
Если в SOA центром была интеграционная шина (ESB), то в микросервисной архитектуре все держится на децентрализации и автономии. Каждый сервис становится самостоятельным — он имеет свою кодовую базу, отдельную схему БД, собственный цикл разработки и развертывания. Такой подход требует новых технических и организационных практик: контейнеризация, DevOps, автоматизация CI/CD, сервисные mesh-решения и управление API.
Принципы микросервисной архитектуры
Микросервисы строятся на нескольких ключевых принципах.
Независимость развертывания
Каждый сервис можно развивать, тестировать и выпускать в продакшн независимо от других. Это ускоряет time-to-market и упрощает отклик на изменения.
Моделирование вокруг бизнес-домена
В отличие от технического деления (UI, логика, БД), микросервисы фокусируются на бизнес-смысловой изоляции. Это соответствует подходу Domain-Driven Design.
Собственная база данных
Каждый сервис владеет своей схемой хранения данных, что исключает прямые связи через общую БД и повышает изоляцию.
Независимые команды
Одна команда — один сервис. Команда отвечает за разработку, тестирование, развертывание и сопровождение.
Преимущества и ограничения
Преимущества | Ограничения |
---|---|
Независимая разработка | Увеличение сложности инфраструктуры |
Горизонтальное масштабирование | Необходимость зрелых DevOps-практик |
Изоляция сбоев | Распределенное логирование и трассировка |
Гибкость в выборе технологий | Повышенные требования к безопасности |
Стек технологий микросервисов
Современные микросервисные системы требуют большого количества вспомогательных технологий, помимо кода самих сервисов.
Язык и фреймворк
В Java-экосистеме стандартом де-факто для микросервисов стал Spring Boot. Он решает ключевую проблему: как быстро создавать и запускать множество сервисов без громоздкой инфраструктуры, характерной для традиционного Java EE-подхода с WAR/EAR-архивами и серверами приложений (WildFly, WebLogic).
⚠️ Традиционная модель с WAR/EAR не масштабируется на десятки и сотни микросервисов. Spring Boot делает запуск каждого сервиса максимально простым и изолированным.
Spring Boot предлагает:
- минимальную конфигурацию (zero-XML),
- встроенный веб-сервер (Tomcat, Jetty, Undertow),
- удобное создание REST API (через Spring MVC/WebFlux),
- быструю интеграцию с БД и внешними системами.
Пример запуска сервиса:
1
$ ./mvnw spring-boot:run
Вместо того чтобы деплоить сервис в Tomcat, теперь сам сервис запускает Tomcat внутри себя — это self-contained executable JAR, готовый к упаковке в Docker.
⚠️ Хотя Spring Boot — доминирующее решение в мире Java, микросервисы могут быть реализованы на любом языке:
- Go (fiber, gin)
- Node.js (express, fastify)
- Python (FastAPI, Flask)
- .NET Core (C#)
- Rust, Kotlin, Elixir — в зависимости от требований проекта
Spring Boot, Spring Cloud и микросервисы: как они связаны?
При проектировании распределенных систем важно понимать различие между Spring Boot и Spring Cloud и их роль в архитектуре.
Spring Boot – это фреймворк, предназначенный для создания самостоятельных (standalone) приложений на Java с минимальной конфигурацией. Он обеспечивает быстрый старт, встроенный web server (например, Tomcat или Netty) и упрощенную работу со Spring Framework. Spring Boot применяется как для разработки микросервисов, так и для монолитных приложений, CLI-утилит или batch-задач.
Spring Cloud – это надстройка над Spring Boot, предоставляющая инструменты для построения распределенных систем и микросервисной архитектуры. Основные задачи Spring Cloud включают:
- Service Discovery (например, Eureka) для обнаружения сервисов в кластере.
- Centralized Configuration (Spring Cloud Config) для централизованного управления настройками.
- Circuit Breaker (Resilience4j) для обеспечения отказоустойчивости.
- API Gateway (Spring Cloud Gateway) для маршрутизации и решения сквозных задач.
- Distributed Tracing (Spring Cloud Sleuth, Zipkin) для трассировки запросов в распределенной системе.
- Load Balancing (Spring Cloud LoadBalancer) для балансировки нагрузки между экземплярами сервисов.
Таким образом, Spring Boot предоставляет основу и инфраструктуру для каждого отдельного приложения, а Spring Cloud решает задачи координации, коммуникации и устойчивости между приложениями в микросервисной экосистеме.
Как правило, вне микросервисной архитектуры Spring Cloud не используется. Spring Cloud создавался как решение именно для распределенных систем, где необходимо управлять множеством сервисов, их конфигурацией, маршрутизацией и доступностью. В монолитных приложениях все эти задачи решаются внутри одного процесса, поэтому Spring Cloud не применяется.
⚠️ Микросервис — это архитектурное решение, где каждый компонент системы — это отдельное Spring Boot приложение (или сервис на любом другом языке). Spring Cloud придает этой системе “связность” и возможности распределенных приложений.
Контейнеризация
Контейнеризация стала одним из ключевых факторов, позволивших микросервисам выйти за пределы теории и внедряться на практике. Решением №1 здесь стал Docker — инструмент, позволяющий упаковать приложение вместе со всеми его зависимостями в самодостаточный образ.
Зачем это нужно?
- Каждый микросервис работает в своей изолированной среде (независимо от окружения хоста),
- Упрощается переносимость между dev, test, staging и prod,
- Контейнеры легко масштабируются и перезапускаются,
- Образы можно версионировать, хранить и деплоить как артефакты.
Пример Dockerfile для Java-сервиса:
1
2
3
FROM openjdk:21
COPY target/payment-service.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
Такой файл создает образ, который можно запускать на любом хосте с установленным Docker, независимо от операционной системы и конфигурации Java.
Создание и запуск:
1
2
$ docker build -t payment-service .
$ docker run -p 8080:8080 payment-service
Контейнеризация особенно полезна, когда в системе десятки и сотни сервисов. Она устраняет “it works on my machine” и задает единообразие среды запуска.
⚠️ Хотя Docker остается самым популярным решением для контейнеризации, у него есть как альтернативы для запуска, так и альтернативы для сборки образов (Podman, Buildah и пр).
Оркестрация
Когда микросервисов становится много, ручное управление контейнерами (docker run, stop, restart) быстро выходит из-под контроля. Для автоматизации управления контейнерами и их окружением применяется Kubernetes(K8S) — открытая система оркестрации от Google, ставшая индустриальным стандартом.
Что делает Kubernetes?
- Автоматический деплой и масштабирование: задается число реплик — Kubernetes сам развернет и распределит их.
- Самовосстановление: если контейнер упал — Kubernetes перезапустит его.
- Сетевые абстракции: сервисы находят друг друга по именам, не по IP.
- Роллинг-обновления и откаты: обновления без даунтайма, с возможностью возврата.
Пример манифеста Deployment (YAML):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: apps/v1
kind: Deployment
metadata:
name: payment-service
spec:
replicas: 2
selector:
matchLabels:
app: payment-service
template:
metadata:
labels:
app: payment-service
spec:
containers:
- name: app
image: myrepo/payment-service:latest
ports:
- containerPort: 8080
Kubernetes позволяет не просто запускать сервисы, а строить самовосстанавливающиеся, динамические кластеры. Именно благодаря ему стало возможным надежно управлять микросервисами в продакшене.
⚠️ Чем отличается Docker от Kubernetes?
- Docker — это инструмент для создания и запуска отдельных контейнеров. Он изолирует среду приложения и помогает упаковать > его с зависимостями.
- Kubernetes — это система для управления большим количеством контейнеров. Она следит за состоянием, балансирует нагрузку, перезапускает при сбоях, масштабирует и обновляет сервисы.
Docker Kubernetes Упаковка и запуск Управление множеством контейнеров Работает с контейнерами Работает с кластерами, узлами, подами Отлично для dev Необходим для продакшн-масштабирования Kubernetes использует Docker или его аналоги как “движок” контейнеров, но сам отвечает за оркестрацию.
API Gateway (Шлюз)
В микросервисной архитектуре внешние клиенты (браузеры, мобильные приложения, сторонние системы) не взаимодействуют напрямую с каждым отдельным сервисом. Вместо этого все входящие запросы проходят через единый шлюз — API Gateway.
API Gateway — это единая входная точка во всю систему микросервисов. Он выполняет роль edge-сервера, через который проходят все внешние запросы. Это не просто прокси, а “контрольная точка” между внешним миром и микросервисами. Он упрощает клиентскую логику, централизует кросс-функциональные задачи и усиливает безопасность.
⚠️ Без API Gateway каждый клиент должен знать URL каждого сервиса, а также самостоятельно обрабатывать аутентификацию, трассировку, ретраи и ошибки. Это усложняет клиентский код и снижает управляемость системы.
Популярные решения:
Gateway | Особенности |
---|---|
Spring Cloud Gateway | Интеграция с Spring, реактивный стек |
Kong | Высокая производительность, плагины |
NGINX | Простота и гибкость, подходит для edge |
Traefik | Автоматическое обнаружение, TLS, Kubernetes-ready |
API Gateway берет на себя ряд критически важных задач.
Маршрутизация (Routing)
Одной из главных ролей API Gateway является smart маршрутизация — направлять каждый запрос к нужному микросервису в зависимости от его содержания. Например, если запрос идет на /api/cards/**
, он перенаправляется в cards-service, а /api/payments/**
— в payment-service.
Шлюз анализирует входящий запрос на основе заданных правил:
- Путь запроса. Например, все, что начинается с
/api/users/
, отправляется вuser-service
, а/api/orders/
— вorder-service
. - HTTP-метод. Можно разделять по типу операций:
GET /api/products
→product-reader
,POST /api/products
→product-writer
. - Заголовки, параметры, куки. Например, по заголовку
X-Tenant-ID
можно направлять запросы разных арендаторов (multi-tenant). - Содержимое тела запроса. Некоторые системы анализируют payload, чтобы выбрать правильный сервис — например, в GraphQL.
Пример правила (Spring Cloud Gateway):
1
2
3
4
5
predicates:
- Path=/api/payments/**
filters:
- AddRequestHeader=X-Origin, Gateway
uri: http://payment-service
Так клиенту не нужно знать, какие конкретно сервисы стоят “под капотом”. Он просто обращается к /api/...
, а Gateway делает все остальное.
В сложных системах маршруты можно настраивать динамически (например, через Config Server или Admin UI).
Аутентификация и авторизация (AuthN / AuthZ)
В микросервисной архитектуре контроль доступа реализуется на уровне API Gateway. Именно здесь происходит первичная проверка подлинности пользователя и определение его прав на выполнение запроса.
Такой подход позволяет централизованно управлять безопасностью и избавить микросервисы от дублирования логики аутентификации и авторизации.
Что делает шлюз на этом этапе:
- Аутентификация (Authentication). Проверка, что пользователь — это действительно он. Чаще всего это валидация: JWT (JSON Web Token), OAuth2 access token, ID token от OpenID Connect провайдера.
- Авторизация (Authorization). Проверка, имеет ли пользователь доступ к конкретному ресурсу. Например, доступ к
/api/admin/**
разрешен только для ролейADMIN
илиSUPERUSER
.
Например, если клиент присылает запрос с заголовком:
1
Authorization: Bearer eyJhbGciOi...
Gateway в этом случае:
- Валидирует токен (проверка подписи, срока жизни, issuer и т.д.).
- Извлекает
roles
: [user
,admin
]. - Пропускает запрос на
/api/admin/*
, если рольadmin
присутствует. - В противном случае — отдает
403 Forbidden
.
Размещение аутентификации на стороне API Gateway позволяет не дублировать логику в каждом сервисе, централизованно управлять доступом, упростить конфигурацию и обновление политик.
Внутри сети микросервисов можно использовать доверенную модель — передавать идентификатор пользователя в заголовках, не проверяя токен повторно.
Ограничение скорости (Rate Limiting)
API Gateway может:
- ограничить количество запросов от конкретного клиента (например, IP, API key),
- задавать разные квоты для разных тарифов (free, pro),
- блокировать подозрительную активность.
Это помогает избежать перегрузки и DDoS-атак.
Трассировка и логирование (Tracing & Logging)
Централизованная точка входа упрощает:
- логирование запросов и ошибок,
- отслеживание распределенных трассировок (tracing IDs, correlation IDs),
- сбор метрик времени отклика, частоты, ошибок и т. д.
Используются OpenTelemetry, Prometheus, Zipkin, Jaeger.
Изменение запроса и ответа (Rewrite, Filter)
Иногда нужно:
- скрыть внутренние URI (/internal/user/{id} → /api/me),
- убрать или добавить заголовки (например, X-Request-Id, X-User-Role),
- трансформировать JSON/формат данных.
Это позволяет изолировать клиенты от реализации и придерживаться единого API-стандарта.
Отказоустойчивость (Resilience)
Gateway может реализовать базовую защиту от сбоев:
-автоматические повторные попытки (retries),
- ограничение времени ожидания (timeouts),
- Circuit Breaker — блокирует доступ к нестабильному сервису временно.
Это позволяет клиенту не зависеть напрямую от состояния конкретного микросервиса.
Как определить границы микросервисов?
Это одна из самых сложных задач. На практике применяются два подхода:
- Domain-Driven Sizing — сервисы формируются вокруг бизнес-объектов (Student, Payment, Enrollment)
- Event Storming — границы сервисов определяются через важные бизнес-события (CompletedPayment, SearchProduct)
Controller-Service-Repository: применять ли на каждую сущность?
Одним из распространенных паттернов проектирования Spring-приложений является построение трехслойной архитектуры:
1
Controller → Service → Repository
В большинстве учебных материалов и CRUD-примеров этот подход используется “по умолчанию”: для каждой модели создаются отдельные контроллер, сервис и репозиторий.
Однако в производственной среде возникает вопрос: насколько оправдано дублирование этого шаблона на каждую сущность?
Классический layered-подход
В Layered Architecture Controller - это слой представления (API), принимает HTTP-запросы. Service – слой бизнес-логики. Repository – слой доступа к данным (DAO).
Преимущества такого подхода:
- Четкое разделение ответственности.
- Упрощение тестирования за счет изоляции слоев.
- Легкое внедрение Spring Data Repositories.
Ограничения:
- Привязка сервисного слоя к структуре базы данных, а не к бизнес-функциям.
- Разрастание количества «пустых» сервисов, которые фактически лишь вызывают репозиторий.
- Отсутствие выраженной предметной модели и use-case ориентации.
Подход, ориентированный на бизнес-функции (Use-case oriented)
Основная идея в том, что сервис отражает бизнес-операцию, а не сущность. Один сервис может управлять несколькими сущностями и репозиториями. Границы сервисов определяются bounded context и use-case логикой, а не таблицами.
Например, рассмотрим неоптимальный вариант:
1
2
UserController → UserService → UserRepository
OrderController → OrderService → OrderRepository
Здесь сервисы фактически дублируют CRUD-операции и не содержат бизнес-логики.
Предпочтительный вариант (use-case):
1
2
UserController → AccountService
OrderController → OrderManagementService
AccountService
реализует операции регистрации, активации, управления пользователями. OrderManagementService
отвечает за создание заказов, расчет скидок, уведомления.
Преимущества use-case подхода в том, что он отражает реальную предметную область, уменьшает количество сервисов и дублирования, упрощает сопровождение бизнес-логики, позволяет строить архитектуру по DDD (Domain-Driven Design) принципам.
Когда применять каждый подход?
Подход | Применение | Особенности |
---|---|---|
Layered per entity | CRUD-приложения, админ-панели, rapid prototyping | Быстрая генерация. Минимальная предметная логика. |
Use-case oriented | Производственные системы, DDD-проекты | Сервис отражает бизнес-операцию. Высокая связность к предметной области. |
Паттерн Strangler Fig: эволюционная миграция
Чтобы перейти от монолита к микросервисам, не всегда нужно переписывать все с нуля. Паттерн “удушающего фикуса” предлагает разворачивать новые сервисы рядом с монолитом, постепенно перехватывая трафик и заменяя старую функциональность.
⚠️ Когда-то монолит был всем деревом. Микросервисы — это вьющийся фикус, который со временем заменяет его целиком.
Статьи серии
- Микросервисы: серия материалов о принципах, паттернах и практике
- Эволюция архитектур: от монолита к микросервисам
- Нужен ли Service Discovery в Docker-среде?
- Изоляция данных и Feign: архитектура без сквозных связей
- Вызов других микросервисов с помощью Feign
- Как работает Service Discovery в Spring Cloud и зачем он нужен
- API Gateway в микросервисной архитектуре
- (в разработке)