Skip to content

Instantly share code, notes, and snippets.

@ganiszulfa
Created July 3, 2025 08:09
Show Gist options
  • Save ganiszulfa/b5b236aa3f38a37d4bc456c3eeb12f71 to your computer and use it in GitHub Desktop.
Save ganiszulfa/b5b236aa3f38a37d4bc456c3eeb12f71 to your computer and use it in GitHub Desktop.
bbf docs

API Client Module

API Client Module is declarative http client. It is replacement of OpenFeign. API Client use Spring Rest Client Interface

Setup Dependency

<dependency>
  <groupId>com.blibli.oss</groupId>
  <artifactId>blibli-backend-framework-api-client-loom</artifactId>
</dependency>

Create API Client

To Create API Client, we can create interface with annotation @ApiClient.

import org.springframework.http.ResponseEntity;
import org.springframework.web.service.annotation.GetExchange;

@ApiClient(
  name = "binListApiClient"
)
public interface BinListApiClient {

  @GetExchange(
    url = "/{number}",
    produces = MediaType.APPLICATION_JSON_VALUE
  )
  ResponseEntity<BinResponse> lookup(@PathVariable("number") String number);

}

Register API Client

And to register it, we need to create bean with ApiClientFactoryBean

import com.blibli.oss.backend.apiclient.loom.factory.ApiClientFactoryBean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class YouConfiguration {

  @Bean
  public ApiClientFactoryBean<BinListApiClient> binListApiClientApiClientFactoryBean() {
    return new ApiClientFactoryBean<>(BinListApiClient.class);
  }

}

API Client Configuration

To configure API Client, we can use properties with bean name.

blibli.backend.apiclient.http-client-type=http_client

blibli.backend.apiclient.configs.binListApiClient.url=http://localhost:8090
blibli.backend.apiclient.configs.binListApiClient.http-client.connect-request-timeout=5s
blibli.backend.apiclient.configs.binListApiClient.http-client.connect-timeout=30s
blibli.backend.apiclient.configs.binListApiClient.http-client.read-timeout=30s
blibli.backend.apiclient.configs.binListApiClient.http-client.connection-time-to-live=300s
blibli.backend.apiclient.configs.binListApiClient.http-client.disabled-cookie-management=true
blibli.backend.apiclient.configs.binListApiClient.http-client.use-system-properties=true
blibli.backend.apiclient.configs.binListApiClient.http-client.max-connections=100
blibli.backend.apiclient.configs.binListApiClient.http-client.max-connections-per-route=10
blibli.backend.apiclient.configs.binListApiClient.http-client.pool-reuse-policy=lifo
blibli.backend.apiclient.configs.binListApiClient.http-client.pool-concurrency-policy=lax
blibli.backend.apiclient.configs.binListApiClient.http-client.evict-expired-connections=true
blibli.backend.apiclient.configs.binListApiClient.http-client.max-idle-time=5m
blibli.backend.apiclient.configs.binListApiClient.interceptors[0]=com.blibli.oss.backend.apiclient.loom.EmptyApiClientInterceptor
blibli.backend.apiclient.configs.binListApiClient.web-client-customizers[0]=com.blibli.oss.backend.apiclient.loom.EmptyWebClientCustomizer

binListApiClient is name of @ApiClient(name)

Default Configuration

Sometimes we have multiple API Client with same configuration, like headers, or interceptor. API Client module support default configuration, where we can share configuration for all API Client.

Without default configuration, we need to create properties like this :

blibli.backend.apiclient.configs.firstApiClient.url=https://firt-host:8080
blibli.backend.apiclient.configs.firstApiClient.http-client.connect-request-timeout=5s
blibli.backend.apiclient.configs.firstApiClient.http-client.connect-timeout=30s
blibli.backend.apiclient.configs.firstApiClient.http-client.read-timeout=30s
blibli.backend.apiclient.configs.firstApiClient.http-client.connection-time-to-live=300s
blibli.backend.apiclient.configs.firstApiClient.http-client.disabled-cookie-management=true
blibli.backend.apiclient.configs.firstApiClient.http-client.use-system-properties=true
blibli.backend.apiclient.configs.firstApiClient.http-client.max-connections=100
blibli.backend.apiclient.configs.firstApiClient.http-client.max-connections-per-route=10
blibli.backend.apiclient.configs.firstApiClient.http-client.pool-reuse-policy=lifo
blibli.backend.apiclient.configs.firstApiClient.http-client.pool-concurrency-policy=lax
blibli.backend.apiclient.configs.firstApiClient.http-client.evict-expired-connections=true
blibli.backend.apiclient.configs.firstApiClient.http-client.max-idle-time=5m
blibli.backend.apiclient.configs.firstApiClient.interceptors[0]=com.blibli.oss.backend.apiclient.loom.EmptyApiClientInterceptor
blibli.backend.apiclient.configs.firstApiClient.web-client-customizers[0]=com.blibli.oss.backend.apiclient.loom.EmptyWebClientCustomizer

blibli.backend.apiclient.configs.secondApiClient.url=https://second-host:8080
blibli.backend.apiclient.configs.secondApiClient.http-client.connect-request-timeout=5s
blibli.backend.apiclient.configs.secondApiClient.http-client.connect-timeout=30s
blibli.backend.apiclient.configs.secondApiClient.http-client.read-timeout=30s
blibli.backend.apiclient.configs.secondApiClient.http-client.connection-time-to-live=300s
blibli.backend.apiclient.configs.secondApiClient.http-client.disabled-cookie-management=true
blibli.backend.apiclient.configs.secondApiClient.http-client.use-system-properties=true
blibli.backend.apiclient.configs.secondApiClient.http-client.max-connections=100
blibli.backend.apiclient.configs.secondApiClient.http-client.max-connections-per-route=10
blibli.backend.apiclient.configs.secondApiClient.http-client.pool-reuse-policy=lifo
blibli.backend.apiclient.configs.secondApiClient.http-client.pool-concurrency-policy=lax
blibli.backend.apiclient.configs.secondApiClient.http-client.evict-expired-connections=true
blibli.backend.apiclient.configs.secondApiClient.http-client.max-idle-time=5m
blibli.backend.apiclient.configs.secondApiClient.interceptors[0]=com.blibli.oss.backend.apiclient.loom.EmptyApiClientInterceptor
blibli.backend.apiclient.configs.secondApiClient.web-client-customizers[0]=com.blibli.oss.backend.apiclient.loom.EmptyWebClientCustomizer

blibli.backend.apiclient.configs.thirdApiClient.url=https://third-host:8080
blibli.backend.apiclient.configs.thirdApiClient.http-client.connect-request-timeout=5s
blibli.backend.apiclient.configs.thirdApiClient.http-client.connect-timeout=30s
blibli.backend.apiclient.configs.thirdApiClient.http-client.read-timeout=30s
blibli.backend.apiclient.configs.thirdApiClient.http-client.connection-time-to-live=300s
blibli.backend.apiclient.configs.thirdApiClient.http-client.disabled-cookie-management=true
blibli.backend.apiclient.configs.thirdApiClient.http-client.use-system-properties=true
blibli.backend.apiclient.configs.thirdApiClient.http-client.max-connections=100
blibli.backend.apiclient.configs.thirdApiClient.http-client.max-connections-per-route=10
blibli.backend.apiclient.configs.thirdApiClient.http-client.pool-reuse-policy=lifo
blibli.backend.apiclient.configs.thirdApiClient.http-client.pool-concurrency-policy=lax
blibli.backend.apiclient.configs.thirdApiClient.http-client.evict-expired-connections=true
blibli.backend.apiclient.configs.thirdApiClient.http-client.max-idle-time=5m
blibli.backend.apiclient.configs.thirdApiClient.interceptors[0]=com.blibli.oss.backend.apiclient.loom.EmptyApiClientInterceptor
blibli.backend.apiclient.configs.thirdApiClient.web-client-customizers[0]=com.blibli.oss.backend.apiclient.loom.EmptyWebClientCustomizer

With default configuration, we can simplify properties file like this :

# default properties
blibli.backend.apiclient.configs.default.http-client.connect-request-timeout=5s
blibli.backend.apiclient.configs.default.http-client.connect-timeout=30s
blibli.backend.apiclient.configs.default.http-client.read-timeout=30s
blibli.backend.apiclient.configs.default.http-client.connection-time-to-live=300s
blibli.backend.apiclient.configs.default.http-client.disabled-cookie-management=true
blibli.backend.apiclient.configs.default.http-client.use-system-properties=true
blibli.backend.apiclient.configs.default.http-client.max-connections=100
blibli.backend.apiclient.configs.default.http-client.max-connections-per-route=10
blibli.backend.apiclient.configs.default.http-client.pool-reuse-policy=lifo
blibli.backend.apiclient.configs.default.http-client.pool-concurrency-policy=lax
blibli.backend.apiclient.configs.default.http-client.evict-expired-connections=true
blibli.backend.apiclient.configs.default.http-client.max-idle-time=5m

blibli.backend.apiclient.configs.firstApiClient.url=https://firt-host:8080
blibli.backend.apiclient.configs.firstApiClient.interceptors[0]=com.company.project.apiclient.interceptor.YourFirstInterceptor

blibli.backend.apiclient.configs.secondApiClient.url=https://second-host:8080
blibli.backend.apiclient.configs.secondApiClient.interceptors[0]=com.company.project.apiclient.interceptor.YourSecondInterceptor

blibli.backend.apiclient.configs.thirdApiClient.url=https://third-host:8080
blibli.backend.apiclient.configs.thirdApiClient.interceptors[0]=com.company.project.apiclient.interceptor.YourThirdInterceptor

With default properties, all config from default properties will copies to our API Client properties. But the config will be copied only if API Client properties is null, so it will not override existing properties.

Interceptor

Some times we want to do something before or after http request using API Client. We can use ApiClientInterceptor. ApiClientInterceptor is interceptor that extend spring web client ``. We can add action before and after http request.

import com.blibli.oss.backend.apiclient.loom.interceptor.ApiClientInterceptor;

@Component
public class ExampleApiClientInterceptor implements ApiClientInterceptor {

  @Override
  public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    return execution.execute(request, body);
  }
}

We can register ApiClientInterceptor using annotation :

@ApiClient(
  name = "aggregateQueryApiClient",
  interceptors = {
    AggregateQueryApiClientInterceptor.class
  }
)
public interface AggregateQueryApiClient {
  
}

or using properties :

blibli.backend.apiclient.configs.aggregateQueryApiClient.interceptors[0]=com.blibli.oss.backend.aggregate.query.interceptor.AggregateQueryApiClientInterceptor
blibli.backend.apiclient.configs.aggregateQueryApiClient.interceptors[1]=com.blibli.oss.backend.aggregate.query.interceptor.OtherInterceptor

We can add more than one ApiClientInterceptor

Global Interceptor

By default, Interceptor only works per API Client. Sometimes we want to create global interceptor, what works on all API Client. To handle this problem, API Client module also has GlobalApiClientInterceptor interface. We only need to create spring bean of this interface, and it will automatically registered to all API Client.

@Component
public static class EchoGlobalApiClientInterceptor implements GlobalApiClientInterceptor {

  @Override
  public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    return execution.execute(request, body);
  }

}

Web Client Customizer

Api Client Module using Spring WebClient as http client. Sometimes we want to change configuration of WebClient. Api Client Module provide ApiClientWebClientCustomizer to customize WebClient creation.

import com.blibli.oss.backend.apiclient.loom.customizer.ApiClientWebClientCustomizer;

@Component
public class BinListWebClientCustomizer implements ApiClientWebClientCustomizer {

  @Override
  public void customize(RestClient.Builder builder) {

  }
}

We can register ApiClientWebClientCustomizer using annotation :

@ApiClient(
  name = "binListApiClient",
  webClientCustomizers = {
    BinListWebClientCustomizer.class
  }
)
public interface BinListApiClient {
  
}

Or using properties :

blibli.backend.apiclient.configs.binListApiClient.web-client-customizers[0]=com.blibli.oss.backend.example.client.customizer.BinListWebClientCustomizer

We can add more than one ApiClientWebClientCustomizer

Custom HTTP Client

By default API Client Loom using Jetty Http Client, but we can change it to another http client. You can create custom http client by creating bean implementing ClientHttpRequestFactoryManager interface.

import com.blibli.oss.backend.apiclient.loom.manager.ClientHttpRequestFactoryManager;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;

@Component
public class JettyClientHttpRequestFactoryManager implements ClientHttpRequestFactoryManager {

  @Override
  public ClientHttpRequestFactory create(ApiClientProperties.ApiClientConfigProperties properties) {
    HttpClient httpClient = // create your http client here

      HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
    // configure your factory here

    return factory;
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment