XUtils

Riptide

Client-side response routing for Spring's RestTemplate.


Dependencies

  • Java 17
  • Spring 6

Configuration

Integration of your typical Spring Boot Application with Riptide, Logbook and Tracer can be greatly simplified by using the Riptide: Spring Boot Starter. Go check it out!

Http.builder()
    .executor(Executors.newCachedThreadPool())
    .requestFactory(new HttpComponentsClientHttpRequestFactory())
    .baseUrl("https://api.github.com")
    .converter(new MappingJackson2HttpMessageConverter())
    .converter(new Jaxb2RootElementHttpMessageConverter())
    .plugin(new OriginalStackTracePlugin())
    .build();

The following code is the bare minimum, since a request factory is required:

Http.builder()
    .executor(Executors.newCachedThreadPool())
    .requestFactory(new HttpComponentsClientHttpRequestFactory())
    .build();

This defaults to:

Examples

  1. Without queue, elastic size

    ThreadPoolExecutors.builder()
        .withoutQueue()
        .elasticSize(5, 20)
        .keepAlive(1, MINUTES)
        .build()
    
  2. Bounded queue, fixed size

    ThreadPoolExecutors.builder()
        .boundedQueue(20)
        .fixedSize(20)
        .keepAlive(1, MINUTES)
        .build()
    
  3. Scale-first, unbounded queue, elastic size

    ThreadPoolExecutors.builder()
        .scaleFirst()
        .unboundedQueue()
        .elasticSize(20)   
        .keepAlive(1, MINUTES)
        .build()
    

You can read more about scale-first here:

In order to configure the thread pool correctly, please refer to How to set an ideal thread pool size.

URI Template

URI

  • none, used as is
  • expected to be already encoded

Responses

Riptide is special in the way it handles responses. Rather than having a single return value, you need to register callbacks. Traditionally, you would attach different callbacks for different response status codes. Alternatively, there are built-in routing capabilities on status code families (called series in Spring) as well as on content types.

http.post("/sales-order")
    // ...
    .dispatch(series(),
        on(SUCCESSFUL).dispatch(contentType(),
            on(SALES_ORDER).call(SalesOrder.class, this::persist),
        on(CLIENT_ERROR).dispatch(status(),
            on(CONFLICT).call(this::retry),
            on(PRECONDITION_FAILED).call(this::readAgainAndRetry),
            anyStatus().call(problemHandling())),
        on(SERVER_ERROR).dispatch(status(),
            on(SERVICE_UNAVAILABLE).call(this::scheduleRetryLater))));

The callbacks can have the following signatures:

persist(SalesOrder)
retry(ClientHttpResponse)
scheduleRetryLater()

Futures

Riptide will return a CompletableFuture<ClientHttpResponse>. That means you can choose to chain transformations/callbacks or block on it.

If you need proper return values take a look at Riptide: Capture.

Exceptions

The only special custom exception you may receive is UnexpectedResponseException, if and only if there was no matching condition and no wildcard condition.

Testing

Riptide is built on the same foundation as Spring’s RestTemplate. That allows us, with a small trick, to use the same testing facilities, the MockRestServiceServer:

RestTemplate template = new RestTemplate();
MockRestServiceServer server = MockRestServiceServer.createServer(template);
ClientHttpRequestFactory requestFactory = template.getRequestFactory();

Http.builder()
    .requestFactory(requestFactory)
    // continue configuration

We basically use an intermediate RestTemplate as a holder of the special ClientHttpRequestFactory that the MockRestServiceServer manages.

If you are using Spring Boot Starter, the test setup is provided by a convenient annotation @RiptideClientTest. See here.

Getting help

If you have questions, concerns, bug reports, etc., please file an issue in this repository’s Issue Tracker.

Credits and references


Articles

  • coming soon...