суббота, 22 декабря 2012 г.

Проектирование контракта сервиса

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

Контракт сервиса должен однозначно описывать его интерфейс и семантику. В идеальном случае контракт должен быть самодокументируемым, т.е. по контракту должно быть понятно что это за сервис, какие у него есть методы, какие параметры принимают данные методы и какие результаты они возвращают. Если контракт по настоящему самодокументируемый, то по нему возможна автоматическая генерация кода. Другим преимуществом самодокументируемого контракта является возможность автоматической валидации запросов к сервису. Причем помимо валидации синтаксиса запроса (пример - валидация XML-документа по XSchema) возможна валидация семантики запроса (пример - использование Schematron). В настоящее время стандартом для представления контрактов сервисов является язык Web Services Description Language (WSDL). В данной заметке мы рассмотрим основные решения, которые должен принять архитектор при разработке WSDL-описания контракта сервиса, а так же приведем некоторые практические советы.


Содержимое WSDL-документа. Модели связывания.


WSDL-описание сервиса состоит из двух частей:

  • описание абстрактного сервиса, включающее в себя типы данных, операции, части. Для описания типов данных используется XML Schema.

  • связывание - раздел документа, описывающий как физически представляются сообщения (например SOAP), используемый транспортный протокол (например, HTTP) и физическое расположение конечной точки (например, URL).

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

При определении модели связывания существует четыре альтернативы, отвечающие на вопросы:

  • RPC vs document binding;

  • literal vs encoded encoding.


В настоящее время наиболее популярной является модель связывания Document/literal. Данная модель считается WS-I-совместимой, однако допускает определение нескольких частей (part сообщений, в то время как WS-I поддерживает только одну часть. К тому же в случае использования document/literal в SOAP не указывается имя метода, что затрудняет диспетчеризацию, если несколько операций сервиса содержат один и тот же набор параметров. Для решения данных проблем придумали некоторое небольшое расширение - Document/literal wrapped. В данной модели запрос обернут в элемент, совпадающий с наименованием вызываемого метода, ответ же оборачивается в элемент имя методаResponse. Это гарантирует, что soap:Body будет содержать только один дочерний элемент, совпадающий с наименованием операции. Такие оборачивающие элементы должны быть определены как элементы, а не как сложные типы. Использование complexType при определении сообщений не является WS-I-совместимым.

Модель document/literal wrapped очень похожа на модель RPC/literal: обе они порождают SOAP-сообщения с единым дочерним элементом внутри soap:Body, имя которого совпадает с наименованием операции.

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

Определение правил разработки схем, описывающих XML-сообщения


Для определения типов входных и выходных данных в абстрактной части WSDL-документов используется XML Schema. Команда разработки SOA-решения обязательно должна иметь правила разработки схем. Такие правила должны быть четко описаны архитектором и содержать следующее:

  • правила именования элементов (нотация, максимальная длина);

  • правила именования типов (tName vs nameType vs name_type);

  • стандарты именования: нужно иметь словари

    • синонимов (Order и Purchase Order -> order);

    • сокращений (shippingAddr);

  • зависимые от контекста имена (не повторять контекст в имени, например вместо addressLine1 использовать line1);

  • обобщенные имена (Generic Names);

  • пространства имен (всегда необходимо использовать targetNamespace, нужно избегать использования пустых пространств имен). Так же необходимо выработать соглашение об именовании пространств имен. Здесь же можно определить стандартные префиксы, например:

    • cmr http://www.rt.ru/integration/model/crm/customer;

    • abn http://www.rt.ru/integration/model/crm/abonent;

    • case http://www.rt.ru/integration/model/crm/case.

  • qualified vs unqualified элементы. Преимущества qualified - по каждому элементу сразу видно, в каком пространстве имен он определен. Можно смешивать - часть элементов определить как qualified, часть - как unqualified, однако данный вариант чреват ошибками.

  • qualified vs unqualified атрибуты. По-умолчанию глобально-определенные атрибуты требуют указания префикса, а локально-определенные - нет. Глобально определенные атрибуты - это атрибуты, определеные как дочерние элементы элемента schema, соответственно такие атрибуты можно использовать в нескольких элементах.

  • глобальные или локальные компоненты (элементы, атрибуты, сложные типы, простые типы). Компоненты могут быть глобальными (если определены как дочерний элемент элемента scheme) или локальными - определены внутри определения другого компонента. Глобальные компоненты являются повторно используемыми, в то время, как локальные - нет. Несколько замечаний, по поводу данного пункта:

    • любой элемент, который предполагается использовать как параметр для операции веб-сервиса, должен быть определен как глобальный;

    • любой элемент, который предполагается использовать как BPEL-переменную, должен быть определен как глобальный.

  • элементы или типы. Типы являются более гибким решением, т.к. может быть определено несколько элементов одного типа. Важно: в BPEL 1.1 можно определять переменные, основанные только на глобальных элементах, не на типах. При использовании в схеме, определенной в другом пространстве имен, элементы предпочтительнее типов. Рекомендуется использовать разные имена для типов и элементов: tElement vs element.


Использование атрибутов для метаданных

Ключевым вопросом при проектировании XML-схем является выбор между использованием атрибутов и элементов. Элементы более гибки: можно создавать сложную структуру, содержащуюся в элементе. Атрибуты же не могут быть расширяемыми.

Одним из решений данного вопроса является следующее: использовать атрибуты для метаданных, а элементы - для данных. Например, если мы инкапсулируем в XML запрос к базе данных, то в атрибутах можем хранить параметры запроса, такие как
startRow или endRow.

Разделение канонической модели

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

Истина как всегда посередине: разделить схему на файлы, содержащие семантически связанные компоненты, например:

  • AbonentOrganization.xsd - определяет все бизнес-объекты, специфичные для юридического лица;

  • AbonentPerson.xsd - определяет все бизнес-объекты, специфичные для физического лица;

  • Case.xsd - определяет все бизнес-объекты, специфичные для заявки;

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


Организация пространств имен

Существуют два подхода к организации пространств имен при определении XML-схем: использование единого пространства имен и использование множества пространств имен.

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

Для каждого домена можно определить свое пространство имен. При этом если хочется сделать в одной схеме ссылки на элементы другой схемы, то можно использовать директиву xsd:import. При этом общие объекты, используемые в нескольких пространствах имен, должны быть вынесены в свое пространство имен. Тогда схему
с таким общим пространством можно импортировать во все другие, тем самым обеспечивая повторное использование компонентов.

В данном случае нет мастер-схемы, каждый сервис должен включать в себя только нужные ему части модели. Каждую часть модели можно расширять независимо от остальных. Однако за все нужно платить, документы при таком подходе становятся более сложными, т.к. в них используется несколько пространств имен.

Стратегии управления изменениями


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

Мажорные и минорные версии

Говорят, что сервис имеет обратную совместимость, если все сообщения, генерируемые и обрабатываемые версией 1 сервиса, корректно обрабатываются версией 2 сервиса. С данной точки зрения все изменения можно разделить на две группы: те, при которых сохраняется обратная совместимость и те - при которых не сохраняется. Версии сервисов таким образом можно поделить на мажорные и минорные: при изменении мажорной версии обратная совместимость не сохраняется (версия 2.0 не является обратно совместимой с версией 1.2), а при изменении минорной версии - сохраняется обратная совместимость в рамках соответствующей мажорной (версия сервиса 1.2 обратно совместима с версией 1.1).

Можно ввести еще и понятие прямой совместимости: потребитель может быть обновлен на новую версию сервиса до того, как провайдер обновится на эту версию.

Будем рассматривать реакцию на изменение каждого из трех компонентов сервиса:

  • контракта сервиса - WSDL-файла;

  • связанных типов данных из канонической модели;

  • актуальной реализации сервиса.


Нам интересно, как изменение какого-то из данных компонентов влияет на версию всего сервиса.

Версионирование реализации сервиса

Ключевая концепция SOA - изоляция изменений. Например, если изменяется реализация сервиса, но потребитель не видит изменений контракта, то кажется, что с его точки зрения изменений не было вовсе. Однако, это не так. Еще раз вернемся к приведенному выше определению обратной совместимости сервисов: сервис называется обратно совместимым, если все сообщения, которые корректно обрабатывались версией 1 сервиса продолжают корректно обрабатываться версией 2 сервиса.

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

Есть другое решение - не вводить новую версию сервиса, а предупредить потребителя об изменениях. Данный путь осуществим с помощью Oracle Enterprise Repository.

Версионирование схемы

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

Минорные изменения включают в себя:

  • добавление определений новых элементов, атрибутов и типов;

  • добавление опциональных атрибутов и элементов к существующим элементам и типам;

  • преобразование необходимых (mandatory) элементов и атрибутов в опциональные;

  • преобразование элементов в группы выбора (choose group);

  • снижение ограничений, наложенных на простые типы.


Стоит отметить, что можно включать версию схемы в путь к ней, например: Abonent_v1.0.xsd. Данное решение имеет два плюса: наглядно видно какая версия схемы используется, а так же можно разворачивать несколько версий схемы одновременно.

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

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

Версионирование контракта сервиса

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

Минорные изменения:

  • добавление операций: это просто расширяет WSDL и является обратно-совместимым;

  • добавление новых опциональных параметров в элемент-обертку, используемый во входящем сообщении;

  • преобразование существующих обязательных параметров в элементе-обертке в опциональные.


Мажорные изменения:

  • удаление или переименование операций;

  • изменение входных и выходных параметров операции, не попадающие под понятие минорных изменений, такие как добавление новых опциональных или обязательных параметров к обертке ответа.


Стоит отметить, что мы не рассматриваем элементы binding и service. Элемент binding относится к реализации сервиса, соответственно для его изменений справедливо все, что сказано об изменении реализации сервиса. Изменение конечной точки сервиса просто обозначает перемещение сервиса, которое происходит, например, при перемещении композита с тестовой среды на продуктив. При этом версию сервиса можно указать в пути к конечной точке. Oracle SOA Suite реализует данную возможность: при разворачивании композита мы указываем его версию. Например, для версии 1.0: http://host:port/soa-infra/services/default/AbonentService!1.0/proxy_ep, WSDL при этом будет доступен по адресу: http://host:port/soa-infra/services/default/AbonentService!1.0/proxy_ep?wsdl. В Oracle Service Bus можно еще гибче управлять расположением конечной точки прокси-сервиса, задавая это расположение как часть конфигурации транспорта.

Данный механизм имеет два преимущества: по URL сразу видно номер версии сервиса, а так же он позволяет иметь несколько версий, развернутых в одной и той же инфраструктуре.

Номер версии так же можно включать в определение WSDL-файла:

<wsdl:definitions name="CaseManagementService"

                 xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"

                 xmlns:tns="http://www.rt.ru/integration/services/crm/casemanage"

                 xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"

                 xmlns:xsd="http://www.w3.org/2001/XMLSchema"

                 xmlns:csmng="http://www.rt.ru/integration/messages/crm/casemanage"

                 xmlns:fault="http://www.rt.ru/integration/messages/fault"

                 targetNamespace="http://www.rt.ru/integration/services/crm/casemanage">

    <wsdl:documentation>Version 1.0</wsdl:documentation>

    ...

</wsdl:definitions>

Управление жизненным циклом сервиса

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

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

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

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

P.S. Данная заметка скорее всего будет последней в завершающемся 2012-м году. Поздравляю всех своих читателей с тем, что конец света так и не наступил, а так же с наступающим Новым годом. Счастья вам и радости в новом году, продолжайте читать блог Сурового челябинского программиста. Спасибо, что были на связи.

Понравилось сообщение - подпишитесь на блог и Twitter

Комментариев нет:

Отправить комментарий

Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!