Что такое границы
Проектирование архитектуры ПО само по себе является искусством проведения границ.
Границы разделяют ПО на элементы, ограничивая зависимости между сторонами. Проведение границ в начале проекта позволяет максимально отложить некоторые решения, чтобы в будущем они не мешали основной бизнес-логике системы. Оно также уменьшает или устраняет ненужную связанность архитектуры. Именно связанность, особенно порождённая преждевременными и незрелыми решениями вроде выбора фреймворка или базы данных, поглощает больше всего трудозатрат. Такие детали следует откладывать.
Как проводить границы? Можно опираться на следующие основные принципы:
- Границы следует проводить между несвязанными вещами, например GUI и бизнес-логикой. Ввод и вывод (GUI) несущественны для бизнес-логики: с ней могут работать разные реализации GUI.
- Границы следует проводить вдоль осей изменения системы. Компоненты по разные стороны должны меняться по разным причинам и с разной скоростью. Оси изменения помогают определить SRP на уровне модулей и классов и CCP на уровне компонентов.
Разобравшись с ролью границ и принципами их проведения, можно перейти к основному процессу:
- Сначала разделите систему на компоненты: одни содержат основную бизнес-логику, другие являются плагинами, не относящимися к ней, но предоставляющими необходимые функции.
- Измените исходный код так, чтобы неосновные компоненты зависели от компонентов основной бизнес-логики.
Проведение границ — конкретное применение принципа инверсии зависимостей и принципа устойчивых абстракций. Стрелка зависимости должна идти от низкоуровневой конкретной реализации к высокоуровневой абстракции.
Разбор границ
Архитектуру системы определяют программные компоненты и границы между ними
Границы бывают разными. В этой главе они рассматриваются на примере вызовов через границу.
Во время выполнения вызов через границу означает, что функция с одной стороны вызывает функцию с другой и передаёт ей данные. Чтобы устроить такие вызовы правильно, нужно управлять зависимостями исходного кода. Иначе изменение кода одного модуля может потребовать изменения или перекомпиляции других модулей и их повторного развёртывания. Чёткие границы помогают сократить число таких случаев.
Вызовы через границы можно разделить на уровни исходного кода, развёртывания, сервисов и физические границы.
Вызовы через границы на уровне исходного кода
Такие вызовы встречаются в монолитной архитектуре. Для управления внутренними зависимостями ей обычно требуется динамический полиморфизм в той или иной форме.
Самый простой случай — низкоуровневый клиент вызывает высокоуровневую сервисную функцию. И во время выполнения, и при компиляции зависимость направлена одинаково: от низкоуровневого компонента к высокоуровневому.
(Исходная схема пока отсутствует)
Но когда клиент высокоуровневого компонента должен вызвать службу низкоуровневого, динамический полиморфизм необходим для инверсии зависимости. Интерфейс Service на схеме представляет собой одну из форм SPI.
(Исходная схема пока отсутствует)
Вызовы через границы на уровне развёртывания
На уровне развёртывания каждая развёртываемая единица упаковывается в удобный для работы формат файла. Помимо этого, компоненты, разделённые на таком уровне, почти не отличаются от монолитной структуры.
Эти вызовы затрагивают физические границы, потому что отдельные развёртываемые единицы должны пересекать их для взаимодействия. Типичные физические границы — динамические библиотеки, потоки и локальные процессы.
Вызовы через границы на уровне сервисов
Сервисы образуют самую сильную границу в архитектуре: каждый сервис является процессом. При проведении архитектурных границ необходимо максимально ограничивать число взаимодействий. На этом уровне обмен должен выдерживать высокие задержки. В остальном применимы те же правила, что для локальных процессов: низкоуровневые сервисы должны становиться плагинами высокоуровневых.