Прелесть веб-сервисов и API-интерфейсов RESTful в том, что любой потребитель, говорящий по протоколу HTTP , сможет его понять и использовать. Тем не менее, одна и та же дилемма возникает снова и снова: следует ли сопровождать веб-APis клиентскими библиотеками или нет? Если да, то какие языки и / или рамки вы должны поддерживать?
Довольно часто это не очень простой вопрос. Итак, давайте сделаем шаг назад и на мгновение подумаем об общей идее: каковы ценности, которые клиентские библиотеки могут принести потребителям?
Кто-то может сказать, чтобы снизить барьер для усыновления. Действительно, особенно в случае строго типизированных языков, исследование контрактов API из вашей любимой IDE (подсветка синтаксиса и автозаполнение, пожалуйста!) Весьма удобно. Но по большому счету веб-API RESTful достаточно просты для начала, и хорошая документация будет, безусловно, более ценной.
Другие могут сказать, что это хорошо, чтобы защитить потребителей от работы с несколькими версиями API или грубыми углами. Также имеет смысл, но я бы сказал, что он просто скрывает недостатки в способе разработки и развития веб-API со временем.
В общем, независимо от того, сколько клиентов вы решили объединить, API все равно будут доступны любому обычному потребителю HTTP ( curl , HttpClient , RestTemplate , вы его называете). Давать выбор — это здорово, но цена за обслуживание может быть очень высокой. Можем ли мы сделать это лучше? И, как вы уже догадались, у нас, конечно, есть немало вариантов, отсюда и этот пост.
Ключевым компонентом успеха здесь является поддержание точной спецификации ваших веб-API RESTful , используя OpenAPI v3.0 или даже его предшественника, Swagger / OpenAPI 2.0 (или RAML , API Blueprint , на самом деле не имеет большого значения). В случае OpenAPI / Swagger инструмент имеет первостепенное значение : можно использовать Swagger Codegen , движок на основе шаблонов, для генерации клиентов API (и даже заглушек серверов) на разных языках, и об этом мы и поговорим. в этом посте.
Чтобы упростить ситуацию, мы собираемся реализовать потребителя веб-API управления людьми, который мы создали в предыдущем посте . Для начала нам нужно получить спецификацию OpenAPI v3.0 в формате YAML (или JSON ).
| 
 1 
 | 
java -jar server-openapi/target/server-openapi-0.0.1-SNAPSHOT.jar | 
А потом:
| 
 1 
 | 
wget http://localhost:8080/api/openapi.yaml | 
Удивительно, половина работы выполнена, в буквальном смысле. Теперь давайте позволим Swagger Codegen взять на себя инициативу. Чтобы не усложнять ситуацию, давайте предположим, что потребитель также является Java-приложением, поэтому мы могли понять механику без каких-либо трудностей (но Java — только один из вариантов, список поддерживаемых языков и фреймворков удивителен).
В этом посте мы будем использовать OpenFeign , один из самых продвинутых Java-клиентских связывателей HTTP . Он не только исключительно прост в использовании, он предлагает довольно много интеграций, от которых мы скоро выиграем.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
 | 
<dependency>    <groupId>io.github.openfeign</groupId>    <artifactId>feign-core</artifactId>    <version>9.7.0</version></dependency><dependency>    <groupId>io.github.openfeign</groupId>    <artifactId>feign-jackson</artifactId>    <version>9.7.0</version></dependency> | 
Swagger Codegen может быть запущен как отдельное приложение из командной строки или как плагин Apache Maven (последний — то, что мы собираемся использовать).
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
24 
25 
26 
27 
28 
29 
30 
 | 
<plugin>    <groupId>io.swagger</groupId>    <artifactId>swagger-codegen-maven-plugin</artifactId>    <version>3.0.0-rc1</version>    <executions>        <execution>            <goals>                <goal>generate</goal>            </goals>            <configuration>                <inputSpec>/contract/openapi.yaml</inputSpec>                <apiPackage>com.example.people.api</apiPackage>                <language>java</language>                <library>feign</library>                <modelPackage>com.example.people.model</modelPackage>                <generateApiDocumentation>false</generateApiDocumentation>                <generateSupportingFiles>false</generateSupportingFiles>                <generateApiTests>false</generateApiTests>                <generateApiDocs>false</generateApiDocs>                <addCompileSourceRoot>true</addCompileSourceRoot>                <configOptions>                    <sourceFolder>/</sourceFolder>                    <java8>true</java8>                    <dateLibrary>java8</dateLibrary>                    <useTags>true</useTags>                </configOptions>            </configuration>        </execution>    </executions></plugin> | 
Если некоторые из вариантов не очень ясны, у Swagger Codegen есть довольно хорошая документация, чтобы искать разъяснения. Важные из них, на которые следует обратить внимание — это язык и библиотека , для которых установлены соответственно java и feign . Однако следует отметить, что поддержка спецификации OpenAPI v3.0 в основном завершена, но, тем не менее, вы можете столкнуться с некоторыми проблемами (как вы заметили, версия 3.0.0-rc1 ).
Когда вы закончите сборку, вы получите простой старый Java-интерфейс PeopleApi , аннотированный аннотациями OpenFeign , который является прямой проекцией спецификации веб-API управления людьми (которая взята из /contract/openapi.yaml ). Обратите внимание, что все классы моделей также генерируются.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
 | 
@javax.annotation.Generated(    value = "io.swagger.codegen.languages.java.JavaClientCodegen",    date = "2018-06-17T14:04:23.031Z[Etc/UTC]")public interface PeopleApi extends ApiClient.Api {    @RequestLine("POST /api/people")    @Headers({"Content-Type: application/json", "Accept: application/json"})    Person addPerson(Person body);    @RequestLine("DELETE /api/people/{email}")    @Headers({"Content-Type: application/json"})    void deletePerson(@Param("email") String email);    @RequestLine("GET /api/people/{email}")    @Headers({"Accept: application/json"})    Person findPerson(@Param("email") String email);    @RequestLine("GET /api/people")    @Headers({"Accept: application/json"})    List<Person> getPeople();} | 
Давайте сравним его с интерпретацией Swagger UI той же спецификации, доступной по адресу http: // localhost: 8080 / api / api-docs? Url = / api / openapi.json :
На первый взгляд все выглядит правильно, но нам лучше убедиться, что все работает так, как ожидалось. Как только у нас будет OpenFeign -аннотированный интерфейс, его можно будет сделать функциональным (в данном случае реализованным через прокси) с использованием семейства сборщиков Feign , например:
| 
 1 
2 
3 
4 
5 
6 
7 
8 
 | 
final PeopleApi api = Feign    .builder()    .client(new OkHttpClient())    .encoder(new JacksonEncoder())    .decoder(new JacksonDecoder())    .logLevel(Logger.Level.HEADERS)    .options(new Request.Options(1000, 2000)) | 
Отличный, свободный строитель в стиле рок. Предполагая, что наш сервер веб-API управления персоналом запущен и работает (по умолчанию он будет доступен по адресу http: // localhost: 8080 / ):
| 
 1 
 | 
java -jar server-openapi/target/server-openapi-0.0.1-SNAPSHOT.jar | 
Мы могли бы общаться с ним, вызывая только что созданные методы экземпляров PeopleApi , как показано ниже:
| 
 1 
2 
3 
4 
5 
 | 
final Person person = api.addPerson(        new Person()            .email("a@b.com")            .firstName("John")            .lastName("Smith")); | 
Это действительно круто, если мы немного перемотаем назад, мы на самом деле ничего не сделали. Все предоставляется нам бесплатно, доступна только спецификация веб-API! Но давайте не будем останавливаться на достигнутом и напомним себе, что использование интерфейсов Java не устранит реальность, с которой мы имеем дело с удаленными системами. И здесь все пойдет не так, рано или поздно, без сомнения.
Не так давно мы узнали о автоматических выключателях и их полезности при правильном применении в контексте распределенных систем. Было бы очень здорово как-то представить эту функцию в нашем клиенте на основе OpenFeign . Приветствуем еще одного члена семьи, сборщика HystrixFeign , бесшовную интеграцию с библиотекой Hytrix :
| 
 1 
2 
3 
4 
5 
6 
7 
8 
 | 
final PeopleApi api = HystrixFeign    .builder()    .client(new OkHttpClient())    .encoder(new JacksonEncoder())    .decoder(new JacksonDecoder())    .logLevel(Logger.Level.HEADERS)    .options(new Request.Options(1000, 2000)) | 
Единственное, что нам нужно сделать, это просто добавить эти две зависимости (строго говоря, hystrix-core на самом деле не нужен, если вы не против остаться на старой версии) в файл pom.xml потребителя.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
 | 
<dependency>    <groupId>io.github.openfeign</groupId>    <artifactId>feign-hystrix</artifactId>    <version>9.7.0</version></dependency><dependency>    <groupId>com.netflix.hystrix</groupId>    <artifactId>hystrix-core</artifactId>    <version>1.5.12</version></dependency> | 
Возможно, это один из лучших примеров того, насколько простой и понятной может быть интеграция. Но даже это не конец истории. Наблюдаемость в распределенных системах так же важна, как никогда, и, как мы узнали некоторое время назад , распределенная трассировка чрезвычайно полезна для нас. И снова, OpenFeign имеет поддержку прямо из коробки, давайте посмотрим.
OpenFeign полностью интегрируется с OpenTracing -совместимым трейсером. Jaeger Tracer — один из тех, у кого, помимо прочего, есть действительно хороший веб-интерфейс для изучения трассировок и зависимостей. Давайте сначала запустим его, к счастью, он полностью Docker- ориентированный.
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
 | 
docker run -d -e \    COLLECTOR_ZIPKIN_HTTP_PORT=9411 \    -p 5775:5775/udp \    -p 6831:6831/udp \    -p 6832:6832/udp \    -p 5778:5778 \    -p 16686:16686 \    -p 14268:14268 \    -p 9411:9411 \    jaegertracing/all-in-one:latest | 
Необходимо ввести несколько дополнительных зависимостей, чтобы клиент OpenFeign знал о возможностях OpenTracing .
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
 | 
<dependency>    <groupId>io.github.openfeign.opentracing</groupId>    <artifactId>feign-opentracing</artifactId>    <version>0.1.0</version></dependency><dependency>    <groupId>io.jaegertracing</groupId>    <artifactId>jaeger-core</artifactId>    <version>0.29.0</version></dependency> | 
Со стороны сборщика Feign единственное изменение (помимо введения экземпляра tracer) — это включение клиента в TracingClient , как показано в следующем фрагменте кода :
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
 | 
final Tracer tracer = new Configuration("consumer-openapi")    .withSampler(        new SamplerConfiguration()            .withType(ConstSampler.TYPE)            .withParam(new Float(1.0f)))    .withReporter(        new ReporterConfiguration()            .withSender(                new SenderConfiguration()    .getTracer();             final PeopleApi api = Feign    .builder()    .client(new TracingClient(new OkHttpClient(), tracer))    .encoder(new JacksonEncoder())    .decoder(new JacksonDecoder())    .logLevel(Logger.Level.HEADERS)    .options(new Request.Options(1000, 2000)) | 
На стороне сервера мы также должны интегрироваться с OpenTracing . Apache CXF имеет первоклассную поддержку для него , встроенную в модуль cxf-интеграции-tracing-opentracing . Давайте включим его в качестве зависимости, на этот раз в pom.xml сервера.
| 
 1 
2 
3 
4 
5 
 | 
<dependency>    <groupId>org.apache.cxf</groupId>    <artifactId>cxf-integration-tracing-opentracing</artifactId>    <version>3.2.4</version></dependency> | 
В зависимости от того, как вы конфигурируете свои приложения, должен быть доступный экземпляр трассировщика, который должен быть передан позже, например, OpenTracingFeature .
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
 | 
// Create tracerfinal Tracer tracer = new Configuration(        "server-openapi",         new SamplerConfiguration(ConstSampler.TYPE, 1),    ).getTracer();// Include OpenTracingFeature featurefinal JAXRSServerFactoryBean factory = new JAXRSServerFactoryBean();factory.setProvider(new OpenTracingFeature(tracer()));...factory.create() | 
Отныне вызов конечной точки API управления любым человеком через сгенерированный клиент OpenFeign будет полностью отслеживаться в веб-интерфейсе Jaeger , доступном по адресу http: // localhost: 16686 / search (при условии, что вашим хостом Docker является localhost ).
Наш сценарий довольно прост, но представьте себе реальные приложения, в которых может происходить дюжина вызовов внешних служб, пока один запрос проходит через систему. Без распределенной трассировки каждая проблема может превратиться в загадку.
В качестве примечания, если вы посмотрите на трассировку с картинки, вы можете заметить, что сервер и потребитель используют разные версии API Jaeger . Это не ошибка, поскольку в последней выпущенной версии Apache CXF используется более старая версия API OpenTracing (и, как таковая, более старый клиентский API Jaeger ), но это не мешает тому, чтобы все работало должным образом.
С этим пора подвести итоги. Надеемся, что преимущества основанной на контракте (или даже лучше, контрактной) разработки в мире веб-сервисов и API-интерфейсов RESTful становятся все более и более очевидными: создание интеллектуальных клиентов, тестирование по контракту, ориентированное на потребителя , обнаружение и обширная документация только несколько, чтобы упомянуть. Пожалуйста, используйте это!
Полные источники проекта доступны на Github .
|   Опубликовано на Java Code Geeks с разрешения Андрея Редько, партнера нашей программы JCG .  См. Оригинальную статью здесь: на месте потребителя: действительно ли вам нужно предоставить клиентские библиотеки для ваших API? 
 Мнения, высказанные участниками Java Code Geeks, являются их собственными.  | 

