Статьи

В шкуре потребителя: вам действительно нужно предоставить клиентские библиотеки для ваших API?

Прелесть веб-сервисов и 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 :

клиентские библиотеки api spec

На первый взгляд все выглядит правильно, но нам лучше убедиться, что все работает так, как ожидалось. Как только у нас будет 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))
    .target(PeopleApi.class, "http://localhost:8080/");

Отличный, свободный строитель в стиле рок. Предполагая, что наш сервер веб-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))
    .target(PeopleApi.class, "http://localhost:8080/");

Единственное, что нам нужно сделать, это просто добавить эти две зависимости (строго говоря, 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()
                    .withEndpoint("http://localhost:14268/api/traces")))
    .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))
    .target(PeopleApi.class, "http://localhost:8080/");

На стороне сервера мы также должны интегрироваться с 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 tracer
final Tracer tracer = new Configuration(
        "server-openapi",
        new SamplerConfiguration(ConstSampler.TYPE, 1),
        new ReporterConfiguration(new HttpSender("http://localhost:14268/api/traces"))
    ).getTracer();
 
// Include OpenTracingFeature feature
final JAXRSServerFactoryBean factory = new JAXRSServerFactoryBean();
factory.setProvider(new OpenTracingFeature(tracer()));
...
factory.create()

Отныне вызов конечной точки API управления любым человеком через сгенерированный клиент OpenFeign будет полностью отслеживаться в веб-интерфейсе Jaeger , доступном по адресу http: // localhost: 16686 / search (при условии, что вашим хостом Docker является localhost ).

клиентские библиотеки потребительские openapi

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

В качестве примечания, если вы посмотрите на трассировку с картинки, вы можете заметить, что сервер и потребитель используют разные версии API Jaeger . Это не ошибка, поскольку в последней выпущенной версии Apache CXF используется более старая версия API OpenTracing (и, как таковая, более старый клиентский API Jaeger ), но это не мешает тому, чтобы все работало должным образом.

С этим пора подвести итоги. Надеемся, что преимущества основанной на контракте (или даже лучше, контрактной) разработки в мире веб-сервисов и API-интерфейсов RESTful становятся все более и более очевидными: создание интеллектуальных клиентов, тестирование по контракту, ориентированное на потребителя , обнаружение и обширная документация только несколько, чтобы упомянуть. Пожалуйста, используйте это!

Полные источники проекта доступны на Github .

Опубликовано на Java Code Geeks с разрешения Андрея Редько, партнера нашей программы JCG . См. Оригинальную статью здесь: на месте потребителя: действительно ли вам нужно предоставить клиентские библиотеки для ваших API?

Мнения, высказанные участниками Java Code Geeks, являются их собственными.