Преобразователи данных
Вернемся к теме баз данных. На каком уровне, по вашему мнению, должны находиться фреймворки ORM, такие как Hibernate?
Во-первых, давайте кое-что проясним: нет такой штуки, как инструмент объектно-реляционного преобразования (Object Relational Mapper; ORM), потому что объекты не являются структурами данных, по крайней мере с точки зрения пользователя. Пользователи объекта не видят данных, потому что все они хранятся в приватных полях. Пользователи видят только общедоступные методы объекта. То есть с точки зрения пользователя объект — это набор операций.
Структура данных, напротив, — набор общедоступных данных, не обладающих подразумеваемым поведением. Название «преобразователи данных» лучше подходит для инструментов ORM, потому что они загружают данные из реляционных таблиц в структуры.
Где должны находиться такие системы ORM? Конечно, на уровне базы данных. В действительности инструменты ORM представляют еще одну разновидность границы «Скромный объект» между интерфейсами шлюза и базой данных.
Службы
А что можно сказать о службах? Если приложение взаимодействует со службами или реализует свой набор служб, можно ли в этом случае применить шаблон «Скромный объект» для создания границы, отделяющей службу?
Конечно! Приложение должно загружать данные в простые структуры и передавать эти структуры через границу модулям, которые преобразуют данные и посылают их внешним службам. С другой стороны, в точке ввода слушатель службы должен принимать данные через интерфейс службы, преобразовывать их в простые структуры данных, пригодные для использования в приложении, и затем передавать их через границу службы.
Заключение
Практически на каждой архитектурной границе можно найти возможность применить шаблон «Скромный объект». Взаимодействия через границу почти всегда осуществляются с применением некой простой структуры данных, и граница часто делит что-то на сложное и простое для тестирования. Применение этого шаблона для организации архитектурных границ значительно улучшает возможность тестирования системы в целом.
Глава 24. Неполные границы
Полноценные архитектурные границы обходятся дорого. Они требуют определения двусторонних пограничных интерфейсов, структур для входных и выходных данных и управления зависимостями для выделения двух сторон в компоненты, компилируемые и развертываемые независимо. Это требует значительных усилий для создания и сопровождения.
Во многих ситуациях хороший архитектор мог бы посчитать затраты на создание такой границы слишком высокими, но хотел бы сохранить место для такой границы на будущее.
Подобное упреждающее проектирование часто расценивается многими последователями гибкой разработки как нарушение принципа 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. Шаблон «Фасад»