Начать проект

Проконсультируем и сориентируем по необходимому бюджету и срокам разработки. Заполните форму или отправьте нам письмо.

Архитектура интернет-проектов

Блог Технологии Процесс Технологии
Варианты и особенности архитектуры информационных систем для интернет-проектов и мобильных приложений. От спагетти до микросервисной архитектуры.
Дмитрий Юркин
15 января 2021
10 минут на чтение

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

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

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

Зачем нужна архитектура

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

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

Архитектура информационной системы оказывает существенное влияние на:

  • Нефункциональные характеристики (например, производительность, отказоустойчивость и другие);
  • Организацию процесса разработки и обслуживания системы (в том числе размер наиболее эффективной команды);
  • Структуру команды разработчиков и формат взаимоотношений между ними;

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

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

Разработка без архитектуры

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

Итак, мы взяли php, html, css, создали несколько таблиц в базе данных и начали писать код без какого-либо плана и проекта. Первое время процесс был весьма увлекательным и интересным. Через некоторое время мы начали сталкиваться с ситуациями, при которых ввод новых функций каким-то образом ломает существующие. Чем дальше, тем эти ситуации возникают чаще, что заставляет тестировать и проверять все по несколько раз после небольших изменений.

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

Система без какой-либо внятной архитектуры обычно приводит к явлению, называемому «спагетти-код».

mvp

Чем дольше мы будем развивать такую систему, тем больше будет страданий требоваться трудозатрат для внесения изменения, стабилизации и ввода в эксплуатацию новых релизов, а «технический долг» будет постоянно расти.

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

Увеличивать команду на таком проекте обычно бессмысленно, так как это не даст существенного прироста производительности, а рост сложности (в следствии роста проекта) будет сопровождаться большим количеством ошибок. Чтобы исправить ситуацию, нам потребуется сделать «рефакторинг» (для таких проектов рефакторинг заключается в фактическом переписывании заново).

Боремся с хаосом

Мы не будем подробно останавливаться на конкретных паттернах проектирования, т.к. для этого одной статьи недостаточно и для первичного погружения это не обязательно. Пока достаточно понимать, что важными (но не единственными) для построения архитектуры ПО являются следующие концепции:

  • абстрагирование
  • дробление и инкапсуляция (изоляция)

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

mvp
Упрощенный пример уровней абстракции.

Изолирование отдельных логических единиц позволяет разделить (декомпозировать) логику и изолировать ответственность разработчиков, которые занимаются внесением правок в код. Если не следовать этому, логика размазывается по проекту в разных его частях, а изменения начинают непредсказуемо влиять на поведение системы и провоцируют ошибки.

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

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

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

Существуют разные подходы к разделению логики на компоненты и слои. Применение того или иного принципа зависит от языка, среды, жизненного цикла приложения. Один из классических — MVC (Model View Controller), подразумевает разделение логики на 3 типа логических элементов:

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

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

mvp
MVC (упрощенно)

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

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

Клиент-серверная архитектура

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

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

Эти и другие проблемы — следствие «монолитной» архитектуры системы. Монолитная архитектура — архитектура, реализованная внутри одного программного модуля (одной программы).

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

Чтобы развиваться дальше, мы делаем шаг в сторону распределенной архитектуры, где различные компоненты информационной системы функционируют физически раздельно, используя для взаимодействия согласованный набор команд, называемый API (Application Programming Interface).

До этого момента у нас была единая система, являющаяся одним программным модулем, использующим единые логические компоненты. С этого момента она будет разделена на отдельные под-системы, но для начала только на две:

  • «back-end» — серверная часть, реализующая основную ценную логику системы
  • «front-end» — клиентская часть, предоставляющая пользователю интерфейс для работы с системой
mvp
Система с разделенным front-end и back-end.

Данный подход позволяет выделить в одну программную систему все, что касается пользовательского интерфейса, а в другую — полезную логику, расчеты, работу с данными. Кроме этого, единый механизм взаимодействия с back-end частью дает возможность делать дополнительные front-end под-системы, например мобильные приложения.

Плюсы

  • Больше нет связности пользовательского интерфейса и логики. Разнопрофильные разработчики теперь могут работать независимо;
  • Рост качества, т.к. каждый ответственнен за свою под-систему и более погружен в нее;
  • API подходит как для веб, так и для мобильных приложений'
  • Удобнее масштабировать под высокие нагрузки. За счет изоляции бизнес логики в отдельный физический модуль, его проще горизонтально/вертикально масштабировать.

Минусы

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

Рост нагрузок и горизонтальное масштабирование

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

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

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

Пример горизонтального масштабирования
Упрощенный пример горизонтального масштабирования при клиент-серверной архитектуре

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

Важно понимать, что слово «эффективность» в разработке ПО не является аналогом «дешевой» или «экономичной» и в первую очередь относится к организации процесса разработки, т.е. к людям. Эффективность оценивается в контексте развития системы при затрачиваемых ресурсах. Основное — скорость и прогнозируемость разработки программного обеспеченияна на длинной дистанции.

В целом, для 90-95% проектов имеет смысл остановиться на этом этапе. Но есть и более сложный вариант развития архитектуры, дающий высокую эффективность на проектах с десятками и сотнями людей в команде и большими нагрузками. Как? Все тоже самое — делим и изолируем.

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

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

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

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

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

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

Пример микросервисной архитектуры
Упрощенный пример микросервисной архитектуры

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

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

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

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

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

Резюме

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

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

Проектируем. Разрабатываем. Внедряем.