Каковы ваши лучшие методы управления версиями WebService?
У нас есть 2 отдельных продукта, которые должны общаться друг с другом через веб-службы.
Какова наилучшая практика для поддержки versioining API?
У меня эта статья от 2004 года, утверждая, что нет фактического стандарта и только лучшие практики. Какие-нибудь лучшие решения? Как вы решаете WS-версию?
Описание проблемы
Система A
Client
class SystemAClient{
SystemBServiceStub systemB;
public void consumeFromB(){
SystemBObject bObject = systemB.getSomethingFromB(new SomethingFromBRequest("someKey"));
}
}
Сервис
class SystemAService{
public SystemAObject getSomethingFromA(SomethingFromARequest req){
return new SystemAObjectFactory.getObject(req);
}
}
Переносимый объект
Версия 1
class SystemAObject{
Integer id;
String name;
... // getters and setters etc;
}
Версия 2
class SystemAObject{
Long id;
String name;
String description;
... // getters and setters etc;
}
Объект запроса
Версия 1
class SomethingFromARequest {
Integer requestedId;
... // getters and setters etc;
}
Версия 2
class SomethingFromARequest {
Long requestedId;
... // getters and setters etc;
}
Система B
Client
class SystemBClient{
SystemAServiceStub systemA;
public void consumeFromA(){
SystemAObject aObject = systemA.getSomethingFromA(new SomethingFromARequest(1));
aObject.getDescription() // fail point
// do something with it...
}
}
Сервис
class SystemBService{
public SystemBObject getSomethingFromB(SomethingFromBRequest req){
return new SystemBObjectFactory.getObject(req);
}
}
Переносимый объект
Версия 1
class SystemBObject{
String key;
Integer year;
Integer month;
Integer day;
... // getters and setters etc;
}
Версия 2
class SystemBObject{
String key;
BDate date;
... // getters and setters etc;
}
class BDate{
Integer year;
Integer month;
Integer day;
... // getters and setters etc;
}
Объект запроса
Версия 1
class SomethingFromBRequest {
String key;
... // getters and setters etc;
}
Версия 2
class SomethingFromBRequest {
String key;
BDate afterDate;
BDate beforeDate;
... // getters and setters etc;
}
Сценарии сбоев
Если клиент System A версии 1 вызывает службу System B версии 2, он может выйти из строя on:
- отсутствующие методы на
SystemBObject
(getYear()
, getMonth()
, getDay()
)
- Неизвестный тип
BDate
Если клиент System A версии 2 вызывает службу System B версии 1, он может выйти из строя на:
- Неизвестный тип
BDate
в SomethingFromBRequest
(клиент использует новый объект запроса B, который B-версия 1 не распознает)
- Если клиент System A достаточно умен, чтобы использовать версию 1 объекта запроса, он может не работать с отсутствующими методами в объекте
SystemBObject
(getDate()
)
Если клиент System B версии 1 вызывает службу System A версии 2, он может выйти из строя on:
- Введите missmatch или overflow на
SystemAObject
(возвращается Long
, но ожидается Integer
)
Если клиент System B версии 2 вызывает службу System A версии 1, она может выйти из строя на:
- Введите missmatch или overflow на
SystemARequest
(запрос Long
вместо Integer
)
- Если запрос каким-то образом прошел, проблемы с литьем (заглушка
Long
, но служба возвращает Integer
, которая несовместима во всех реализациях WS)
Возможные решения
- Используйте числа при продвижении версий: например.
SystemAObject1
, SystemBRequest2
и т.д., но при этом отсутствует API для соответствия исходной/целевой версии
- В сигнатуре передать XML, а не объекты (yuck, pass escaped XML в XML, двойная сериализация, десериализация/синтаксический анализ, unparsing)
- Другое: например. У документа/литерала/WS-I есть средство правовой защиты?
Ответы
Ответ 1
Я предпочитаю метод управления версиями Salesforce.com. Каждая версия веб-служб получает отдельный URL-адрес в формате:
http://api.salesforce.com/{version}/{serviceName}
Итак, у вас будут URL-адреса веб-сервисов, которые выглядят следующим образом:
http://api.salesforce.com/14/Lead
http://api.salesforce.com/15/Lead
и т.д.
С помощью этого метода вы получаете преимущества:
-
Вы всегда знаете, с какой версией вы разговариваете.
-
Поддерживается обратная совместимость.
-
Вам не нужно беспокоиться о проблемах с зависимостями. Каждая версия имеет полный набор услуг. Вам просто нужно убедиться, что вы не смешиваете версии между вызовами (но это зависит от потребителя услуги, а не от разработчика).
Ответ 2
Решение состоит в том, чтобы избежать несовместимых изменений в ваших типах.
Возьмем, к примеру, SystemBObject. Вы описываете "версию 1" и "версию 2" этого типа, но они не одинаковы. Совместимое изменение этого типа включает только добавление свойств, а не изменение типа любых существующих свойств. Ваше гипотетическое "обновление версии" нарушило оба этих ограничения.
Следуя этой гильдии, вы можете избежать всех описанных вами проблем.
Поэтому, если это ваше определение типа в версии 1
class SystemBObject{ // version 1
String key;
Integer year;
Integer month;
Integer day;
... // getters and setters etc;
}
Тогда это не может быть вашим определением типа в v2:
// version 2 - NO NO NO
class SystemBObject{
String key;
BDate date;
... // getters and setters etc;
}
... потому что он устранил существующие поля. Если это изменение, которое вам нужно сделать, это не новая "версия", это новый тип и должен быть назван как таковой, как в коде, так и в формате сериализации.
Другой пример: если это ваш существующий тип v1:
class SomethingFromARequest {
Integer requestedId;
... // getters and setters etc;
}
... тогда это не является допустимым "v2" этого типа:
class SomethingFromARequest {
Long requestedId;
... // getters and setters etc;
}
... потому что вы изменили тип существующего свойства.
Эти ограничения более подробно объясняются главным образом технологически нейтральным способом в статье Microsoft Service Versioning.
Помимо исключения этого источника несовместимости, вы можете и должны включать номер версии в тип. Это может быть простой серийный номер. Если у вас есть привычка регистрировать или проверять сообщения, а пропускная способность и пространство для хранения данных не являются проблемой, вы можете захотеть увеличить простое целое число с помощью UUID для идентификации экземпляра каждой уникальной версии типа.
Кроме того, вы можете проектировать форвардную совместимость в объекты передачи данных, используя слабую обработку и отображение "дополнительных" данных в "дополнительное" поле. Если XML - это ваш формат сериализации, вы можете использовать xsd: xmlAny или xsd: any и processContents = "lax" для захвата любых непризнанных элементов схемы, когда служба v1 получает запрос v2 (больше). Если ваш формат сериализации - JSON, с его более открытой моделью контента, то это бесплатно.
Ответ 3
Я знаю, что это уже поздно, но я довольно глубоко вникаю в эту проблему. Я действительно думаю, что лучший ответ включает в себя еще один кусочек головоломки: посредник службы. Microsoft Managed Services Engine - пример одного - я уверен, что другие существуют. В основном, изменив пространство имен XML вашего веб-сервиса (чтобы указать номер версии или дату, как упоминается в связанной статье), вы разрешаете посреднику возможность маршрутизировать различные клиентские вызовы на соответствующие серверные реализации. Дополнительная (и, IMHO, очень крутая) функция MSE - это возможность выполнять преобразования на основе политик. Вы можете определить преобразования XSLT, которые конвертируют запросы v1 в запросы v2, и ответы v2 в ответы v1, что позволяет вам удалять реализации служб v1 без нарушения реализации клиента.
Ответ 4
Я думаю, что что-то еще, что нужно иметь в виду, это ваша клиентская база, публично публикую эту услугу или она ограничена набором известных агентов?
Я участвую в последней ситуации, и мы обнаружили, что это не так сложно решить с помощью простой коммуникации/привлечения внимания.
Хотя это косвенно связано с вашим вопросом, мы обнаружили, что, основывая наш номер версии на совместимость, кажется, работает достаточно хорошо. Использование A.B.C в качестве примера...
- A: Изменения, требующие перекомпиляции (прерывает обратную совместимость)
- B: Изменения, которые не требуют перекомпиляции, но без дополнительных функций недоступны (новые операции и т.д.).
- C: Изменения в основной механике, которые не изменяют WSDL