Прелесть веб-сервисов и 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, являются их собственными. |

