API Client Module is declarative http client. It is replacement of OpenFeign. API Client use Spring Rest Client Interface
<dependency>
<groupId>com.blibli.oss</groupId>
<artifactId>blibli-backend-framework-api-client-loom</artifactId>
</dependency>
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);
}
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);
}
}
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)
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.
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
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);
}
}
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
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;
}
}