Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Декларативный HTTP-клиент

Микросервисы постоянно обращаются друг к другу по HTTP. В Micronaut для этого не нужно вручную собирать запрос и разбирать JSON: достаточно описать интерфейс с аннотациями, как у контроллера, — реализацию сгенерирует обработчик аннотаций. Такой клиент называется декларативным: вы объявляете, что нужно, а не пишете, как это сделать.

Для примера блог будет импортировать статьи из учебного API jsonplaceholder.typicode.com — у него есть адрес /posts/{id}, который отдаёт пост в JSON.

Зависимость

Генератор уже добавил micronaut-http-client, но с <scope>test</scope> — только для тестов. Чтобы клиент работал в самом приложении, поменяйте область видимости в pom.xml:

<!-- pom.xml -->
<dependency>
  <groupId>io.micronaut</groupId>
  <artifactId>micronaut-http-client</artifactId>
  <scope>compile</scope>
</dependency>

Модель и клиент

Запись для ответа чужого сервиса:

// src/main/java/com/example/ExternalPost.java
package com.example;
 
import io.micronaut.serde.annotation.Serdeable;
 
@Serdeable
public record ExternalPost(Long id, Long userId, String title, String body) {
}

Интерфейс клиента:

// src/main/java/com/example/PlaceholderClient.java
package com.example;
 
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
 
import java.util.List;
import java.util.Optional;
 
@Client(id = "placeholder")
public interface PlaceholderClient {
 
    @Get("/posts/{id}")
    Optional<ExternalPost> fetchPost(Long id);
 
    @Get("/posts?userId={userId}")
    List<ExternalPost> fetchByUser(Long userId);
}

id = "placeholder" — имя сервиса. Его адрес задаётся в настройках, а не в коде:

# src/main/resources/application.properties
micronaut.http.services.placeholder.url=https://jsonplaceholder.typicode.com

Так адрес легко поменять для тестов или сервера. Можно указать адрес и прямо: @Client("https://jsonplaceholder.typicode.com").

Использование

Клиент — обычный бин, его внедряют через конструктор:

// src/main/java/com/example/ImportController.java
package com.example;
 
import io.micronaut.http.HttpStatus;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Post;
import io.micronaut.http.exceptions.HttpStatusException;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
 
@ExecuteOn(TaskExecutors.BLOCKING)
@Controller("/import")
public class ImportController {
 
    private final PlaceholderClient placeholderClient;
    private final ArticleService articleService;
 
    public ImportController(PlaceholderClient placeholderClient, ArticleService articleService) {
        this.placeholderClient = placeholderClient;
        this.articleService = articleService;
    }
 
    @Post("/{id}")
    public Article importPost(Long id) {
        ExternalPost post = placeholderClient.fetchPost(id)
                .orElseThrow(() -> new HttpStatusException(HttpStatus.NOT_FOUND, "Поста " + id + " нет на удалённом сервере"));
        return articleService.create(new ArticleForm(post.title(), post.body()));
    }
}

Вызов клиента блокирует поток до ответа, поэтому контроллер снова помечен @ExecuteOn(TaskExecutors.BLOCKING).

Проверка

curl -X POST http://localhost:8080/import/2
{"id":1,"title":"qui est esse","body":"est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\n..."}

Пост с удалённого сервера сохранился как статья блога. Для несуществующего поста:

curl -X POST http://localhost:8080/import/1000
{"_links":{"self":[{"href":"/import/1000","templated":false}]},"_embedded":{"errors":[{"message":"Поста 1000 нет на удалённом сервере"}]},"message":"Not Found"}

Если метод клиента возвращает Optional, ответ 404 превращается в пустой Optional. Другие ошибки (500, 401) клиент выбрасывает как HttpClientResponseException.

Частая ошибка

Если оставить зависимость с <scope>test</scope>, основной код не скомпилируется:

[ERROR] PlaceholderClient.java:[4,43] package io.micronaut.http.client.annotation does not exist

В тестах клиент работает, а в приложении нет — проверьте область видимости micronaut-http-client в pom.xml.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.