Описание задачи
В книге «Программист прагматик» есть глава, посвящённая документации. В ней сказано, что должен быть один источник информации, а на основе него генерироваться производные формы этой информации. например, в одном формате хранится вся информация о структуре таблиц (это может быть файл любого подходящего или собственного формата), а на основе неё генерируются сами таблицы, документация по этим таблицам, DAO объекты для работы с таблицами. Плюсы это подхода в том, между производными формами представления информации не будет рассинхронизации, ведь единственное место, где будет редактироваться информация — это тот первоначальный источник.
Минусы в том, что все заинтересованные люди должны уметь править этот файл и должны быть конвертеры в нужные форматы.
В web services эти понятия называется contract first и contract last. Contract first — когда сначала создаётся wsdl, а по ней генерируется Java код. Contract last — наоборот, сначала Java код, а по нему wsdl.
Про плюсы и минусы можно почитать по ссылкам: http://ift.tt/1hboYWz http://ift.tt/1hboYWB
В этой статье я рассмотрю создание SOAP Web Service с самодокументацией, что бы получить похожее решение — вся информация о web services будет содержаться в нём самом. Получается этакий «contract last» — сначала пишется код, а потом по нему получается документация в виде wsdl, которая содержит максимум информации об этом web сервисе — комментарии ко всем методам и типам, с которыми сервис работает и большую часть логики проверки на корректность данных. Для чего это решение может быть полезно? В том случае, когда вся разработка сосредоточена на написании кода и wsdl генерируется на основе него, а не наоборот. Или когда сложно содержать документацию изначально в wsdl. Но если веб методы пишутся по аналитике, то эту аналитику лучше всё же хранить в wsdl. Т.е. сначала wsdl, а по ней генерируются методы, классы и читаемая документация. Иначе будут проблемы проверки соответствия уже написанного сервиса с тех. заданием.
Проверку на корректность входных данных тоже лучше вынести на сторону xsd, что бы можно было проверить запрос без подключения в web сервису и что бы эта логика была видна (в xsd), а не была зашита где то в недрах сервиса.
Варианты решения
На текущий момент получить wsdl можно из любого jax-ws приложения. Но стандартные jax-ws/jaxb аннотации не поддерживают документацию к веб сервисам — для этого нужно пользоваться возможностями, которые предоставляют сторонние framework'и. А конкретно — apache CXF (jax-ws) предоставляет аннотацию WSDLDocumentation, которая применяется к веб методам и в которой указывается описание веб метода, которое в последствие попадёт в wsdl. Для документирования xml сущностей, которые будут использоваться в веб методах, используется библиотека jaxb-facets ( http://ift.tt/1jCm1gf, http://ift.tt/1jCm1wB). Она позволяет, так же как CXF для веб методов, документировать классы и поля xml bean'ов. По мимо этого она добавляет поддержку XSD Restrictions — дополнительных ограничений на значения полей xml. (описание XSD Restrictions находится здесь http://ift.tt/1aH0zC4) Это будет полезно, когда нужна логика проверки корректности значений сложнее чем обязательный/не обязательный — т.к. jaxb не в полной мере поддерживает возможности xsd по части валидации. А конкретно, будет возможно указать ограничения на минимальное, максимальное значения, использовать регулярные выражения, использовать различные способы обработки строк на пустые значения, установить ограничения на длину значения.
Реализация
В качестве примера создадим простой веб сервис с несколькими методами.
В статье я опишу конкретные примеры с кусками кода. Весь же сервис можно скачать по ссылке http://ift.tt/1hboYWI Это IDEA maven проект. В IDEA можно открыть либо импортом IDEA проекта либо импортом maven проекта. В pom.xml настроена генерация wsdl файла с трансформацией его в html с помощью xslt, но к сожалению, этот wsdl не содержит тега documentation из jaxb-facets, хотя в генерируемом wsdl (доступным по ссылке /web_service/WebService?wsdl) этот тег есть. Так же в проекта отличается применение аннотаций — там аннотации применяются к get методам, а в этих примерах к полям класса. Это сделано для того, что бы сократить статью. Что бы применять аннотации к полям класса нужно либо не делать get методы либо указать @XmlAccessorType(XmlAccessType.FIELD).
Примеры
В качестве примера рассмотрим несколько конкретных задач
Ограничение на максимальное и минимальные значение даты и времени.
Для date:
@XmlSchemaType(name = "date")
@Facets(minInclusive = "1900-01-01", maxInclusive = "9999-12-31Z")
Date date;
Сгенерируемая xsd схема:
<xs:element minOccurs="0" name="date">
<xs:simpleType>
<xs:restriction base="xs:date">
<xs:maxInclusive value="9999-12-31Z"/>
<xs:minInclusive value="1900-01-01"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
@XmlSchemaType(name = "dateTime")
@Facets(minInclusive = "1900-01-01T00:00:00", maxInclusive = "9999-12-31T23:59:59")
Date dateTime;
<xs:element minOccurs="0" name="dateTime">
<xs:simpleType>
<xs:restriction base="xs:dateTime">
<xs:maxInclusive value="9999-12-31T23:59:59"/>
<xs:minInclusive value="1900-01-01T00:00:00"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
(Аннотация Facets из библиотеки jaxb-facets)
У нас нужно было использовать т.к. СУБД поддерживала не весть диапазон дат, доступных в Java. Т.е. в web service можно передать дат, например, 1000-01-01 и в Java она корректно распарсится, но при записи в CУБД будет ошибка, т.к. она не поддерживает такой диапазон дат. И что бы ограничить принимаемые значения вводится это ограничение.
Для даты максимальная дата указана с учётом часового пояса. Нужно, что бы поддерживались выходные даты 9999-12-31 с часовым поясом (например 9999-12-31+04:00). Если этого не делать, то при сериализации этого значения (при отдаче клиенту) будет ошибка xsd validation.
Ограничение на конкретную длину поля
@Facets(length = 16)
String id;
<xs:element minOccurs="0" name="id">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:length value="16"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
Нужно, например, когда поле является уникальным идентификатором определённой длинны или каким то другим идентификатором конкретной длинны — номер паспорта, карточки.
Хотя в случае id можно выделить отдельный тип Id:
public class Id {
@Facets(length = 16)
@XmlAttribute
String attr;
}
И использовать его так:
Id id;
Так у нас будет явно указано, что это переменная — уникальный идентификатор, при этом все ограничение будут работать.
Обнуление полей
Некоторые необязательные поля нужно обнулять. Для строк это делается очень просто — шлётся пустой тег (e.g. ) и при unmarshalling'е (преобразование xml запроса в java объекты) получим просто пустую строку (String.isEmpty() == true). И её вполне можно интерпретировать как строку, которую нужно установить в null.
Но если нужно обнулить числа или даты, то при передаче пустого тега будет ошибка:
Unmarshalling Error: cvc-datatype-valid.1.2.1: '' is not a valid value for 'integer'
Это вполне объяснимо — пустой тег нельзя представить в виде числа (null не считается — null это отсутсвие тега).
Решить это можно с помощью default значения для поля
@XmlElement(defaultValue = "-2147483648")
Integer id;
<xs:element default="-2147483648" minOccurs="0" name="id" type="xs:int"/>
При этом если передать пустой тег (<id/>) в Java переменная id примет значение -2147483648 (это Integer.MIN_VALUE). Это решение немного похоже Null Object Pattern — когда нужно указать, что явно передаётся null значение (а не просто не указали значение), то передаём специальный null объект.
http://ift.tt/MEYi06
Для дат этот же подход будет выглядеть так:
@XmlSchemaType(name = "date")
@XmlElement(defaultValue = "1900-01-01")
Date date;
<xs:element default="1900-01-01" minOccurs="0" name="date" type="xs:date"/>
@XmlSchemaType(name = "dateTime")
@XmlElement(defaultValue = "1900-01-01T00:00:00")
Date dateTime;
<xs:element default="1900-01-01T00:00:00" minOccurs="0" name="dateTime" type="xs:dateTime"/>
Документирование
Документирование полей делается с помощью аннотации Documentation (из библиотеки jaxb-facets).
@XmlSchemaType(name = "date")
@Documentation("Пример ограничения даты")
@Facets(minInclusive = "1900-01-01", maxInclusive = "9999-12-31Z")
Date date;
<xs:element minOccurs="0" name="date">
<xs:annotation>
<xs:documentation>Пример ограничения даты</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:date">
<xs:maxInclusive value="9999-12-31Z"/>
<xs:minInclusive value="1900-01-01"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
Документирование методов web service делается с помощью аннотации WSDLDocumentation из CXF.
@WSDLDocumentation("Пример документации для метода. Метод показывает работы с XML Schema Restrictions")
String facetsExampleMethod(FacetsExample request);
<wsdl:operation name="facetsExampleMethod">
<wsdl:documentation>
Пример документации для метода. Метод показывает работы с XML Schema Restrictions
</wsdl:documentation>
<wsdl:input message="tns:facetsExampleMethod" name="facetsExampleMethod"/>
<wsdl:output message="tns:facetsExampleMethodResponse" name="facetsExampleMethodResponse"/>
</wsdl:operation>
В дальнейшем описание к этим полям и методам можно посмотреть в wsdl.
Генерация документации по сервису (wsdl to html)
Для преобразовании сгенерированной wsdl в читаемый вид воспользуемся файлом xslt http://ift.tt/1hboYWK Так же SoapUI может генерировать читаемый html. Всё это описано на StackOverflow:
http://ift.tt/1hboYWL Преобразование в html на этапе сборки реализовано в pom.xml Дальше эта страница будет доступна по адресу /WebService.html
Не сколько замечаний по генерации html:
Параметры web методов корректно отображаются только если указан @SOAPBinding(style = SOAPBinding.Style.RPC).
Аннотация Documentation попадает в wsdl и, соответственно, в документацию в html только при runtime генерации wsdl из работающего сервиса. При сборке wsdl не содержит документации. Почему так — не знаю. Скорее всего ошибка генератора wsdl.
Остальные возможности jaxb-facets
Вкратце расскажу о остальных возможностях jaxb-facets, которые я использовал:
Проверка значения по регулярному выражению — Facets.pattern.
Установка логики обработки пробельных символов — Facets.whiteSpace. Пробельные символы можно учитывать, убирать, заменять на пробелы (для табов и символов переноса строки). Работает только при проверке в xsd, т.е. в Java код попадёт значение как есть, с пробельными символами. Полезно будет для проверки обязательности заполнения строкового поля, когда поле заполненное пробелами, тоже должно интерпретироваться как пустое.
Вообще jaxb-facets поддерживает все xsd restrinction. Ознакомиться с ними можно в конце страницы http://ift.tt/1aH0zC4 Так же в последней версии (2.2.6) появилась поддержка xs:assert. Пример xs:assert с сайта IBM ( http://ift.tt/1jCm1N9 ):
<xs:element name=«dimension»>
<xs:complexType>
<xs:attribute name=«height» type=«xs:int»/>
<xs:attribute name=«width» type=«xs:int»/>
<xs:assert test="@ height < @ width"/>
</xs:complexType>
</xs:element>
Замечания
Повторюсь, вообще если пишется сервис «с нуля» то лучше сделать корректный wsdl, а по нему уже генерировать Java код и документацию. Но если уже есть написанный web сервис, то добавление в него XSD Restrictions позволит сделать его более прозрачным со стороны клиента — он будет видеть логику проверки входных значений (хотя, понятно, не всю — в некоторых случаях нельзя проверить корректность запросов без обращения к СУБД или каким то связанным ресурсам). Плюс генерация документации позволит удобно сравнить соответствие реальных web методов со своими параметрами, тех. заданию. Без этого пришлось бы проверять или методом «тыка» — смотря запросы и ответы web методов, либо разбираться в хитросплетениях сгенерируемой wsdl.
This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий