Как стать автором
Обновить

Эволюция архитектурных паттернов в бэкенд-разработке: от MVC к микросервисам

Время на прочтение10 мин
Количество просмотров3.8K

Архитектурный паттерн – фундамент разработки любого масштабного проекта. От его выбора зависит успешное развитие и поддержка бэкенд-систем. Традиционный шаблон MVC (Model-View-Controller) долгое время считался оптимальным выбором для разработки веб-приложений.

Современные вариации MVC включают механизмы внедрения зависимостей (IoC, DI), благодаря чему можно значительно усложнить и расширить его функционал. Однако даже сложные MVC-структуры имеют ограничения, особенно при масштабировании и поддержке растущей бизнес-логики.

Несмотря на то, что современные MVC-фреймворки позволяют реализовывать сложную архитектуру, базовый MVC-подход (без каких-либо дополнений) многие команды используют до сих пор.

В этой статье мы сделаем небольшой экскурс в эволюцию архитектурных подходов – от классического шаблона MVC, популярного на начальных стадиях разработки, до более современных решений, таких как SOA, DDD, Modular Monolith и микросервисы.

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

Проблемы традиционного MVC

Главные преимущества MVC – скорость разработки и низкий порог вхождения. Это простой и понятный шаблон, который можно быстро освоить и внедрить в проект. Однако в долгосрочной перспективе эти преимущества могут повлечь за собой дополнительные временные затраты ввиду сложности поддержки. Они связаны с распределённостью бизнес-логики по контроллерам, ростом проекта, трудностями с тестированием и дублированием логики. Особенно это проявляется при отсутствии модульных тестов и невозможности переиспользовать код в разных частях приложения.

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

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

  • Интеграция IoC и DI – современные MVC-фреймворки (например, ASP.NET MVC, Laravel, Spring MVC) предлагают встроенные контейнеры для внедрения зависимостей, что повышает модульность и облегчает тестирование;

  • Чёткое разделение представлений и контроллеров – использование View Models, шаблонов и сервисов позволяет структурировать взаимодействие между слоями;

  • Расширяемость за счёт дополнительных слоёв – при необходимости MVC можно дополнить слоями бизнес-логики, доменной модели и инфраструктуры, что делает архитектуру более гибкой.

Однако, несмотря на видимые улучшения, остаются и ограничения:

  • Распыление бизнес-логики – даже при наличии сервисного слоя и репозиториев, логика бывает распределена между контроллерами, моделями и другими компонентами, что усложняет её поддержку;

  • Сложности масштабирования – в ходе роста проекта управление зависимостями и изменение функционала могут приводить к запутанности кода, что требует частого рефакторинга;

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

Теперь, когда мы знаем про основную проблему, рассмотрим поэтапное развитие проекта, чтобы понять, когда может возникнуть необходимость в изменении архитектуры приложения. А также реально ли предугадать скорый переход на другие паттерны и подготовиться к нему.

Этапы эволюции архитектуры

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

Допустим, мы начали свой проект с ранее описанного MVC и запустили MVP-версию, которая быстро стала успешной. Клиент решает дорабатывать продукт, и с течением времени, мы начинаем сталкиваться со сложностями в поддержке и изменении существующей или новой логики. В таких случаях можно рассмотреть переход на другую архитектуру, чтобы сократить время на разработку и уменьшить количество багов.

Рассмотрим варианты, которые, на мой взгляд, лучше всего подходят для быстрорастущих продуктов.

Переход к Domain-Driven Design (DDD)

Это методология моделирования фокусируется на глубоком понимании предметной области. В DDD основной акцент сделан на создании «обогащённой» доменной модели и использовании таких концепций, как ограниченные контексты (Bounded Contexts), агрегаты, сущности и value objects. DDD помогает структурировать бизнес-логику внутри приложения, но не предписывает конкретную стратегию развертывания или взаимодействия между компонентами.

К преимуществам и особенностям перехода на DDD относятся следующие характеристики:

  • Снижение сложности за счет четкого описания предметной области. У каждого домена есть область ответственности, и его взаимодействие ограничено определёнными границами (Bounded Contexts);

  • Логика изменения и добавления новых функций легко вписывается в уже существующие доменные модели, если они правильно структурированы;

  • Внесение изменений в логику требует глубокого понимания доменных сущностей и их взаимосвязей;

  • Если система построена с использованием DDD, то изменения вписываются в существующий контекст, минимизируя риск появления хаотичной логики;

  • Добавление новых функций происходит через создание новых доменов или расширение текущих контекстов.

Переход на Modular Monolith

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

Преимущества и особенности перехода на модульный монолит:

  • Сложность уменьшается через разбивку системы на модули с чёткими интерфейсами, каждый из которых можно разрабатывать и тестировать независимо от других;

  • Если модули хорошо изолированы, это снижает риск «разлома» других частей системы при внесении изменений;

  • Модульная архитектура хорошо подходит для перехода к микросервисам, так как каждый модуль может стать им;

  • Изменение существующих модулей требует проверки взаимодействия между ними, но при правильной архитектуре это становится менее рискованным;

  • Поддержка кода упрощена за счёт изолированных областей.

Выбор подхода

Все зависит от конкретного проекта, команды, их отношения к DDD. Однако можно ориентироваться на эту таблицу, описывающую различия в зависимости от разных аспектов.

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

Внедрение микросервисов и SOA

SOA
SOA
Микросервисы
Микросервисы

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

Однако в этом случае у вас должна быть продуманная инфраструктура для оркестрации, мониторинга и управления межсервисным взаимодействием.

Микросервисная архитектура и SOA (Service-oriented architecture) говорят об одном и том же, но разными словами. Оба направлены на распределение системы на отдельные компоненты, но различаются в подходах, масштабах и применении.

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

Автономно работающие компоненты минимизируют влияние изменений в них работу всех системы. В модульном монолите или DDD границы между модулями (например, Bounded Contexts) более концептуальны, и изменения в одном модуле могут затронуть связанные части.

Возможность выбора различных языков для каждого компонента позволяет оптимизировать работу всех системы. В одном проекте можно использовать Python для обработки данных, Go – для высокопроизводительных сервисов, Node.js – для быстрого прототипирования.

Нагрузка на систему не однородна и, благодаря возможности масштабирования отдельных компонентов, можно эффективно управлять ресурсами и экономить на инфраструктуре.

Все преимущества перехода собраны в таблице:

Оба подхода имеют свои сильные стороны, и выбор между ними зависит от потребностей вашей системы. Таблица с сопоставлением их аспектов поможет определиться с нужной архитектурой:

Практический опыт и примеры из проектов

На своем опыте я столкнулся с двумя значимыми проектами, каждый из которых в определенный момент столкнулся с необходимостью изменений. Проблемы архитектуры и технический долг вынудили команды задуматься о рефакторинге и переходе на другую архитектуру. Хотя путь был весьма тернистым.

Кейс 1: переход на современную MVC-структуру

Средний по масштабу проект, где смена архитектуры заняла три месяца и была выполнена всего двумя разработчиками. Основной задачей стала полная переработка кода приложения, с целью привести его в соответствие с современной MVC-структурой и апдейтом фреймворка до Laravel 8.0. До переноса проект был на Laravel 5.8, где использовалась классическая модель: REST API с логикой, встроенной в модели, и перегруженные контроллеры, отвечавшие не только за маршрутизацию, но и за бизнес-логику. Попытки переиспользования кода сводились к вынесению логики в хэлперы, что со временем привело к избыточной связанности.

Основные сложности перехода:

  • Баги после перераспределения логики – исправление ошибок, появившихся вследствие изменения архитектурных принципов;

  • Давление со стороны бизнеса – ожидание более быстрого процесса, что осложняло итерации и отладку;

  • Разделение логики по слоям и сервисам – необходимость минимизировать связанность и достичь атомарности.

Самой серьезной проблемой стало разделение бизнес-логики. За годы разработки код писали разные команды, что привело к накоплению дублирующихся решений и сложности переиспользовать его. Логика могла встречаться на различных уровнях приложения – от middleware до моделей, что затрудняло рефакторинг. В процессе выявлялись новые кейсы, требующие расширения или модификации уже существующих решений, которые порождали цепочку переписывания сервисов и тестов. Иногда это приводило к необходимости разделить один сервис на несколько из-за его разрастания на 1-2 тысячи строк. Это влияло на взаимодействие компонентов и требовало дополнительной отладки, включая решение проблем с бесконечными зависимостями в DI.

Несмотря на сложности, переход принес значительные преимущества. Улучшенная архитектура, тестовое покрытие и упрощенная поддержка позволили ускорить разработку новых функций. В целом, скорость разработки увеличилась примерно на 25%, а количество багов сократилось на 30%, что подтверждает эффективность проведенного рефакторинга.

Кейс 2: путь через компромиссы

Проект включал 450+ контроллеров и всего 90 сервисов. На первый взгляд, странное соотношение, которое можно объяснить большой численностью команд, использовавших разные архитектурные подходы. В результате обнаружить и учитывать все места хранения бизнес-логики было практически невозможно.

Проект столкнулся с проблемами: большое количество багов, сложность поддержки, масштабирования и оптимизации. Эти ограничения вынуждали искать новые решения. Но ситуация оказалась сложнее: 100+ разработчиков, более 5 команд и отдельные группы менеджеров, аналитиков, архитекторов, DevOps и других специалистов участвовали в проекте. Консенсус между всеми этими группами был недостижим. Переход от идеи до начала реальных работ занял около полугода. Бизнес, как известно, неохотно выделяет ресурсы на исправление технического долга.

Вместо полного перехода к микросервисной архитектуре был принят компромисс: отдельные команды начали переводить свои компоненты в микросервисы. Существующий функционал на монолите продолжал использоваться, но интеграция с новыми микросервисами обеспечивалась через базы данных, Redis, Prometeus и новый API микросервисов. Это решение требовало значительных усилий со стороны DevOps-команды, поскольку опыт оркестрации микросервисов на новом для них языке Go был минимальным. Для минимизации рисков, в первую очередь, переводились компоненты с низкой нагрузкой.

Основные трудности в этом кейсе:

  • Обучение и адаптация – язык Go был новым для всех. Был нанят лидер разработки и организованы курсы переподготовки для PHP-разработчиков. Однако недостаток опыта привел к несогласованности данных между системами, крашам и потерям выручки;

  • Бутылочные горлышки – высокая нагрузка выявляла критические участки, требующие немедленного исправления;

  • Стресс команды – множество багов, нестабильность инфраструктуры и обилие проблем создавали ощущение приближающегося провала.

После сложного этапа стабилизации, включая исправление багов и оптимизацию узких мест, ситуация изменилась. Архитектура стала прозрачнее, разработка ускорилась, а тестирование позволило минимизировать ошибки. Свобода команд в разработке своих компонентов, возможность масштабирования отдельных сервисов и стандартизация API значительно повысили эффективность и независимость.

✍ Вывод. Переход на новую архитектуру неизбежно сопряжен с трудностями для всех — разработчиков, менеджеров, DevOps-команды и владельцев продукта. Тем не менее, результат оправдывает усилия: команды получают свободу работы над своими зонами ответственности, сервисы становятся более устойчивыми и масштабируемыми, а бизнес выигрывает за счет прозрачности и гибкости системы.

Рекомендации по выбору архитектурного паттерна

При выборе архитектуры стоит учитывать следующие критерии:

  • Тестируемость – архитектура должна позволять легко покрывать функциональные блоки модульными и интеграционными тестами;

  • Читаемость и поддерживаемость кода – структурированный код упрощает внесение изменений и снижает вероятность ошибок;

  • Переиспользование логики – разделение функционала на атомарные компоненты помогает избежать дублирования и ускоряет разработку новых функций;

  • Гибкость и масштабируемость – архитектура должна быть способна адаптироваться к изменяющимся требованиям проекта, позволяя поэтапно вводить новые возможности.

В контексте рассмотренных архитектур можно выявить следующие различия, которые могут помочь вам определиться:

  • Цель и область применения – DDD фокусируется на моделировании бизнес-логики, а SOA и Modular Monolith – на структурировании приложения с точки зрения развертывания и управления зависимостями;

  • Развертывание – SOA подразумевает независимое развертывание сервисов, в то время как Modular Monolith развёрнут как единое целое, несмотря на внутреннюю модульную структуру. DDD же не диктует способ развертывания;

  • Коммуникация – в SOA сервисы взаимодействуют по сети (например, через REST или SOAP), что влечёт за собой вопросы сетевой задержки, надёжности и безопасности. Modular Monolith не имеет таких проблем, так как модули вызывают друг друга напрямую внутри одного процесса;

  • Управление сложностью – DDD помогает управлять сложностью бизнес-логики через чёткое разделение доменной модели на контексты. SOA и Modular Monolith, напротив, структурируют систему с точки зрения технического разделения и развертывания.

Несмотря на схожесть в стремлении к разделению системы на логические части, эти подходы решают разные задачи: DDD – моделирование предметной области, SOA – организацию распределённой системы, а Modular Monolith – создание модульного, но цельного приложения.

Важно помнить, что выбор архитектурного паттерна зависит от специфики проекта, бюджета и долгосрочных целей заказчика. В некоторых случаях может быть оправдан старт с более простой архитектуры (MVC) с последующим переходом на DDD, если проект предполагает значительный рост и усложнение бизнес-логики.

Заключение

Современные реализации MVC с использованием IoC и DI могут быть сложными и эффективными для небольших и средних проектов. Однако при росте системы и усложнении бизнес-логики они могут столкнуться с проблемами масштабирования, фрагментации логики и сложности поддержки. Альтернативные архитектурные паттерны, такие как SOA, DDD, Modular Monolith и микросервисы, предлагают явное разделение обязанностей, изоляцию компонентов и гибкость, что значительно облегчает развитие и сопровождение крупных проектов.

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

В следующей части мы поделимся кейсами для каждого варианта и сравним подходы на практике.

С вами была команда dev.family!

Теги:
Хабы:
+5
Комментарии7

Публикации

Ближайшие события