Назад в блог
Разработка

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

12 минут
Preview
Содержание

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

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

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

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

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

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

case image

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

case image

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

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

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

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

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

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

Максим Б.
Ищете разработчиков для своего проекта? Расскажите нам про свою идею
Максим Б. CEO
Запланировать встречу

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

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

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

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

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

case image

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

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

  • Снижение сложности за счет четкого описания предметной области. У каждого домена есть область ответственности, и его взаимодействие ограничено определёнными границами (Bounded Contexts);
  • Логика изменения и добавления новых функций легко вписывается в уже существующие доменные модели, если они правильно структурированы;
  • Внесение изменений в логику требует глубокого понимания доменных сущностей и их взаимосвязей;
  • Если система построена с использованием DDD, то изменения вписываются в существующий контекст, минимизируя риск появления хаотичной логики;
  • Добавление новых функций происходит через создание новых доменов или расширение текущих контекстов.

Переход на Modular Monolith

case image

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

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

  • Сложность уменьшается через разбивку системы на модули с чёткими интерфейсами, каждый из которых можно разрабатывать и тестировать независимо от других;
  • Если модули хорошо изолированы, это снижает риск «разлома» других частей системы при внесении изменений;
  • Модульная архитектура хорошо подходит для перехода к микросервисам, так как каждый модуль может стать им;
  • Изменение существующих модулей требует проверки взаимодействия между ними, но при правильной архитектуре это становится менее рискованным;
  • Поддержка кода упрощена за счёт изолированных областей.

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

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

АспектDomain-Driven Design (DDD)Modular Architecture
СосредоточениеБизнес-доменФункциональные или технические области
Внесение измененийЧерез существующие доменные моделиЧерез добавление или изменение модулей
Чёткие границыГраницы контекстаФизическая изоляция модулей
ТестированиеТестирование логики через доменыЛокальное тестирование модулей
ПреимуществаГлубокое моделирование бизнесаПростота физического разделения

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

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

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

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

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

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

case image

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

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

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

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

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

АспектМикросервисы/SOADDD/Модульный Монолит
Настройка под задачиСпецифические настройки для сервисовОбщие настройки для всего приложения
Изоляция модулейПолная автономияКонцептуальное разделение
Технологическая гибкостьВозможность выбора языка для каждого сервисаЕдиный стек технологий
МасштабируемостьНа уровне сервисовГлобальная для монолита
Упрощение поддержкиАвтономное обновлениеКоординация внутри системы
ИнтеграцияЛёгкая через APIВстроенная с ограничениями

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

АспектSOAМикросервисы
Размер сервисовКрупныеМелкие, автономные
КоммуникацияESB, сложные протоколы (SOAP)REST, gRPC, лёгкие API
ЗависимостиСильные между службамиМинимальные, высокая изоляция
Гибкость технологийОграничена корпоративными стандартамиПолная свобода выбора технологий
МасштабируемостьЗависит от централизованных компонентовЛокальное масштабирование сервисов
Нужна помощь в выборе архитектуры? Наша команда проведет бесплатную консультацию
Заполнить форму

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

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

Кейс 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!

Остались вопросы к нашей команде? Напишите нам, и мы обязательно ответим
Заполнить форму
Читайте также