This guide documents how chenile-service-registry and chenile-proxies work together, and how to replace direct RestClient usage with a Chenile proxy.
Why these two repositories matter together
chenile-service-registry answers:
- what remote Chenile services exist
- what operations they expose
- what URLs and return types they advertise
chenile-proxies answers:
- how a caller invokes one of those services through a Java interface
- whether the call should be routed locally or remotely
- how request and response conversion should happen
Together, they let an application depend on a service interface instead of hand-writing HTTP client code.
Module roles
chenile-service-registry
Modules:
service-registry-apiservice-registry-serviceservice-registry-delegate
Responsibilities:
service-registry-apicontainsChenileRemoteServiceDefinition,ChenileRemoteOperationDefinition,ServiceRegistryService, and the local near-cache.service-registry-servicehosts the registry, persists definitions, and publishes local Chenile services into the registry at startup.service-registry-delegateis the client-side delegate that talks to a remote registry endpoint and maintains a local cache of remote definitions.
chenile-proxies
Modules:
chenile-proxy
Responsibilities:
ProxyBuildercreates a Java proxy for a Chenile service interface.ProxyUtilslooks up remote service definitions and operation definitions from the registry.RemoteProxyInvokerperforms the actual remote invocation.ResponseBodyTypeSelectorreconstructs the correctGenericResponse<T>type for remote calls using service-registry metadata.
End-to-end flow
Server side
- The server hosts
service-registry-service. - On startup,
ServiceRegistryInitializerwalks the local Chenile configuration and saves each service definition. - Those definitions include:
- service id
- version
- base URL
- operation URLs
- output type metadata
Client side
- The client includes
service-registry-delegate. RemoteServiceRegistryInitializercan publish local client services to a remote registry if needed.ServiceRegistryClientImplreads remote definitions from the server-side registry and caches them locally.ProxyBuilderuses those definitions to create an interface-based proxy.- Business code calls the Java interface, not HTTP.
Configuration needed
Server package
The server must include the hosted registry service:
<dependency>
<groupId>org.chenile</groupId>
<artifactId>service-registry-service</artifactId>
</dependency>
And the Spring application must scan:
org.chenile.service.registry.configuration
Client package
The client must include:
<dependency>
<groupId>org.chenile</groupId>
<artifactId>service-registry-delegate</artifactId>
</dependency>
<dependency>
<groupId>org.chenile</groupId>
<artifactId>chenile-proxy</artifactId>
</dependency>
The client must also know where the remote registry is hosted:
chenile:
remote:
service:
registry: http://localhost:8000
Replacing RestClient with Chenile proxy
Before
The direct HTTP approach hard-codes URL structure and response parsing in application code.
Example pattern:
restClient.post()
.uri("/case-history")
.body(entry)
.retrieve()
.toBodilessEntity();
And:
restClient.get()
.uri("/case-history/{caseId}", caseId)
.retrieve()
.body(CASE_HISTORY_LIST_RESPONSE);
Problems with this style:
- URL knowledge is duplicated in the client
- response typing is manual
- transport details leak into business code
- moving a service between local and remote invocation requires client rewrites
After
Define and depend on the service interface:
CaseHistoryService
Create a proxy bean:
@Bean
CaseHistoryService caseHistoryService(ProxyBuilder proxyBuilder) {
return proxyBuilder.buildProxy(CaseHistoryService.class, "caseHistoryService", null);
}
Then business code becomes:
caseHistoryService.save(entry);
List<CaseHistoryEntry> entries = caseHistoryService.retrieve(caseId);
This is exactly the pattern now used by:
AiModelConfigurationCaseMemoryService
What the proxy needs
For ProxyBuilder to work, the service must be present in the registry with a Chenile service id.
In the casehistory example:
- the interface is
CaseHistoryService - the Chenile controller is registered as service id
caseHistoryService
Specifically:
@ChenileController(value = "caseHistoryService", serviceName = "_caseHistoryService_")
The proxy bean must use the same service id:
proxyBuilder.buildProxy(CaseHistoryService.class, "caseHistoryService", null)
How response typing works
Remote Chenile calls return GenericResponse<T>.
The proxy layer reconstructs T from service-registry metadata.
That is handled in:
ChenileRemoteOperationDefinitionResponseBodyTypeSelector
This is why the client can call:
List<CaseHistoryEntry> entries = caseHistoryService.retrieve(caseId);
without hand-writing a ParameterizedTypeReference.
When to keep RestClient
Use a Chenile proxy when:
- the target is a Chenile service
- the target is registered in the service registry
- you want interface-based invocation
- you want to hide transport details from business code
Use RestClient when:
- the remote system is not a Chenile service
- you need a one-off external API client
- the service contract is not represented as a shared Java interface
Migration checklist
- Move the target contract into a shared API module.
- Host the target service through a normal Chenile controller and service id.
- Include
service-registry-serviceon the server. - Include
service-registry-delegateandchenile-proxyon the client. - Point
chenile.remote.service.registryto the server. - Create a proxy bean with
ProxyBuilder. - Replace
RestClientcode with the shared interface. - Remove manual URL and response-type handling from business services.