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

Паттерн: порты и адаптеры (объектно-структурный)

Альтернативное название: гексагональная архитектура

Назначение

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

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

Рис. 1
Рис. 1

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

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

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

Интересная аналогичная проблема есть и на «обратной стороне» приложения, где бизнес-логика оказывается жестко связана с внешней базой данных или сервисом. Когда сервер базы данных недоступен, находится на модернизации или его заменяют, программисты не могут работать, поскольку их код напрямую зависит от наличия базы данных. Это вызывает простои и нередко — конфликты в команде.

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

Принцип решения

Проблемы как на стороне пользователя, так и на стороне сервера вызваны единой ошибкой проектирования и программирования — переплетением бизнес-логики и взаимодействия с внешними сущностями. Ключевая асимметрия заключается не в противопоставлении левой и правой частей приложения, а в разграничении внутреннего и внешнего пространства. Главное правило: внутренний код не должен просачиваться вовне.

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

Каждому внешнему устройству соответствует специализированный адаптер, который трансформирует определение API в сигналы, понятные этому устройству, и наоборот. Графический интерфейс (GUI) — пример адаптера, преобразующего действия человека в вызовы API порта. Другие адаптеры для того же порта включают: автоматизированные тестовые каркасы (например, FIT или Fitnessе), пакетные драйверы, а также любой код для межсервисного взаимодействия внутри предприятия или сети.

С другой стороны приложение взаимодействует с внешней сущностью для получения данных, обычно через протокол взаимодействия с базой данных. С точки зрения приложения, если база данных меняется с SQL-базы на плоский файл или иную систему, взаимодействие через API не должно меняться. Таким образом, дополнительными адаптерами для этого порта могут быть: SQL-адаптер, адаптер для плоских файлов и — что особенно важно — адаптер для мок-базы данных, которая хранится в памяти и не зависит от доступности реальной базы данных.

У многих приложений есть всего два порта: пользовательский интерфейс и интерфейс взаимодействия с базой данных. Это придает им асимметричную структуру, из-за которой кажется естественным проектировать приложения в рамках одномерной стековой архитектуры с тремя, четырьмя или пятью уровнями.

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

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

а) асимметрию «внутреннего» и «внешнего» и единообразие портов, что позволяет уйти от одномерного многоуровневого представления и всех связанных с ним ограничений;

б) наличие конечного числа различных портов — двух, трех или четырех (больше четырех автор до сих пор не встречал).

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

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

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

Структура

Рис. 2
Рис. 2

На рис. 2 изображено приложение с двумя активными портами и несколькими адаптерами для каждого из них. Эти порты представляют сторону управления приложением и сторону получения данных. Рисунок демонстрирует, что приложение может одинаково управляться автоматизированными регрессионными тестами на системном уровне, непосредственно пользователем-человеком, удаленным HTTP-приложением или другим локальным приложением. Со стороны данных приложение можно настроить для работы в отрыве от внешних баз данных с использованием базы данных с хранением в памяти (мока), либо подключить к тестовой или рабочей базе данных. Функциональные требования к приложению (например, в виде сценариев использования) формулируются на основе интерфейса внутреннего гексагона, а не какой-либо конкретной внешней технологии.

Рис. 3
Рис. 3

На рис. 3 показано то же приложение, перенесенное на трехуровневую архитектурную схему. Для упрощения на каждом порту изображено только по два адаптера. Эта схема демонстрирует размещение множества адаптеров на верхнем и нижнем уровнях, а также последовательность использования различных адаптеров в процессе разработки системы. Пронумерованные стрелки показывают порядок, в котором команда может разрабатывать и использовать приложение:

  1. Управление через тестовый каркас FIT с использованием мок-базы данных (с хранением в памяти) вместо реальной.

  2. Добавление GUI к приложению с продолжением использования мок-базы данных.

  3. Интеграционное тестирование: автоматизированные тестовые скрипты (например, CruiseControl) управляют приложением, работающим с реальной базой данных, содержащей тестовые данные.

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

Пример кода

Простейшее приложение, демонстрирующее паттерн «порты и адаптеры», к счастью, доступно в документации фреймворка FIT. Это простое приложение для расчета скидки:

Благодарю Гьяна Шарму из IHC за код для этого примера.

Этап 1. FIT + приложение + константа-как-мок-база-данных

На первом этапе мы создаем тестовые случаи в виде HTML-таблицы (подробнее см. в документации FIT):

TestDiscounter

amount

discount()

100

5

200

10

import fit.ColumnFixture;

public class TestDiscounter extends ColumnFixture {
   private Discounter app = new Discounter();
   public double amount;

   public double discount() {
      return app.discount(amount);
   }
}

По сути, это весь адаптер. Пока что мы запускаем тесты из командной строки (подробнее о пути см. в документации FIT). Мы использовали следующую команду:

FIT_HOME=/FIT/FitLibraryForFit15Feb2005
java \
  -cp "$FIT_HOME/lib/javaFit1.1b.jar:$FIT_HOME/dist/fitLibraryForFit.jar:src:bin" \
  fit.FileRunner \
  test/Discounter.html \
  TestDiscount_Output.html

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

На этом этапе код готов к фиксации в репозитории, интеграции с CruiseControl (или другой системой автоматизированной сборки) и включению в комплекс сборки и тестирования.

Этап 2. UI + приложение + константа-как-мок-база-данных

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

...
Discounter app = new Discounter(); 

public void actionPerformed(ActionEvent event) { 
  ...String amountStr = text1.getText(); 
  double amount = Double.parseDouble(amountStr); 
  discount = app.discount(amount)); 
  text3.setText( "" + discount ); 
...

Теперь приложение готово для демонстрации и регрессионного тестирования. Оба пользовательских адаптера запущены и работают.

Этап 3. (FIT или UI) + приложение + мок-база данных

Для создания сменного адаптера на стороне базы данных мы определяем интерфейс репозитория — RepositoryFactory, который будет возвращать либо мок-базу данных, либо реальный сервисный объект. Также мы реализуем мок-базу данных, хранимую в памяти.

public interface RateRepository { 
  double getRate(double amount); 
}
public class RepositoryFactory { 
  public RepositoryFactory() { super(); } 
  
  public static RateRepository getMockRateRepository() { 
    return new MockRateRepository(); 
  } 
}
public class MockRateRepository implements RateRepository { 
  public double getRate(double amount) { 
    if(amount <= 100) return 0.01; 
    if(amount <= 1000) return 0.02; 
    return 0.05; 
  } 
}

Для подключения этого адаптера к приложению Discounter нам потребуется обновить само приложение, добавив возможность принимать адаптер репозитория, после чего настроить пользовательский адаптер (FIT или UI) для передачи соответствующего репозитория (реального или мок-) в конструктор приложения. Далее представлены обновленная версия приложения и адаптер FIT, передающий мок-репозиторий (код адаптера FIT, отвечающий за выбор между моком и реальным репозиторием, оказался длинным, не добавляя ничего принципиально нового, поэтому эта версия здесь опущена).

import repository.RepositoryFactory; 
import repository.RateRepository; 

public class Discounter { 
  private RateRepository rateRepository; 
  
  public Discounter(RateRepository r) { 
    super(); 
    rateRepository = r; 
  } 
  
  public double discount(double amount) { 
    double rate = rateRepository.getRate( amount ); 
    return amount * rate; 
  } 
}
import app.Discounter; 
import fit.ColumnFixture; 

public class TestDiscounter extends ColumnFixture { 
  private Discounter app = 
    new Discounter(RepositoryFactory.getMockRateRepository()); 
  public double amount; 
  
  public double discount() { 
    return app.discount( amount );
  } 
}

На этом реализация простейшего варианта гексагональной архитектуры завершена.

Заметки по применению

Асимметрия лево-право

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

Как мог заметить внимательный читатель, во всех приведенных примерах фикстуры FIT применяются на левых портах, а на правых — моки. В трехуровневой архитектуре FIT занимает верхний уровень, а моки — нижний.

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

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

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

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

Сценарии использования и граница приложения

Полезно использовать паттерн гексагональной архитектуры для закрепления предпочтительного подхода к написанию сценариев использования.

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

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

Сколько портов?

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

Ни один из крайних подходов не является оптимальным.

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

Похоже, выбор «неправильного» количества портов не несет в себе особых рисков, поэтому здесь можно положиться на интуицию. Я предпочитаю использовать небольшое количество портов — два, три или четыре, — как это было показано выше и будет продемонстрировано в примерах ниже.

Известные применения

Рис. 4
Рис. 4

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

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

Их подход к проектированию изменился: теперь интерфейсы системы проектировались по их назначению, а не по технологии, а сами технологии можно было заменять (со всех сторон) с помощью адаптеров. Они сразу же получили возможность подключить HTTP-канал и уведомления по электронной почте (новые адаптеры на схеме обозначены пунктирными линиями). Благодаря тому, что каждое приложение могло работать в headless-режиме через API, они смогли добавить адаптер для связи приложений и разбить комплект на части, соединяя подприложения по требованию. Наконец, обеспечив возможность полностью изолированного запуска каждого приложения с использованием тестовых и мок-адаптеров, они получили возможность проводить регрессионное тестирование с помощью автономных автоматизированных тестовых скриптов.

Mac, Windows, Google, Flickr, Web 2.0

В начале 1990-х годов приложения для Macintosh, такие как текстовые процессоры, должны были иметь интерфейсы, управляемые через API, чтобы другие приложения и пользовательские скрипты имели доступ ко всем их функциям. Приложения для Windows также развили эту возможность (у меня нет достаточных исторических знаний, чтобы утверждать, кто был первым, но в данном контексте это и не важно).

Современная (на 2005 год) тенденция в веб-разработке заключается в том, чтобы публиковать API и предоставлять другим веб-приложениям прямой доступ к ним. Это позволяет, например, отображать данные о местной преступности на картах Google или создавать веб-приложения, использующие возможности Flickr для хранения и аннотирования фотографий.

Объединяет все эти примеры то, что API первичных портов делаются видимыми. При этом мы не видим никакой информации о вторичных портах.

Сохраненный вывод

Этот пример описан Виллемом Богертсом на C2 wiki:

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

Мне это совершенно не нравилось. Тогда я нашел решение: использовать контроллер представления с функцией хранения. Теперь приложение не перенаправляет вывод в разные направления, а просто выводит его в контроллер представления. Именно этот контроллер буферизует результат и предоставляет пользователю возможность его сохранить.

Традиционная многоуровневая архитектура подчеркивает различие между “пользовательским интерфейсом” и “системой хранения”. Архитектура портов и адаптеров позволяет вернуться к простой концепции “вывода данных”».

Анонимный пример с C2 wiki

«В одном из проектов, над которым я работал, мы использовали системную метафору компонентной стереосистемы. Каждый компонент имеет четко определенные интерфейсы, каждый из которых служит конкретной цели. Затем мы можем соединять компоненты почти бесконечным числом способов с помощью простых кабелей и адаптеров».

Распределенная разработка в большой команде

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

Разделение разработки пользовательского интерфейса и бизнес-логики

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

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

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

Родственные паттерны

Адаптер

Книга «Паттерны проектирования» содержит описание общего паттерна «Адаптер»: «Преобразует интерфейс одного класса в другой интерфейс, на который рассчитаны клиенты». Паттерн порты и адаптеры представляет собой частный случай применения паттерна «Адаптер».

Model-View-Controller

Паттерн MVC был реализован еще в 1974 году в рамках проекта Smalltalk. За последующие годы появилось множество вариаций этого паттерна, такие как Model-Interactor и Model-View-Presenter. Каждая из этих вариаций реализует концепцию портов и адаптеров для первичных портов, но не для вторичных.

Мок-объекты и Петля

«Мок-объект — это “двойной агент”, используемый для проверки поведения других объектов. Во-первых, мок-объект выступает в роли фейковой реализации интерфейса или класса, которая имитирует внешнее поведение реальной реализации. Во-вторых, мок-объект отслеживает, как другие объекты взаимодействуют с его методами, и сравнивает фактическое поведение с заданными ожиданиями. При обнаружении расхождения мок-объект может прервать тест и сообщить об аномалии. Если расхождение не было выявлено во время теста, то метод верификации, вызываемый тестировщиком, подтверждает, что все ожидания были выполнены, либо сообщает об ошибках» — цитата с сайта http://MockObjects.com.

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

Паттерн «Петля» предназначен специально для создания внутренней замены внешнего устройства.

Постаменты

В статье «Паттерны для построения многоуровневой архитектуры» Берри Рубел описывает паттерн создания оси симметрии в управляющем программном обеспечении, который очень напоминает порты и адаптеры. Паттерн «Постамент» предполагает создание объекта, представляющего каждое аппаратное устройство в системе, и связывание этих объектов в едином управляющем слое. Этот паттерн можно использовать для описания любой из сторон гексагональной архитектуры, однако он не акцентирует единообразие различных адаптеров. Кроме того, будучи созданным для механической среды управления, паттерн неочевидным образом применим к IT-приложениям.

Проверки

Разработанный Уордом Каннингемом язык паттернов для обнаружения и обработки ошибок в пользовательском вводе хорошо подходит для обработки ошибок на границах внутреннего гексагона.

Инверсия зависимости (внедрение зависимости) и Spring

Сформулированный Бобом Мартином принцип «инверсии зависимости» (также названный Мартином Фаулером «внедрением зависимости») гласит: «Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций». Паттерн «Внедрение зависимости», описанный Мартином Фаулером, предлагает конкретные варианты реализации этого принципа. Они показывают, как создавать взаимозаменяемые адаптеры для вторичных акторов. Код можно прописывать напрямую (как показано в примерах в статье) или использовать конфигурационные файлы, чтобы фреймворк Spring сгенерировал эквивалентный код.

Благодарности

Выражаю благодарность Гьяну Шарме из Intermountain Health Care за предоставленный пример кода, а также Ребекке Вирфс-Брок за ее книгу «Дизайн объектов». Изучение этой работы в сочетании с главой о паттерне «Адаптер» из книги «Паттерны проектирования» помогло мне понять суть гексагональной архитектуры. Также благодарю авторов wiki-ресурса Уорда за комментарии об этом паттерне, которые они оставляли на протяжении многих лет (в частности, Кевину Резерфорду за текст — http://silkandspinach.net/blog/2004/07/hexagonal_soup.html).

Источники и дополнительная литература

Cunningham W. FIT, A Framework for Integrating Testing, онлайн по адресу: http://fit.c2.com; Mugridge R., Cunningham W. Fit for Developing Software. Prentice-Hall PTR, 2005.

См. паттерн «Адаптер» в: Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Паттерны объектно-ориентированного проектирования. СПб.: Питер, 2021. С. 171–184.

Паттерн «Постамент»: Rubel B. Patterns for Generating a Layered Architecture // Coplien J., Schmidt D. Pattern Languages of Program Design. Addison-Wesley, 1995. P. 119–150.

Паттерн «Проверки»: Cunningham W. The Checks. Pattern Language of Information Integrity, онлайн по адресу: http://c2.com/ppr/checks.html.

Принцип «инверсии зависимости»: Мартин Р. Чистая архитектура. СПб.: Питер, 2022. С. 100–104, а также онлайн по адресу: http://www.objectmentor.com/resources/articles/dip.pdf.

Паттерн «Внедрение зависимости»: Fowler M. Inversion of Control Containers and the Dependency Injection Pattern, онлайн по адресу: http://www.martinfowler.com/articles/injection.html.

Паттерн «Мок-объект»: Freeman S. онлайн по адресу: http://MockObjects.com.

Паттерн «Петля»: Cockburn A. Loop Back, онлайн по адресу: http://c2.com/cgi/wiki?LoopBack.

Про «сценарии использования» см.: Cockburn A. Writing Effective Use Cases. Addison-Wesley, 2001; Cockburn A. Structuring Use Cases with Goals, онлайн по адресу: http://alistair.cockburn.us/crystal/articles/sucwg/structuringucswithgoals.htm.

Комментарии (1)


  1. SolidSnack
    12.09.2025 15:23

    Спасибо за перевод и список литературы! Самое то для меня сейчас:)