Публичный интерфейс модуля содержит операции, нужные лишь одному типу клиентов. Какой дефект границы это создаёт?
Это слишком широкий интерфейс, нарушающий принцип разделения интерфейсов. Потребители зависят от операций, которые им не нужны, поэтому изменение одной группы возможностей может заставить обновлять или повторно проверять несвязанных клиентов.
Границу следует разделить на несколько клиент-ориентированных контрактов либо предоставить отдельные фасады. Тогда каждый потребитель зависит только от нужного ему набора возможностей.
Принцип разделения интерфейсов сформировался как часть набора принципов проектирования объектных систем, известного как SOLID. Его исходная проблема — уменьшить влияние изменений поставщика на клиентов, когда один крупный контракт обслуживает несколько разных сценариев.
На уровне архитектуры ПО эта идея применяется шире отдельных классов: публичный контракт модуля, сервиса или библиотеки также должен отражать реальные потребности групп потребителей.
Предположим, модуль предоставляет операции чтения, изменения и администрирования. Клиенту отчётности нужны только операции чтения, но он зависит от полного контракта.
В результате изменение административной части может потребовать публикации новой версии общего контракта, обновления клиентских библиотек, повторного тестирования и координации релизов. Формальная изоляция модулей сохраняется, но их эволюция становится связанной через избыточный интерфейс.
Важно отличать широкий интерфейс от просто большого. Большой контракт может быть оправдан, если все его операции относятся к одной cohesive-возможности и нужны одним и тем же потребителям. Дефект возникает, когда контракт объединяет независимые группы изменений или сценарии с разными клиентами.
Сначала группируют операции по потребителям и по причинам изменения. Если один клиент использует только чтение, а другой — управление, им предоставляют разные контракты: например, интерфейс чтения и интерфейс управления.
Разделение уменьшает контрактную связанность. Клиент компилируется, тестируется и изменяется относительно небольшого набора обязательств; изменение административных операций не должно затрагивать потребителя, который работает только с чтением.
Есть два распространённых варианта:
Разделение не означает механического создания интерфейса для каждой операции. Слишком мелкие контракты усложняют навигацию, документацию, авторизацию и поддержку. Границу выбирают по устойчивым сценариям использования и независимым причинам изменения, а не только по размеру интерфейса.
Если контракт уже опубликован, безопасная эволюция может потребовать версионирования, периода совместимости или адаптера. Простое добавление новых операций иногда безопасно для конкретного протокола, но всё равно может нарушить клиентов, которые обязаны реализовывать полный контракт или используют автоматическую генерацию моделей.
В платформе был единый контракт для работы с документами: просмотр, редактирование, публикация и управление правами. Сервис поиска использовал только просмотр, но зависел от общего пакета контракта.
Рассматривались варианты. Сохранить единый интерфейс было проще, но любое изменение управления правами увеличивало область регрессионного тестирования. Создать отдельные сервисы для каждой операции давало максимальную независимость, однако добавляло сетевые вызовы и усложняло эксплуатацию.
Выбрали два фасада в пределах одного модуля: контракт чтения документов и контракт управления документами. Внутренняя реализация осталась общей, но потребители получили разные зависимости. В результате изменения прав доступа перестали требовать обновления сервиса поиска; при этом не появились лишние распределённые границы.
Дополнительный вопрос 1: Достаточно ли вынести интерфейс в отдельный файл или пакет, чтобы устранить дефект?
Нет. Физическое расположение объявления не меняет связанность само по себе. Если все клиенты по-прежнему зависят от одного широкого контракта или общего пакета, изменение этого контракта продолжит распространяться на них.
Нужно разделить набор обязательств на независимые клиентские зависимости и проверить, что публикация, версия и процесс изменения этих контрактов действительно могут развиваться раздельно.
Дополнительный вопрос 2: Следует ли всегда создавать отдельный интерфейс для каждого потребителя?
Нет. Такое решение может привести к дублированию контрактов, лишним адаптерам и фрагментации модели. Интерфейсы следует группировать по устойчивым ролям и сценариям, когда несколько потребителей имеют одинаковые потребности и одинаковую причину изменения.
Если два клиента используют один и тот же набор операций, изменяющийся согласованно, разделение только ради формального уменьшения числа методов не даст архитектурной пользы.
Дополнительный вопрос 3: Чем широкий интерфейс отличается от нарушения принципа единственной ответственности?
Они связаны, но проверяют разные аспекты. Нарушение принципа единственной ответственности означает, что компонент имеет несколько независимых причин изменения. Нарушение принципа разделения интерфейсов означает, что клиент вынужден зависеть от части контракта, которая ему не нужна.
Один компонент может внутренне иметь несколько обязанностей, но предоставлять узкие фасады для разных клиентов. И наоборот, компонент с одной общей обязанностью может публиковать чрезмерно широкий контракт, если его потребители используют только непересекающиеся части.