Post

Микросервисная архитектура

Микросервисная архитектура

Микросервисы представляют собой небольшие, изолированные сервисы, развертываемые независимо друг от друга. Каждый из них реализует отдельную бизнес-функцию и взаимодействует с другими через сетевые протоколы.

Микросервисы не просто делят систему на модули — они предполагают полную автономность каждого компонента. Каждый сервис — это мини-приложение, которое:

  • реализует отдельную бизнес-функцию,
  • управляется своей командой,
  • развертывается независимо.

Чтобы сделать такой подход жизнеспособным, нужны определенные технические и организационные условия. Ниже — основные принципы и практики, лежащие в основе микросервисной архитектуры.

От 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 — это система для управления большим количеством контейнеров. Она следит за состоянием, балансирует нагрузку, перезапускает при сбоях, масштабирует и обновляет сервисы.
DockerKubernetes
Упаковка и запускУправление множеством контейнеров
Работает с контейнерамиРаботает с кластерами, узлами, подами
Отлично для 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/productsproduct-reader, POST /api/productsproduct-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 entityCRUD-приложения, админ-панели,
rapid prototyping
Быстрая генерация.
Минимальная предметная логика.
Use-case orientedПроизводственные системы,
DDD-проекты
Сервис отражает бизнес-операцию.
Высокая связность к предметной области.

Паттерн Strangler Fig: эволюционная миграция

Чтобы перейти от монолита к микросервисам, не всегда нужно переписывать все с нуля. Паттерн “удушающего фикуса” предлагает разворачивать новые сервисы рядом с монолитом, постепенно перехватывая трафик и заменяя старую функциональность.

⚠️ Когда-то монолит был всем деревом. Микросервисы — это вьющийся фикус, который со временем заменяет его целиком.

Статьи серии

This post is licensed under CC BY 4.0 by the author.