Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Shablony_korporativnykh_prilozheniy_Fauler_M.docx
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
3.82 Mб
Скачать

Слой служб (Service Layer)

Рэнди Стаффорд

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

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

Слой служб определяет границы приложения [12] и множество операций, предостав­ляемых им для интерфейсных клиентских слоев кода. Он инкапсулирует бизнес-логику приложения, управляет транзакциями и координирует реакции на действия.

Принцип действия

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

Разновидности "бизнес-логики"

Подобно сценарию транзакции (Transaction Script) и модели предметной области (Domain Model), слой служб представляет собой типовое решение по организации бизнес-логики. Многие проектировщики, и я в том числе, любят разносить бизнес-логику по двум категориям: логика домена (domain logic) имеет дело только с предметной областью как таковой (примером могут служить стратегии вычисления зачтенного дохода по контракту), а логика приложения (application logic) описывает сферу ответственности приложения [11] (скажем, уведомляет пользователей и сторонние приложения о проте­кании процесса вычисления доходов). Логику приложения часто называют также "логикой рабочего процесса", несмотря на то что под "рабочим процессом" часто пони­маются совершенно разные вещи.

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

Варианты реализации

Двумя базовыми вариантами реализации слоя служб являются создание интерфейса доступа к домену (domain facade) и конструирование сценария операции (operation script). При использовании подхода, связанного с интерфейсом доступа к домену, слой служб реализуется как набор "тонких" интерфейсов, размещенных "поверх" модели предметной области. В классах, реализующих интерфейсы, никакая бизнес-логика отражения не на­ходит — она сосредоточена исключительно в контексте модели предметной области. Тон­кие интерфейсы устанавливают границы и определяют множество операций, посредст­вом которых клиентские слои взаимодействуют с приложением, обнаруживая тем самым характерные свойства слоя служб.

Создавая сценарий операции, вы реализуете слой служб как множество более "толстых" классов, которые непосредственно воплощают в себе логику приложения, но за бизнес-логикой обращаются к классам домена. Операции, предоставляемые клиентам слоя служб, реализуются в виде сценариев, создаваемых группами в контексте классов, каж­дый из которых определяет некоторый фрагмент соответствующей логики. Подобные классы, расширяющие супертип слоя (Layer Supertype) и уточняющие объявленные в нем абстрактные характеристики поведения и сферы ответственности, формируют "служ­бы" приложения (в названиях служебных типов принято употреблять суффикс "Servi­ce"). Слой служб и заключает в себе эти прикладные классы.

Быть или не быть удаленному доступу

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

За удаленные вызовы приходится платить, применяя технологии распределения объек­тов. Чтобы обеспечить возможность совмещения методов слоя служб с контекстом объек­тов переноса данных (Data Transfer Object), требуется приложить немалые усилия. Не стоит недооценивать затраты на выполнение этой работы, особенно при использова­нии сложной модели предметной области и насыщенных интерфейсов редактирования данных для "навороченных" вариантов использования! Это весьма трудоемкое и хлопот­ное дело — по крайней мере второе по степени сложности после объектно-реляционного отображения. Да, и не забывайте о Первом Законе Распределения Объектов (см. главу 7, с.113).

Советую начинать со слоя служб, адресуемого в локальном режиме, и включать воз­можность удаленного вызова только при необходимости (если таковая вообще возник­нет), размещая "поверх" слоя служб соответствующие интерфейсы удаленного доступа (Remote Facade) или снабжая нужными интерфейсами объекты слоя служб как тако­вые. Если приложение обладает графическим Web-интерфейсом или использует шлюзы для интеграции, основанные на Web-службах, я не могут назвать правило, которое за­ставляло бы располагать бизнес-логику в контексте процесса, отделенного от страниц сервера или Web-служб. Отдавая предпочтение режиму совместного выполнения, вы сэкономите много сил и времени, не утратив возможность масштабирования.

Определение необходимых служб и операций

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

Как это ни грустно, большинство вариантов использования корпоративных прило­жений составляют банальные операции CRUD (create, read, update, delete — создать, счи­тать, обновить, удалить) над объектами домена: создать экземпляр того, считать коллек­цию этих или обновить что-нибудь еще. На практике варианты использования CRUD практически всегда полностью соответствуют операциям слоя служб.

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

К сожалению, определить, в какие абстракции слоя служб необходимо сгруппировать родственные операции, далеко не просто. Универсального рецепта для решения этой проблемы не существует. Небольшому приложению вполне может хватить одной абст­ракции, названной по имени самого приложения. Более крупные приложения обычно разбиваются на несколько подсистем, каждая из которых включает в себя вертикальный срез всех имеющихся архитектурных слоев. В этом случае я предпочитаю создавать по одной одноименной абстракции для каждой подсистемы. В качестве альтернатив можно предложить создание абстракций, отражающих основные составляющие модели пред­метной области, если таковые отличаются от подсистем (например, ContractService, ProductsService) или абстракций, названных в соответствии с типами поведения (например, RecognitionService).

Особенности Java-реализации

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

Я предпочитаю реализовать слой служб с использованием локальных интер­фейсов и сценария операции в виде компонентов сеанса EJB 2.0, обращающих­ся за логикой домена к классам объектов домена POJO. Что ни говори, а слой служб очень удобно реализовать в виде компонента сеанса, не меняющего со­стояния, потому что в EJB предусмотрена поддержка распределенных транзак­ций, управляемых на уровне контейнера. Кроме того, с помощью локальных интерфейсов, появившихся в EJB 2.0, слой служб может использовать весьма ценные службы транзакций и в то же время избежать всех неприятностей, свя­занных с вопросами распределения объектов.

Говоря о Java, хочу обратить ваше внимание на отличие слоя служб от интерфейса доступа посредством компонентов сеанса (Session Facade) — типового решения для J2EE, описанного в [3, 28]. Разработчики интерфейса доступа по­средством компонентов сеанса намеревались избежать падения производитель­ности, связанной с чрезмерным количеством удаленных вызовов компонентов сущностей; поэтому в данном типовом решении доступ к компонентам сущно­стей происходит с помощью компонентов сеанса. В отличие от этого, слой служб направлен на определение как можно большего количества общих опе­раций, для того чтобы избежать дублирования кода и обеспечить возмож­ность повторного использования; это архитектурное решение, которое выхо­дит за рамки технологии. Действительно, типовое решение граница приложения (Application Boundary), описанное в [12] и ставшее прототипом слоя служб, поя­вилось примерно на три года раньше, чем EJB. Интерфейс доступа посредством компонентов сеанса имеет много общего со слоем служб, однако на данный мо­мент его имя, назначение и позиционирование принципиально иные.

Назначение

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

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

Тем не менее, как только у вас появится вторая категория клиентов или начнет ис­пользоваться второй ресурс транзакции, вам неизбежно придется ввести слой служб, что потребует полной переработки приложения.

Дополнительные источники информации

Слой служб имеет не так уж много прототипов. В его основу легло типовое решение граница приложения (Application Boundary), описанное Алистером Кокберном (Alistair Cockburn) в [12]. В источнике [2], посвященном удаленным службам, обсуждается роль интерфейсов доступа в распределенных системах. Сравните эти концепции с описания­ми типового решения интерфейс доступа посредством компонентов сеанса (Session Facade), содержащимися в [3, 28]. Рассмотрение вариантов использования в соответствии с типа­ми поведения, приведенное в справочнике [11], может помочь в определении обязанно­стей приложения, координацию которых должен проводить слой служб. Похожие идеи под названием "системных операций" представлены в более раннем справочнике [13], посвященном технологии Fusion.

Пример: определение зачтенного дохода (Java)

Продолжим рассмотрение примера определения зачтенного дохода, который был на­чат в разделах, посвященных типовым решениям сценарий транзакции и модель предмет­ной области. На сей раз я продемонстрирую использование слоя служб, реализовав его операцию в виде сценария, который инкапсулирует в себе логику приложения и обраща­ется за логикой домена к классам домена. Для реализации слоя служб (вначале с помо­щью объектов POJO, а затем компонентов EJB) будет использован подход сценария опе­рации.

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

Для начала немного изменим класс RecognitionService ИЗ примера определения зачтенного дохода для сценария транзакции. Расширим его супертип слоя и добавим не­сколько шлюзов (Gateway), инкапсулирующих в себе логику приложения. Полученная схема классов показана на рис. 9.7. Класс RecognitionService станет РОЮ-реализацией службы из слоя служб, а его методы будут представлять две операции этого слоя.

Рис. 9.7. Схема связей POJO-класса RecognitionService

Методы класса RecognitionService представляют логику приложения в виде сце­нариев, обращающихся за логикой домена к классам объектов домена, описанным в примере для модели предметной области.

public class ApplicationService {

protected EmailGateway getEmailGateway() {

//return an instance of EmailGateway

}

protected IntegrationGateway getIntegrationGateway() {

//return an instance of IntegrationGateway

}

}

public interface EmailGateway {

void sendEmailMessage(String toAddress, String subject, String body);

}

public interface IntegrationGateway {

void publishRevenueRecognitionCalculation(Contract contract);

}

public class RecognitionService

extends ApplicationService {

public void calculateRevenueRecognitions(long contractNumber) {

Contract contract = Contract.readForUpdate(contractNumber);

contract.calculateRecognitions();

getEmailGateway().sendEmailMessage(

contract.getAdministratorEmailAddress(),

"RE: Contract #" + contractNumber,

contract + " has had revenue recognitions calculated.");

getIntegrationGateway().publishRevenueRecognitionCalculation(contract);

}

public Money recognizedRevenue(long contractNumber, Date asOf) {

return Contract.read(contractNumber).recognizedRevenue(asOf);

}

}

Для упрощения примера я опустил детали хранения данных. Достаточно сказать, что класс contract реализует статические методы, предназначенные для считывания из ис­точника данных контрактов с заданными номерами. Один из этих методов (а именно readForUpdate) считывает контракты для последующего обновления, что позволяет преобразователю данных (Data Mapper), используемому в приложении, регистриро­вать считываемый объект или объекты, скажем, в единице работы (Unit of Work).

В этом примере были опущены и детали управления транзакциями. Метод calcu­lateRevenueRecognitions() по своей сути является транзакционным, поскольку во время его выполнения обновляются постоянные объекты контракта путем добавления к ним значения зачтенного дохода, ставятся в очередь публикуемые сообщения и по элек­тронной почте отправляются отчеты. Все эти отклики должны быть переданы посредст­вом атомарных транзакций, потому что отправку отчета и публикацию сообщений следу­ет выполнять только в том случае, если обновление объектов базы данных прошло ус­пешно.

В среде J2EE EJB-контейнеры могут управлять распределенными транзакциями, реа­лизуя службы приложений (и шлюзы) в виде компонентов сеанса, не меняющих своего состояния и использующих ресурсы транзакций. На рис. 9.8 изображена схема реализа­ции класса RecognitionService, использующего локальные интерфейсы EJB2.0 и идиому "бизнес-интерфейса". В этой реализации также используется супертип слоя, обеспечивающий стандартную реализацию методов классов компонентов, необходимых EJB-контейнерам, в дополнение к методам, специфичным для данного приложения. Кроме того, если рассматривать интерфейсы EmailGateway И IntegrationGateway как бизнес-интерфейсы соответствующих компонентов сеанса, то управление распределен­ными транзакциями может выполняться путем присвоения методам calculateRevenue Recognitions, sendEmailMessage И publishRevenueRecognitionCalculation ста­туса транзакционных. В этом случае методы класса RecognitionService, рассмотрен­ные в примере с реализацией объекта POJO, "плавно переходят" в класс RecognitionServiceBeanImpl без каких-либо изменений.

Важно отметить, что для координации транзакционных откликов на выполнение опе­раций слоя служб используются и сценарии операции, и классы объектов домена. Рас­смотренный ранее метод calculateRevenueRecognitions реализует сценарий логики приложения для выполнения откликов на варианты использования, однако обращается за логикой предметной области к классам домена. Здесь также продемонстрировано не­сколько приемов, представляющих собой попытку избежать дублирования логики в сце­нариях операций слоя служб. Обязанности по выполнению откликов выносятся в раз­личные объекты (например, в шлюзы), которые могут быть повторно использованы по­средством делегирования. Доступ к этим объектам удобно получать через супертип слоя.

Puc. 9.8. Схема связей EJB-класса RecognitionService

Некоторые могут не согласиться с моим выбором, предложив реализовать сценарий операции посредством типового решения наблюдатель (Observer), представленного в [20]. Конечно, это более эффектное решение, однако реализовать его в многопоточном, не имеющем состояний слое служб было бы весьма затруднительно. Как мне кажется, от­крытый код сценария операции гораздо понятнее и проще в использовании.

Можно было бы поспорить и о размещении логики приложения. Думаю, некото­рые предпочли бы реализовать ее в методах объектов домена, таких, как Con­tract. calculateRevenueRecognitions(), ИЛИ вообще В слое источника данных, ЧТО позволило бы обойтись без отдельного слоя служб. Тем не менее подобное размещение логики приложения кажется мне весьма нежелательным, и вот почему. Во-первых, клас­сы объектов домена, которые реализуют логику, специфичную для приложения (и зави­сят от шлюзов и других объектов, специфичных для приложения), менее подходят для повторного использования другими приложениями. Это должны быть модели частей предметной области, представляющих интерес для данного приложения, поэтому подоб­ные объекты вовсе не обязаны описывать возможные отклики на все варианты использо­вания приложения. Во-вторых, инкапсуляция логики приложения на более высоком уровне (каковым не является слой источника данных) облегчает изменение реализации этого слоя, возможно, посредством некоторых специальных инструментальных средств.

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]