Программная архитектура (часть 3)

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

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

В общем все, что создает или изменяет программист, может в какой-то мере затрагивать архитектуру. Я не утверждаю, что любой код, любые правки – это ну прямо вопрос «жизни и смерти», но даже незначительные на первый взгляд решения, могут иметь серьезные последствия.

Рассмотрим такой пример. Программисту делегировали разработку микросервиса. Нас сейчас не интересует структура сервиса, что и как делал программист – не важно. В процессе разработки оформились какие-то функции, которые программист решил обобщить и вынести в виде общеиспользуемых библиотечных функций. То есть то, что нужно ему самому в его сервисе он оформил в библиотечный пакет и вынес в отдельный репозиторий.

Его мотивация понятна: он сделал что-то, что посчитал полезным не только для себя, но и для других. Он не поленился, оформил это в виде библиотечного пакета и вынес вовне своего сервиса. Допустим, что он угадал и это действительно оказалось полезным не только ему. Он молодец?! Пока да)

Проходит какое-то время, появляются новые сервисы, многие из них используют ту самую библиотеку, которую программист в свое время создал и разместил для общего пользования. Вроде бы все замечательно. Проблема только в том, что появилась новая устойчивая зависимость на уровне пакетов. И любые изменения в логике библиотечных функций или их сигнатуре затронут все зависимые от них сервисы. То есть вы не может просто так взять и поменять функцию, нужно идти по всем сервисам и вносить соответствующие правки в них. Это называется связанность (coupling). Если с ней не очень хорошо, то ее называют «высокой связанностью» (high coupling).

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

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

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

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

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

Версирование глобальных зависимостей в виде shared пакетов, отчасти решает проблему: если вносишь изменение – инкрементируй версию, а зависимые сервисы переводятся на новую версию по возможности. Но это тоже «не бесплатно», во-первых, версированием нужно заниматься, т.е. должна быть соответствующая культура, нельзя просто так взять и внести правки в текущую версию. А практика показывает, что можно) Люди разные и делают они очень по-разному. Во-вторых, могут быть зависимости между библиотечными пакетами, т.е. вы не может обновить какой-то один пакет, не обновив при этом другой, который изначально и не собирались трогать.

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

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

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

Валерий Чугреев, 06.03.2021

Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии