Архитектура как бремя

в 12:38, , рубрики: clean architecture, ejb, ERP-системы, legacy-код, Rule Engine, vendor lock, Анализ и проектирование систем, антипаттерны, архитектура по, архитектура приложений, архитектура системы, базы данных, командная работа, Проектирование и рефакторинг, разработка приложений, Совершенный код, управление проектами, чистая архитектура

За время своей карьеры я поработал с разными legacy-проектами, каждый из которых страдал от тех или иных изъянов.

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

В сущности, улучшение кода — дело довольно простое, особенно сейчас, когда движение за мастерство разработки ПО (software craftsmanship) продвигает хорошие практики в командах. Однако изменение стержневых частей систем, ограничений, введенных в самом начале их жизненного цикла, является чрезвычайно сложной задачей.

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

Вашей базой данных совместно пользуется вся компания

Это, пожалуй, одна из наиболее распространенных из виденных мной проблем. Когда нескольким приложениям нужно использовать общие данные, почему бы нам просто не дать им совместный доступ к общей базе данных? В конце концов, в разработке ПО дублирование считается злом, так ведь? Ну, не всегда это верно, и особенно в тех случаях, когда дело касается базы данных. Венкат Субраманиам (Venkat Subramaniam) выразил эту мысль следующим образом: «База данных — как зубная щетка: никогда не давайте ею пользоваться кому-то другому». Что такого уж плохого в совместном использовании базы данных? На самом деле, довольно многое…

Первая вещь, которая приходит на ум, — зацепление (coupling) модели данных. Представьте, что два приложения, А и Б, обрабатывают данные по автомобилям. Приложение А используется командой, отвечающей за ремонтные работы, и поэтому им нужно хранить много технической информации: о механике, поломках, истории вмешательств в автомобиль… Приложение Б используется для постановки задач технической команде, так что ему нужна только базовая информация об автомобиле, достаточная для его идентификации. В этом случае использование одной и той же структуры данных для обеих программ не имеет смысла: они пользуются разными данными, поэтому каждая должна использовать свою собственную структуру данных. Сделать это тем проще, что автомобиль легко идентифицировать, так что в общем справочнике нет нужды.

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

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

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

Ваша система привязана к ПО, которое используется в компании

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

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

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

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

Сильное зацепление между несколькими приложениями

Вспомните начало 2000-х годов: как приятно было использовать Enterprise Java Beans (EJB) для обработки удаленных вызовов между приложениями вашей информационной системы. В то время это могло казаться неплохой идеей. Совместное использование вашего кода с другими командами с целью избежать дублирования тоже выглядит хорошо. Да, все команды были вынуждены выкатывать свои приложения в одно и то же время, чтобы убедиться, что двоичные зависимости не сломались, но это были веселые вечера — поедание пиццы с коллегами во время 2-часового ожидания завершения поставки приложения, не так ли?

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

Как только вы осознаете, какой беспорядок вызывают эти рано принятые решения, усилия, необходимые для отделения вашего приложения от всего остального мира, оказываются огромными. Вы видите, что в буквальном смысле годы уйдут на то, чтобы нарезать ваш проект на различные компоненты так, чтобы другие приложения больше не смогли использовать ядро вашей предметной области, ваш клиент или механизм кэширования; чтобы удалить все обращения к внешним классам, которые приводят к сильному зацеплению с другими проектами; чтобы заменить все вызовы EJB на интерфейсы REST API… Однако за все эти усилия каждый сотрудник, связанный с проектом, будет вознагражден с лихвой: упростятся разработка и тестирование, ускорится процесс поставки, так как отныне больше нет нужды синхронизировать свою работу с кем-то еще; станут лучше разделены понятия в вашем собственном коде; упростится управление зависимостями, исчезнут проблемы с транзитивными зависимостями, когда вы импортируете тучу зависимостей других приложений в ваш classpath… Все эти дорогостоящие изменения просто спасут жизнь команды, и они могли бы быть гораздо проще в реализации, сделай вы их на заре проекта!

Вы строите свой проект на базе чьего-то чужого проекта

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

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

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

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

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

Вся ваша бизнес-логика хранится внутри системы управления правилами

Поместить некоторое количество своей бизнес-логики в систему управления правилами (rule management system) — это общая практика. Это, например, полезно, если какие-то из ваших бизнес-правил требуется часто обновлять, а процесс поставки вашего монолитного приложения требует долгой фазы тестирования перед тем, как можно будет проверить кандидат в релиз, и это делает невозможным настройку некоторых из ваших «изменчивых» правил. Хотя я и предпочитаю подход, когда все правила предметной области находятся в исходном коде, я могу понять, что в некоторых ситуациях система управления правилами может быть полезна.

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

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

Если это уже кажется проблемой, то вот еще кое-что: ни в одном правиле не было совершенно никакой подсказки о его предназначении. Правила были названы наподобие Rule1, Rule2, и так далее, общим количеством более 100! И в основном каждое правило состояло из проверок и присвоения жестко закодированных (hard coded) величин без каких-либо терминов предметной области. Даже название проекта не проясняло предназначения всего ETL в целом.

Заключение

Как разъяснял дядюшка Боб в своей книге «Чистая архитектура», при обдумывании архитектуры своего проекта следует отложить принятие некоторых решений до тех пор, пока нам действительно не понадобится сделать выбор, без которого мы не можем продолжать добавлять ценность в наш продукт (как выбор базы данных, например). Другие решения должны быть приняты действительно рано — не ждите, пока все станет плохо. К счастью, этот вид критических решений легко выявить, потому что они являются тем, что можно назвать «архитектура с душком»: когда вы обдумываете подобные идеи, вы не видите в них ничего хорошего — лишь проблему, которая рано или поздно обязательно вернется и будет преследовать вас. К сожалению, при работе с legacy-проектами этот вид бремени часто погребен глубоко в коде, что делает его устранение очень затратным делом.

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

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

Автор: bevalorous

Источник


* - обязательные к заполнению поля