Полноценные архитектурные границы обходятся дорого. Они требуют определения двусторонних пограничных интерфейсов, структур для входных и выходных данных и управления зависимостями для выделения двух сторон в компоненты, компилируемые и развертываемые независимо. Это требует значительных усилий для создания и сопровождения.
Во многих ситуациях хороший архитектор мог бы посчитать затраты на создание такой границы слишком высокими, но хотел бы сохранить место для такой границы на будущее.
Подобное упреждающее проектирование часто расценивается многими последователями гибкой разработки как нарушение принципа YAGNI: «You Aren’t Going to Need It» («Вам это не понадобится»). Однако некоторые архитекторы смотрят на эту проблему и думают: «А мне может это понадобиться». В этом случае они могут реализовать неполную границу.
Один из способов сконструировать неполную границу — проделать все, что необходимо для создания независимо компилируемых и развертываемых компонентов, и затем просто оставить их в одном компоненте. В этом компоненте будут присутствовать парные интерфейсы, структуры входных и выходных данных и все остальное, но все это будет компилироваться и развертываться как один компонент.
Очевидно, что для реализации такой неполной границы потребуется тот же объем кода и подготовительного проектирования, что и для полной границы. Но в этом случае не потребуется администрировать несколько компонентов. Не потребуется следить за номерами версий и нести дополнительное бремя управления версиями. Это отличие не нужно недооценивать.
Эта стратегия использовалась в начале развития FitNesse. Компонент веб-сервера FitNesse проектировался с возможностью отделения от компонентов вики и тестирования. Мы думали, что впоследствии у нас может появиться желание создать другие веб-приложения, использующие этот веб-компонент. В то же время мы не хотели вынуждать пользователей загружать два компонента. Как вы помните, одна из наших целей выражалась фразой «Загрузи и вперед». Мы специально хотели, чтобы пользователям нужно было загрузить и выполнить только один jar-файл, не заботясь о поиске других jar-файлов с совместимыми версиями и т.д.
История FitNesse также указывает на одну из опасностей такого подхода. Со временем, когда стало ясно, что отдельный веб-компонент никогда не понадобится, грань между веб-компонентом и компонентом вики стала размываться. Начали появляться зависимости, пересекающие линию в неправильном направлении. Разделить их сейчас было бы очень сложно.
Для оформления полноценной архитектурной границы требуется создать парные пограничные интерфейсы для управления изоляцией в обоих направлениях. Поддержание разделения в обоих направлениях обходится дорого не только на начальном этапе, но и на этапе сопровождения.
На рис. 24.1 показана более простая схема, помогающая зарезервировать место для последующего превращения в полноценную границу. Это пример традиционного шаблона «Стратегия». Клиенты пользуются интерфейсом ServiceBoundary, который реализуют классы ServiceImpl.
Рис. 24.1. Шаблон «Стратегия»
Должно быть ясно, что это создает основу для будущей архитектурной границы. Здесь имеет место инверсия зависимости, необходимая для отделения клиента от класса ServiceImpl. Также должно быть ясно, что разделение может очень быстро стираться, о чем свидетельствует пунктирная стрелка на диаграмме. В отсутствие парных интерфейсов ничто не мешает появлению таких обратных зависимостей, кроме старательности и дисциплинированности разработчиков и архитекторов.
Еще более простой подход к организации границ дает шаблон «Фасад», изображенный на рис. 24.2. В этом случае отсутствует даже инверсия зависимостей. Граница определяется простым классом Facade c методами, представляющими службы и реализующими обращения к службам, к которым клиенты, как предполагается, не должны иметь прямого доступа.
Рис. 24.2. Шаблон «Фасад»
Обратите внимание, однако, что клиент имеет транзитивную (переходную) зависимость от всех этих классов служб. В языках со статической системой типов изменение исходного кода в одном из классов служб вызывает необходимость повторной компиляции клиента. Также представьте, насколько просто в этой схеме создать обратные связи.
Мы увидели три простых способа реализации неполных архитектурных границ. Конечно, таких способов намного больше. Эти три стратегии служат лишь примерами.
Каждый из представленных подходов имеет свои достоинства и недостатки. Каждый подходит на роль заменителя полноценной архитектурной границы в определенных контекстах. И каждый может со временем деградировать, если граница никогда не будет материализована.
Одна из задач архитектора — решить, где провести архитектурную границу и как ее реализовать, частично или полностью.