How CoApi Integrates with Spring Cloud LoadBalancer for Service Discovery
CoApi integrates with Spring Cloud LoadBalancer by detecting load-balanced declarations through the @LoadBalanced annotation or lb:// URL scheme, then injecting Spring Cloud's LoadBalancerInterceptor or LoadBalancedExchangeFilterFunction into the underlying HTTP client to enable automatic service discovery and request routing.
The ahoo-wang/coapi library provides a declarative HTTP client framework for Spring Boot that abstracts service-to-service communication. By leveraging Spring Cloud LoadBalancer, CoApi automatically routes requests to healthy service instances registered in discovery systems like Eureka, Consul, or Kubernetes without requiring manual load-balancing logic in application code.
Detecting Load-Balanced APIs
CoApi determines whether an interface requires load balancing during the annotation processing phase. Two mechanisms trigger load-balanced mode:
@LoadBalancedannotation – A marker annotation applied to the interfacelb://URL scheme – A base URL prefix indicating load-balanced resolution
Both conditions are evaluated in CoApiDefinition.kt:
val resolvedLoadBalanced = getAnnotation(LoadBalanced::class.java) != null
val baseUrlLoadBalanced = resolvedBaseUrl.startsWith(LB_PROTOCOL_PREFIX)
val loadBalanced = resolvedLoadBalanced || baseUrlLoadBalanced
When either condition evaluates to true, the CoApiDefinition.loadBalanced property becomes true and the base URL transforms from lb://service-id to http://service-id for the underlying HTTP client.
Configuring Load-Balanced HTTP Clients
The loadBalanced flag stored in CoApiDefinition drives configuration in the client factory beans. Both synchronous and reactive clients extend AbstractHttpClientFactoryBean to access this flag via the loadBalanced() method.
Synchronous Client Integration
For synchronous HTTP clients using Spring 5's RestClient, RestClientFactoryBean.kt registers a RestClientBuilderCustomizer that conditionally adds the LoadBalancerInterceptor:
if (loadBalanced()) {
val loadBalancerInterceptor = appContext.getBean(LoadBalancerInterceptor::class.java)
it.add(loadBalancerInterceptor)
}
The LoadBalancerInterceptor intercepts outgoing requests, extracts the service ID from the URL (e.g., http://todo-service), and delegates to Spring Cloud LoadBalancer to select a concrete instance from the service registry.
Reactive Client Integration
For reactive stacks using Spring WebFlux's WebClient, WebClientFactoryBean.kt injects the reactive LoadBalancedExchangeFilterFunction:
val hasLoadBalancedFilter = it.any { filter -> filter is LoadBalancedExchangeFilterFunction }
if (loadBalanced() && !hasLoadBalancedFilter) {
appContext.getBean(LoadBalancedExchangeFilterFunction::class.java)
}
This filter function rewrites the request URI and performs the load-balancing lookup for each reactive request stream.
Runtime Service Discovery Flow
When an application sends a request through a CoApi interface, the integration follows this execution path:
- Request Interception – The injected
LoadBalancerInterceptor(sync) orLoadBalancedExchangeFilterFunction(reactive) intercepts the request containing the logical service ID - Service Resolution – Spring Cloud LoadBalancer queries the configured
DiscoveryClient(Eureka, Consul, etc.) to retrieve healthy instances of the target service - Instance Selection – The load balancer applies its selection strategy (round-robin, random, weighted) to choose a specific host and port
- Request Routing – The request proceeds to the selected concrete instance
This architecture abstracts the service discovery complexity, allowing developers to work with logical service identifiers while CoApi and Spring Cloud LoadBalancer handle the dynamic resolution.
Implementation Example
The following example demonstrates a complete CoApi setup with Spring Cloud LoadBalancer integration:
// 1. Define the CoApi interface with load balancing
@CoApi(serviceId = "todo-service")
@LoadBalanced
interface TodoApi {
@GetMapping("/todos/{id}")
suspend fun findById(@PathVariable id: String): Todo
}
// 2. Enable CoApi in your Spring Boot application
@SpringBootApplication
@EnableCoApi
class DemoApplication
// 3. Inject and use the client
@Service
class TodoService(private val todoApi: TodoApi) {
suspend fun getTodo(id: String): Todo {
// Automatically load-balanced across todo-service instances
return todoApi.findById(id)
}
}
Alternatively, you can omit @LoadBalanced and specify the lb:// scheme in the @CoApi annotation:
@CoApi(baseUrl = "lb://todo-service")
interface TodoApi {
@GetMapping("/todos")
fun listTodos(): List<Todo>
}
Summary
- Detection mechanism – CoApi identifies load-balanced APIs via the
@LoadBalancedannotation orlb://URL prefix inCoApiDefinition.kt - Client configuration –
RestClientFactoryBeaninjectsLoadBalancerInterceptorfor synchronous clients, whileWebClientFactoryBeaninjectsLoadBalancedExchangeFilterFunctionfor reactive clients - Automatic resolution – Requests to logical service IDs are automatically resolved to concrete instances via Spring Cloud LoadBalancer
- Zero boilerplate – Developers declare interfaces with
serviceIdattributes while CoApi handles the integration with service registries like Eureka or Consul
Frequently Asked Questions
How does CoApi know when to use load balancing without the @LoadBalanced annotation?
CoApi checks for the lb:// protocol prefix in the baseUrl attribute of the @CoApi annotation. In CoApiDefinition.kt, the logic resolvedBaseUrl.startsWith(LB_PROTOCOL_PREFIX) evaluates to true when the URL starts with lb://, automatically enabling load-balanced mode without requiring the explicit annotation marker.
What happens if a service is not registered in the discovery client?
If the service ID specified in the CoApi interface cannot be resolved by the configured DiscoveryClient, Spring Cloud LoadBalancer throws a NoSuchElementException or similar error indicating that no instances are available for the requested service. CoApi propagates this exception to the caller, allowing standard error handling mechanisms to manage service unavailability.
Does CoApi support reactive and synchronous clients equally?
Yes. CoApi provides parallel implementations for both programming models. RestClientFactoryBean handles synchronous HTTP clients using Spring's RestClient, while WebClientFactoryBean manages reactive clients using Spring WebFlux's WebClient. Both factories check the loadBalanced flag and inject the appropriate Spring Cloud LoadBalancer component for their respective execution models.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →