Эволюция архитектур: от монолита к микросервисам
Рассмотрим эволюцию архитектур программных систем на примере образовательной платформы EduSphere (произвольный пример). Эта платформа могла бы предоставлять онлайн-курсы для студентов, управление профилями преподавателей, записи на курсы, оплату, уведомления и пр. По мере роста и усложнения функциональности системы, увеличения числа пользователей - архитектура также развивалась.
Монолитная архитектура (Monolith)
В начале 2000-х годов монолитная архитектура была основным выбором для большинства веб-приложений. Это объяснялось несколькими причинами:
- Простота разработки: все компоненты системы (UI, бизнес-логика и доступ к данным) находились в одном приложении.
- Стандартный подход в индустрии: большинство популярных платформ (Java EE, .NET) и стек доступных технологий по умолчанию предполагали разработку монолитных приложений.
- Ограниченные требования к масштабируемости: пользовательские нагрузки большинства систем позволяли обслуживать их силами одного сервера, без сложных решений по горизонтальному масштабированию.
- Отсутствие зрелых альтернатив: контейнеризация, брокеры сообщений и облачные сервисы либо отсутствовали, либо только начинали развиваться и не имели широкого распространения в индустрии.
Для EduSphere на начальном этапе это означало, что будет единое приложение для управления студентами, преподавателями, курсами и платежами. Все сущности будут храниться в одной базе данных. Простая схема деплоя: сборка приложения и развертывание на единственном сервере приложений (или сервлет-контейнере).
Выглядеть это могло следующим образом:
Весь функционал системы был собран в одном развертываемом артефакте (обычно WAR или EAR). В нем компоненты пользовательского интерфейса, бизнес-логики и доступа к данным (Data Access Layer) располагались в виде слоев внутри общего кода. Такая структура известна как слоеная (layered) архитектура.
Артефакт развертывался в сервлет-контейнере (например, Apache Tomcat) или в сервере приложений (WildFly, WebLogic, IBM WebSphere, JBoss), если использовались компоненты EJB.
Взаимодействие с базой данных осуществлялось через слой DAO (Data Access Object), который напрямую выполнял SQL-запросы или работал через ORM-фреймворки (если они применялись).
Разработка и развертывание (CI/CD)
На этапе монолита процесс разработки и развертывания выглядел следующим образом:
- Все команды работали с единой кодовой базой в общем репозитории (Single Code Repo).
- Код собирался с помощью инструментов сборки (Ant, Ivy, позднее Maven или Gradle).
- Для автоматизации сборки и деплоя использовались системы CI/CD — чаще всего Hudson или Jenkins.
- Артефакт сборки (обычно EAR или WAR) включал в себя весь функционал приложения: UI, бизнес-логику и доступ к данным.
- После сборки приложение развртывалось в сервлет-контейнере (Apache Tomcat) или сервере приложений (JBoss).
Все приложение использовало единую базу данных.
Скелетон приложения мог быть таким, например:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
EduSphere/
├── src/ (Исходный код: UI, бизнес-логика, DAO и модели)
│ ├── com/edusphere/ui/ (JSP, Servlets)
│ ├── com/edusphere/business/ (Бизнес-логика)
│ ├── com/edusphere/dao/ (Data Access Objects, JDBC)
│ └── com/edusphere/model/ (DTO, сущности)
├── web/ (Фронтенд-часть и конфигурации сервера)
│ ├── WEB-INF/ (Java EE/Spring конфиги)
│ │ ├── web.xml
│ │ ├── applicationContext.xml (если подключали Spring)
│ └── static/ (CSS, JS, изображения)
├── lib/ (сторонние библиотеки, JAR'ники)
├── build.xml (Ant build script)
├── README.txt
└── db/ (SQL-скрипты для создания схемы и начальных данных)
├── schema.sql
└── seed_data.sql
Преимущества монолита
Рассмотрим ключевые преимущества такого подхода.
- Простая разработка и деплой для небольших команд и приложений. Монолитная архитектура подразумевает единую кодовую базу и общее приложение. Это упрощает процесс проектирования, так как не требуется продумывать механизмы взаимодействия между отдельными сервисами или сложную инфраструктуру. Для небольших команд это означает возможность быстро разрабатывать и развертывать новые функции без необходимости координировать изменения между множеством сервисов.
- Меньше кросс-функциональных задач (security, monitoring) на старте. В монолите большинство кросс-функциональных аспектов (например, безопасность, мониторинг, логирование) реализуются централизованно. Разработчикам не нужно заботиться о том, как согласованно применять эти аспекты во множестве отдельных сервисов, как это требуется в микросервисной архитектуре. Это упрощает старт проекта и снижает начальные накладные расходы.
- Лучшая производительность: нет сетевых задержек между компонентами. В монолитных приложениях все модули взаимодействуют между собой через вызовы внутри процесса (in-process calls), которые значительно быстрее сетевых запросов. Это особенно важно для операций, требующих высокой скорости отклика или большой пропускной способности. Отсутствие сетевых задержек и расходов на сериализацию данных позволяет достигать более высокой производительности по сравнению с распределенными системами.
Ограничения монолита
По мере роста проекта и усложнения его функциональности, монолитная архитектура начинает проявлять ряд ограничений, которые становятся все более заметными.
- Сложность внедрения новых технологий. Все компоненты монолитного приложения связаны общей кодовой базой и общим циклом сборки. Внедрение новой технологии в одной части приложения (например, переход на новый фреймворк или СУБД) часто требует изменений во всех слоях и модулях. Это увеличивает сложность миграции и может привести к непредвиденным ошибкам в других частях системы.
- Ограниченная гибкость Монолитная архитектура плохо подходит для команд, которые хотят работать независимо друг от друга. Любые изменения в одной части системы требуют пересмотра и тестирования всего приложения. Это ограничивает возможности команды быстро адаптироваться к меняющимся требованиям бизнеса.
- Единая кодовая база Большие монолиты со временем становятся трудно поддерживаемыми. Из-за большого количества взаимосвязей, изменения в одном модуле могут повлиять на другие, что увеличивает вероятность ошибок и усложняет тестирование. Работа с общей кодовой базой также затрудняет параллельную разработку несколькими командами.
- Отсутствие устойчивости к сбоям (Fault Tolerance) Если один модуль монолитного приложения выходит из строя (например, из-за ошибки в коде или проблем с памятью), это может привести к сбою всего приложения. Монолитные системы обычно не предусматривают механизмов изоляции сбоев между модулями.
- Любое обновление требует полного деплоя Даже небольшие изменения, затрагивающие только одну функциональность, требуют пересборки и полного развертывания всего приложения. Это увеличивает время вывода обновлений в продакшн (deployment time) и повышает риск внесения непреднамеренных ошибок.
Монолиты сегодня
Несмотря на появление новых архитектурных подходов, монолиты остаются популярными для небольших и средних проектов См. пример. Они позволяют быстрее начать разработку, проще поддерживать целостность данных и снижают инфраструктурные затраты. Даже в крупных системах нередко используют модульные монолиты или гибридные подходы, совмещая преимущества монолитной архитектуры с возможностями масштабирования.
Со временем появилось несколько подходов к структурированию монолитных приложений:
Single-Process Monolith Классический монолит: все слои и компоненты запускаются в рамках одного процесса. Минимальная сложность, но максимальная связанность.
Modular Monolith Приложение физически единое, но логически разделено на отдельные модули с чткими границами ответственности. Это позволяет ограничить связанность и облегчить сопровождение кода.
Distributed Monolith Формально приложение разделено на несколько развертываемых компонентов или сервисов, но в реальности между ними сохраняется сильная связанность (shared-базы данных, синхронные вызовы), из-за чего оно унаследовало многие проблемы классического монолита.
Service-Oriented Architecture (SOA)
По мере роста EduSphere монолитная архитектура начинает создавать проблемы. Новые модули все сильнее увеличивают связанность компонентов, внедрение изменений замедляется, поддержка и масштабирование становятся дорогими и трудоемкими.
Чтобы справиться с новыми требованиями, архитектура программных систем эволюционировала в сторону сервис-ориентированной модели (SOA).
SOA — это архитектурный подход, в котором система организована как набор слабо связанных сервисов. Каждый сервис реализует отдельную бизнес-функцию (например, управление студентами, курсы или платжную систему). Взаимодействие между сервисами обычно происходит через интеграционную шину (Enterprise Service Bus, ESB), которая обеспечивает маршрутизацию, преобразование (трансформацию) сообщений, безопасность, логирование и контроль доступа.
Этот подход стал логичным следующим шагом после монолита и получил широкое распространение в 2000-е годы, особенно в больших корпоративных приложениях.
Причины перехода к SOA
Переход на сервис-ориентированную архитектуру был обусловлен несколькими ключевыми факторами:
- Рост функциональности. Появление новых модулей (например, в случае с EduSphere - это могли бы быть онлайн-курсы, аналитика, интеграции с внешними системами) сделало монолит слишком громоздким и сложно расширяемым.
- Необходимость независимого развития команд. Разработчики, отвечающие за разные области (например, студенты, курсы, платежи), все чаще сталкивались с ограничениями общей кодовой базы. Им требовалась возможность развивать функциональность без постоянной координации с другими командами.
- Упрощение масштабирования. Возникла необходимость масштабировать отдельные компоненты системы в зависимости от их нагрузки. Например, сервис оплаты мог требовать большей вычислительной мощности по сравнению с управлением курсами.
- Повышение отказоустойчивости. В случае сбоя одного модуля система должна продолжать функционировать, что труднодостижимо в монолите.
- Возможность повторного использования сервисов. Бизнес-функции (например, платежи или уведомления) должны быть доступны для разных частей системы или даже внешних клиентов.
Идеальный vs реальный SOA
В идеальном варианте SOA все клиенты — это отдельные приложения (например, Student Portal, Teacher Portal, Admin Panel), которые могут развертываться независимо друг от друга. ESB представляет собой отдельный компонент уровня middleware и развертывается как самостоятельный процесс или кластер. Каждый сервис (StudentService, CourseService и т. д.) по замыслу также должен быть независимым и развертываться отдельно.
Однако в реальных реализациях SOA сервисы нередко группировались в один процесс или приложение — это зависело от бюджета, опыта команды и технических ограничений. Несмотря на то, что каждый сервис имел четкие логические границы, на практике часто все еще существовали крупные сборки, в которые входили сразу несколько сервисов (например, веб-интерфейс и связанные с ним модули на одном сервере).
Типичный стек технологий SOA
Обычный технологический стек того времени включал:
- Enterprise Service Bus (ESB): IBM WebSphere ESB, Oracle Service Bus (OSB), Apache ServiceMix, Mule ESB.
- Серверы приложений: IBM WebSphere Application Server, Oracle WebLogic Server, JBoss Application Server, Apache Tomcat (иногда использовался для менее сложных сервисов).
- Фреймворки и экосистемы: Spring Framework (часто для сервисов), Java EE (EJB, JAX-WS/JAX-RS).
- Коммуникации между сервисами: SOAP (основной стандарт в SOA), REST (начал использоваться ближе к 2010-м).
- База данных: Oracle Database, IBM DB2, MS SQL Server.
- CI/CD: Инструменты автоматизации сборки и деплоя чаще всего включали Jenkins, TeamCity или Bamboo. Однако во многих проектах сборка и развертывание оставались частично ручными или полуавтоматизированными.