Создайте API с помощью Spring-Data-Rest и создайте соответствующий клиент OpenFeign.

В этой статье мы представим удобную библиотеку Spring-Data-Rest для устранения бессмысленного кода в приложении Spring Boot. Как мы все знаем, API доступа к БД будет реализован как стек Controller-Service-Repository в соглашении о кодировании приложений Spring Boot. Часто в контроллерах и сервисах нет бизнес-логики, а только вызов очередного компонента и возврат результата. Это точно бессмысленный код. Spring-Data-Rest может помочь нам элегантно избавиться от этого.

После применения Spring-Data-Rest мы можем обнаружить, что ответ API отформатирован в соответствии с ограничением Hypermedia as the Engine of Application State (HATEOAS). Есть много свойств, которые возвращаются с исходным объектом БД.

Таким образом, нам нужно интегрировать Spring-HATEOAS, чтобы обернуть компонент API-клиент, после чего мы можем предоставить модуль SDK, который можно использовать для вызова конечной точки Spring-Data-Rest API с минимальными усилиями.

Вот что мы рассмотрим:

  1. Интегрируйте Spring-Data-Rest для замены API-интерфейсов доступа к БД в приложении Spring Boot.
  2. Настройте открытую конечную точку как сервер запросов CQRS
  3. Интегрируйте Spring-HATEOAS для создания клиентского модуля API для API доступа к БД.

Давай начнем!

1. Интеграция Spring-Data-Rest

Интеграция Spring-Data-Rest в приложение Spring Boot очень проста, поскольку существует официальный файл spring-boot-starter-data-rest.

поэтому мы можем добавить следующую зависимость в gradle.build:

dependencies {
    ...
    implementation 'org.springframework.boot:spring-boot-starter-data-rest'
    ...
}

затем укажите класс сущности и соответствующий интерфейс JPA-репозитория как OrderRecord.java:

@Entity
@Table(name = "ORDER_RECORD")

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class OrderRecord {

    @Id
    private String orderId;

    private OrderStatus status;

    private Instant createdDate;

    private Instant updatedDate;

}

и OrderRepository.java:

public interface OrderRepository extends JpaRepository<OrderRecord, String> {
}

затем мы можем запустить приложение и увидеть, как происходит волшебство:

мы можем попробовать API, как показано ниже:

  • API профиля

  • почтовая сущность

2. Настройте конечную точку

Параметр Spring-Data-Rest по умолчанию предоставляет все конечные точки (POST, GET, PUT, DELETE…) на пути так же, как и имя объекта, то есть POST http://localhost:8083/orderRecords. Иногда мы можем захотеть

  1. скрыть некоторые операции,
  2. выставить какой-то конкретный поисковый API
  3. изменить путь API

Здесь мы расскажем, как сделать упомянутую настройку, есть много других конфигураций, которые можно найти в официальном документе.

Скрыть некоторые операции

В некоторых случаях, т.е. чистому серверу запросов в системе CQRS потребуется только открыть API GET и скрыть другие рабочие API. Есть два способа сделать это, и они связаны с некоторыми стратегиями установки файла свойств (или .yaml) spring.data.rest.detection-strategy.

  1. Используйте стратегию DEFAULT (не нужно устанавливать свойства) и добавьте RepositoryRestResource(exported = false)/@RestResource(exported = false) в репозиторий/методы, которые не нужно раскрывать.
  2. Используйте стратегию ANNOTATED и добавьте @RepositoryRestResource к объекту, который нужно выставить, а также добавьте @RestResource(exported = false) к методам в репозитории, которые не нужно выставлять.

Есть две стратегии, ALL и VISIBILITY, которые я считаю не очень полезными, поэтому мы их здесь пропустим.

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

public interface OrderRepository extends JpaRepository<OrderRecord, String> {

    @Override
    @RestResource(exported = false)
    void deleteById(String id);

    @Override
    @RestResource(exported = false)
    OrderRecord save(OrderRecord orderRecord);

}

Таким образом, мы можем отключить конечную точку API, которая нам не нужна.

Специальный поисковый API

Иногда нам понадобятся некоторые API, которые получают доступ к данным по другим полям вместо первичного ключа (PK). Затем нам нужно реализовать некоторые API, такие как API поиска. В Spring-Date-Rest это также рассматривается. мы можем написать метод в классе репозитория:

public interface OrderEventRepository extends JpaRepository<OrderEventRecord, Long> {
    ...
    List<OrderEventRecord> findByOrderId(String orderId);
    ...
}

и будет сгенерирован соответствующий API (GET API и аргументы метода становятся параметрами запроса в запросе):

Изменить путь к API

Первое, что мы можем сделать, это изменить базовый путь всех API, предоставляемых Spring-Date-Rest. Самый простой способ — установить его в файле свойств (.yaml или .properties), как показано ниже:

# application.yaml
spring:
  data:
    rest:
      basePath: /api

# application.properties
spring.data.rest.basePath=/api

или мы можем сделать это в классе конфигурации, как это делает документ.

Вторая конфигурация — изменить путь от имени класса сущностей к нашему предпочтению. Библиотека предоставляет аннотацию @RepositoryRestResource для переопределения всех путей API в репозитории, как показано ниже:

@RepositoryRestResource(path = "v1-orders", collectionResourceRel = "v1-orders", itemResourceRel = "v1-orders")
public interface OrderRepository extends JpaRepository<OrderRecord, String> {
    ...
}

Как мы видим, по умолчанию метод поиска будет открыт, а путь к API будет таким же, как и имя метода. мы можем изменить его с помощью свойства в аннотации RestResource, как показано ниже:

@RepositoryRestResource(path = "v1-orders-log", collectionResourceRel = "v1-orders-log", itemResourceRel = "v1-orders-log")
public interface OrderEventRepository extends JpaRepository<OrderEventRecord, Long> {
    ...
    @RestResource(path = "order-id")
    List<OrderEventRecord> findByOrderId(String orderId);
    ...
}

Одно из обнаруженных ограничений заключается в том, что путь как в RepositoryRestResource, так и в @RestResource поддерживает только один сегмент (может содержать -, _). Другими словами, мы можем предоставлять API только onsome/base/api-path/repository-path/method-path.

3. Клиентский модуль для Spring-Data-Rest API

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

Поскольку API Spring-Date-Rest поддерживаются форматом HATEOAS, мы должны добавить соответствующую зависимость:

dependencies {
    ...
    // import from org.springframework.boot:spring-boot-dependencies:3.0.0 bom
    implementation 'org.springframework.boot:spring-boot-starter-hateoas'
    ...
}

И добавьте аннотацию @EnableHypermediaSupport, чтобы включить ее в классе конфигурации клиента:

@AutoConfiguration
@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)
public class OrderQueryClientConfig {

// @Observed, ObservationRegistry, MeterRegistry, MicrometerObservationCapability
// MicrometerCapability is for Observability. can ignore if you didn't use it 
    @Bean
    @Observed
    public OrderQueryClient orderQueryClient(ObservationRegistry observationRegistry, MeterRegistry meterRegistry) {
        Feign.Builder builder = Feign.builder()
                .logLevel(Logger.Level.FULL)
                .logger(new Slf4jLogger())
                .encoder(new JacksonEncoder(List.of(new JavaTimeModule())))
                .decoder(new JacksonDecoder(List.of(new JavaTimeModule())))
                .addCapability(new MicrometerObservationCapability(observationRegistry))
                .addCapability(new MicrometerCapability(meterRegistry));
        return new OrderQueryClient(builder);
    }

}

а затем мы можем реализовать класс клиента (класс модели можно найти здесь, я опущу их код):

public class OrderQueryClient {

    private final OrderQueryStub orderQueryStub;

    public OrderQueryClient(Feign.Builder builder) {
        OrderQueryStub feign = builder.target(OrderQueryStub.class, "http://localhost:8083");
        this.orderQueryStub = feign;
    }

    public V1Order get(String id) {
        return orderQueryStub.get(id).getContent();
    }

    private interface OrderQueryStub {

        String BASE_PATH = "/api/v1-orders";

        @RequestLine("GET " + BASE_PATH + "/{id}")
        @Headers("Content-Type: application/json")
        EntityModel<V1Order> get(@Param("id") String id);

    }

}

В моем случае я использую OpenFeign для создания своего клиента API. различия между чистыми клиентами Json OpenFeign:

  1. Тип возвращаемого класса должен быть обернут EntityModel
    (или CollectionModel для списка, карты и т. д. ссылка)
  2. Следует вызвать getContent() для использования данных объекта.

Здесь мы используем переменную поля OrderQueryStub для инкапсуляции клиента Feign, скрытия сложного формата ответа и возврата объекта только при обычном использовании. Это простой способ, но, игнорируя формат HATEOAS, мне, возможно, потребуется немного больше исследований, чтобы узнать, как его элегантно использовать.

Таким образом, клиент может использоваться другими модулями. Но если вам интересно, как упростить его использование, обратитесь к моей статье Как автоматически настроить компонент Spring Boot. Кроме того, есть проблема, с которой я сталкиваюсь при употреблении java.time.Instant:

окончательное использование в других модулях будет таким, как показано ниже:

@Service
@RequiredArgsConstructor
@Slf4j
@LogInfo
public class OrderService {

    private final OrderQueryClient orderQueryClient;
    private final OrderEventProducer orderEventProducer;

    public String completeOrder(String id) {
        V1Order result = orderQueryClient.get(id);
        if (result.status() == V1OrderStatus.CREATED) {
            orderEventProducer.create(new OrderEvent(id, COMPLETED, Instant.now()));
            return "OK";
        } else {
            throw new RuntimeException("order(id = {}) is not in right status.");
        }
    }

}

Краткое содержание

В этой статье представлена ​​библиотека Spring-Data-Rest, которая может помочь избавиться от бессмысленного кода, такого как контроллер и служба, для чистого API доступа к БД в приложении Spring Boot. В нем также рассказывается, как настроить открытые конечные точки, в том числе скрыть некоторые операции, открыть определенные поисковые API и изменить путь к API. Эти настройки могут быть достигнуты путем добавления аннотаций и настройки свойств в файле свойств. В статье также объясняется, как интегрировать Spring-HATEOAS, чтобы обернуть клиент API и предоставить другим микросервисам более простой способ использования этих API.

Я открыл соответствующий Pull Request (PR) в своем личном репозитории, не стесняйтесь получить более подробную информацию и полный код здесь.

Если вам понравилась эта статья, поделитесь ею и поддержите ее аплодисментами, а также подпишитесь на меня здесь, в Medium, чтобы узнать больше о загрузке Java и Spring. Спасибо, что прочитали.

Ссылка