Спасибо! Мы свяжемся с Вами в ближайшее время.
Начать проект
Архитектура интернет-проектов
15 января 2021
10 минут на чтение
Статья предназначена как для начинающих специалистов, так и для увеличения кругозора людей из других отраслей, понимающих базовые принципы программирования.
Как на предпродажном этапе, так и при проектировании, в общении с клиентом зачастую проскакивают вопросы про выбор архитектуры и обоснованность этого выбора. Это важный момент, и ответ не всегда однозначен, потому что многое зависит от специфики проекта, от скорости роста нагрузки, от ресурсов и возможностей команды.
Ниже я попробую в упрощенном виде (на сколько это возможно) описать основные подходы к проектированию архитектуры интернет-проекта (и в целом информационных систем), плюсы, минусы, а также эволюцию на примере условного проекта.
Зачем нужна архитектура
Правильный выбор и проектирование архитектуры информационной системы является необходимым и существенным фактором эффективности. Эффективности как самой системы с точки зрения использования вычислительных ресурсов для обслуживания пользователей, так и эффективности разработки этой системы, определяемой затратами человеческих ресурсов на обслуживание и развитие, скоростью выпуска релизов и т.д.
Архитектура позволяет системе работать предсказуемее, упрощает управление разработкой и развитием системы, будь то корпоративное программное обеспечение, интернет-проект или серверное ПО для мобильного приложения.
Архитектура информационной системы оказывает существенное влияние на:
- Нефункциональные характеристики (например, производительность, отказоустойчивость и другие);
- Организацию процесса разработки и обслуживания системы (в том числе размер наиболее эффективной команды);
- Структуру команды разработчиков и формат взаимоотношений между ними;
Сфера разработки программного обеспечения развивается очень быстро. Тем не менее, общие принципы архитектурных решений программного обеспечения не так подвластны быстрым изменениям и строятся на одних и тех же фундаментальных принципах. Со временем происходит некоторая трансформация различных подходов, вызванная прежде всего ростом вычислительных возможностей, появлением облачных вычислений и т.д., но основная суть остается той же.
Ниже я расскажу о нескольких ключевых подходах на примере некоторого условного проекта, который постепенно растет и меняет свою архитектуру, от работы одного разработчика к высоконагруженному проекту с несколькими командами разработки. Надеюсь, это позволит показать, как архитектура связана с форматом и размером команды, и почему нет идеальной архитектуры для любого проекта.
Разработка без архитектуры
Для лучшего понимания, представим, что мы занимаемся разработкой некоторого
Итак, мы взяли php, html, css, создали несколько таблиц в базе данных и начали писать код без какого-либо плана и проекта. Первое время процесс был весьма увлекательным и интересным. Через некоторое время мы начали сталкиваться с ситуациями, при которых ввод новых функций каким-то образом ломает существующие. Чем дальше, тем эти ситуации возникают чаще, что заставляет тестировать и проверять все по несколько раз после небольших изменений.
Ошибки мы кое как поправили, но любое изменение функциональности вызывает непредсказуемые ошибки в других местах. Кроме того, чтобы изменить логику какой-нибудь функции, нам приходится лазать по проекту и изменять код в нескольких разных местах и т.д.
Система без какой-либо внятной архитектуры обычно приводит к явлению, называемому «спагетти-код».
Чем дольше мы будем развивать такую систему, тем больше будет страданий требоваться трудозатрат для внесения изменения, стабилизации и ввода в эксплуатацию новых релизов, а «технический долг» будет постоянно расти.
Разработчик, работающий с такой системой, чтобы показывать нормальную производительность, должен понимать ее на уровне интуиции. Такой подход существенно усложняет ввод новых разработчиков для расширения команды с целью увеличить производительность разработки.
Увеличивать команду на таком проекте обычно бессмысленно, так как это не даст существенного прироста производительности, а рост сложности (в следствии роста проекта) будет сопровождаться большим количеством ошибок. Чтобы исправить ситуацию, нам потребуется сделать «рефакторинг» (для таких проектов рефакторинг заключается в фактическом переписывании заново).
Боремся с хаосом
Мы не будем подробно останавливаться на конкретных паттернах проектирования, т.к. для этого одной статьи недостаточно и для первичного погружения это не обязательно. Пока достаточно понимать, что важными (но не единственными) для построения архитектуры ПО являются следующие концепции:
- абстрагирование
- дробление и инкапсуляция (изоляция)
Проще говоря, разбиваем сложное действие на маленькие простые действия, каждое из которых имеет понятный вход-выход и не влияет на другие (управляется с более высокого уровня). Сложное действие описывается на более абстрактном (верхнем) уровне с применением ранее созданных функций нижнего уровня.
Изолирование отдельных логических единиц позволяет разделить (декомпозировать) логику и изолировать ответственность разработчиков, которые занимаются внесением правок в код. Если не следовать этому, логика размазывается по проекту в разных его частях, а изменения начинают непредсказуемо влиять на поведение системы и провоцируют ошибки.
Основные шаги по борьбе с хаосом — разделение полезной логики на отдельные логические единицы и введение уровней абстракции. Уровни абстракции позволяют управлять сложностью системы и снижать ее за счет сокрытия рутинной и малозначимой логики на нижних слоях, вынося основную логику в верхние слои.
Уровней абстракции может быть несколько, в зависимости от используемого подхода и физической реализации системы. Код вышестоящего уровня не должен знать и учитывать как работают программные единицы нижестоящего уровня. Для него они — черный ящик, дающий однозначный набор функций, которые он может использовать. Уровни строятся таким образом, чтобы на самом верхнем уровне исполнялась бизнес-логика (полезные функции, для которых и нужна система), а на нижних — вспомогательные функции и взаимодействие с инфраструктурными системами (база данных, хранилище, и т.д.).
Уровни абстракции также полезны для уменьшения связности между программными единицами. Например, правильный подход, когда программные элементы одного уровня абстракции не взаимодействуют между собой напрямую, а только через вышестоящий (управляющий) уровень. Этот и другие принципы делают код более удобным для поддержки, развития и тестирования.
Существуют разные подходы к разделению логики на компоненты и слои. Применение того или иного принципа зависит от языка, среды, жизненного цикла приложения. Один из классических — MVC (Model View Controller), подразумевает разделение логики на 3 типа логических элементов:
- Model — модель данных, описывает структуру данных и взаимодействует с инфраструктурой базы данных;
- View — представление, реализация логики отображения данных пользователю и взаимодействия с пользователем;
- Controller — логика, управляющая данными и представлением;
Применяется как множество реализаций этого подхода, так и другие подходы с разным количеством типов программных единиц и слоев абстракции. Каждый имеет свои плюсы и минусы. Про эти подходы написано множество книг и останавливаться на этом мы не будем.
Вернемся к нашему условному проекту. После разделения на отдельные логические элементы, работать с кодом нашего проекта стало намного проще и удобнее.
Если мы будем следовать этим принципам, введем слой абстракции для бизнес-логики, то у такого проекта есть возможность достаточно долго развиваться без существенного накопления технического долга. Для небольших программных систем или проектов на стадии MVP этой архитектуры достаточно.
Клиент-серверная архитектура
Наш интернет-проект развивается, трафик растет, появляется обратная связь от пользователей и заработанные деньги на развитие новых и улучшение существующих функций.
По мере роста объема работ, возникают проблемы с синхронизацией работ, например при работе над интерфейсом и логикой проекта. Обычно, это зоны ответственности разных специалистов, и при интенсивной работе неизбежно возникает сильная зависимость от результата работ друг друга, а также конфликты при работе с одной и той же функциональной частью системы. Следствие этих конфликтов — необходимость более объемного тестирования даже после небольших изменений, снижение частоты релизов.
Эти и другие проблемы — следствие «монолитной» архитектуры системы. Монолитная архитектура — архитектура, реализованная внутри одного программного модуля (одной программы).
Монолитная архитектура удобнее для одного разработчика или микро-команд из 2-4 человек, т.к. требует меньше издержек и рутинных действий. Когда команда растет, а как следствие растет число параллельных потоков разработки, монолитная архитектура становится узким местом и не позволяет эффективно организовать процесс.
Чтобы развиваться дальше, мы делаем шаг в сторону распределенной архитектуры, где различные компоненты информационной системы функционируют физически раздельно, используя для взаимодействия согласованный набор команд, называемый API (Application Programming Interface).
До этого момента у нас была единая система, являющаяся одним программным модулем, использующим единые логические компоненты. С этого момента она будет разделена на отдельные под-системы, но для начала только на две:
- «back-end» — серверная часть, реализующая основную ценную логику системы
- «front-end» — клиентская часть, предоставляющая пользователю интерфейс для работы с системой
Данный подход позволяет выделить в одну программную систему все, что касается пользовательского интерфейса, а в другую — полезную логику, расчеты, работу с данными. Кроме этого, единый механизм взаимодействия с back-end частью дает возможность делать дополнительные front-end под-системы, например мобильные приложения.
Плюсы
- Больше нет связности пользовательского интерфейса и логики. Разнопрофильные разработчики теперь могут работать независимо;
- Рост качества, т.к. каждый ответственнен за свою под-систему и более погружен в нее;
- API подходит как для веб, так и для мобильных приложений'
- Удобнее масштабировать под высокие нагрузки. За счет изоляции бизнес логики в отдельный физический модуль, его проще горизонтально/вертикально масштабировать.
Минусы
- Рост команды. Технологии разработки фронт-енд и бек-енд систем различны и при разделении обычно требуется наличие отдельного специалиста или команды на каждую под-систему;
- Дороже за счет роста команды. Для проектов с постоянной интенсивной работой и релизами, этот подход будет эффективнее, но для редко обновляемых проектов затраты будут выше.
Рост нагрузок и горизонтальное масштабирование
После того, как мы разделили проект на отдельные логические единицы, уровни абстракции и реализовали клиент-серверную архитектуру, нам стало намного удобнее вносить правки и развивать продукт.
По мере роста нагрузки встает необходимость оптимизировать проект под них. Основной принцип оптимизации под высокие нагрузки — горизонтальное масштабирование. Подробно как это работает мы опишем в отдельной статье. В контексте данной статьи надо иметь ввиду, что горизонтальное масштабирование технически возможно при любой архитектуре, но наиболее эффективно это реализуется при распределенной архитектуре, начиная от клиент-серверной архитектуры.
При текущей архитектуре мы масштабируем бекенд на несколько серверов. Простыми словами, разврачиваем копию серверного приложения на нескольких серверах и учим его работать параллельно. Физически это могут быть как отдельные арендованные сервера, так и облачные мощности, автоматически развертывающие дополнительные параллельные экземпляры при росте нагрузок.
С помощью горизонтального масштабирования мы можем справиться с большими нагрузками, но это усложняет обслуживание системы, выкатку обновлений. Общая логика системы усложняется, взаимодействие с инфраструктурными системами становится более изощренной. Эффективность снижается.
Важно понимать, что слово «эффективность» в разработке ПО не является аналогом «дешевой» или «экономичной» и в первую очередь относится к организации процесса разработки, т.е. к людям. Эффективность оценивается в контексте развития системы при затрачиваемых ресурсах. Основное — скорость и прогнозируемость разработки программного обеспеченияна на длинной дистанции.
В целом, для 90-95% проектов имеет смысл остановиться на этом этапе. Но есть и более сложный вариант развития архитектуры, дающий высокую эффективность на проектах с десятками и сотнями людей в команде и большими нагрузками. Как? Все тоже самое — делим и изолируем.
Микросервисная архитектура
Следующей стадией развития является физическое разделение серверного звена сисиемы (бекенда) на независимые приложения, называемые сервисами или микросервисами.
Ранее, при вводе клиент-серверной архитектуры мы уже разделили клиентскую (пользовательский интерфейс) часть и серверную, получив так называемую «трехзвенную архитектуру» (клиент-логика-данные). Теперь мы делим серверную на отдельные под-системы, каждая из которых обслуживает свою функциональную область или даже отдельные функции.
Принцип деления на сервисы может быть различным и зависит от особенностей проекта, распределения нагрузки и других вводных. В некоторых случаях, даже состав команды и возможности по найму имеют некоторое значение при выборе принципа разделения на сервисы.
Разделение может быть на основную ведущую систему и вспомогательные сервисы (например, основная логика остается в одном бекенд-приложении, а вспомогательные функции выносится в отдельные сервисы). Также можно разделить систему на более-менее равнозначные сервисы, каждый из которых отвечает за свою функциональную область.
Одно из важных преимуществ микросервисной архитектуры заключается в том, что взаимодействие между сервисами осуществляется с помощью API, которое согласовывается отдельно и меняется редко. Этот принцип чем-то похож на длительные договорные отношения: вы один раз заключили общий договор, описывающий принцип получения каких-нибудь услуг на длительный срок, и Вам не важно что и как делает подрядчик, чтобы выполнять свои обязательства по условиям договора. Важно, что взаимодействие будет предсказуемо.
Микросервисную архитектуру хорошо применять там, где она действительно повысит эффективность разработки. Делать проект командой из 2-4 человек на микросервисной архитектуре — ошибочное и неэффективное решение, т.к. значительный ресурс будет уходить на инфраструктурные издержки, а не на основную бизнес-логику.
Разделять на микросервисы функционирующий проект сложно, но это наиболее правильный путь, т.к. к этому моменту обычно уже есть понимание и об узких местах, и о профиле нагрузки, и о планах развития.
Микросервисная архитектура позволяет эффективно организовать работу большой команды и дает возможность выделить отдельные небольшие команды для каждого сервиса, которые будут заниматься только им. Такой подход существенно упрощает и повышает эффективность управления разработкой крупных интернет-проектов и корпоративных систем.
Благодаря взаимодействию сервисов через API, команды могут спокойно заниматься развитием своего микросервиса, выкатывая релизы без необходимости согласования и синхронизации с другими участниками (API не меняется, следовательно, для других сервисов тоже ничего не изменится).
Кроме того, микросервисная архитектура позволяет эффективнее использовать облачные вычислительные мощности. Микросервис, осблуживающий большую нагрузку, удобно масштабировать с помощью облачных контейнеров. Это позволяет по запросу выделять необходимое количество вычислительной мощности для нужд микросервиса.
Резюме
Для каждой ситуации специфичен свой вариант построения архитектуры информационной системы. Для новых проектов и небольших команд больше подойдет монолитная или клиент-серверная архитектура с единым серверным приложением. Далее, по мере развития проекта и роста нагрузок следует рассматривать переход на микросервисную архитектуру, с целью разделения как потоков разработки, так и выделения наиболее нагруженных функций для дальнейшего масштабирования.
Делать проект командой из 2-4 человек изначально на микросервисной архитектуре — неправильное и малоэффективное решение, т.к. больше ресурсов будет уходить на инфраструктурные издержки. Для старта проекта, особенно на этапе MVP версии, подойдет монолитная MVC (или другой паттерн) или клиент-серверная архитектура в зависимости от специфики. В случае развития проекта, можно будет рассматривать переход на микросервисную архитектуру. Для функционирующего проекта это сложно, но это лучшее решение, т.к. к этому времени есть понимание и об узких местах, и в целом о функциональности и планах развития.