Service Call
The Service Call EIP is deprecated |
How can I call a remote service in a distributed system where the service is looked up from a service registry of some sorts?
Use a Service Call acting as a Messaging Gateway for distributed systems, that handles the complexity of calling the service in a reliable manner.
The pattern has the following noteworthy features:
-
Location transparency — Decouples Camel and the physical location of the services using logical names representing the services.
-
URI templating — Allows you to template the Camel endpoint URI as the physical endpoint to use when calling the service.
-
Service discovery - Looks up the service from a service registry of some sort to know the physical locations of the services.
-
Service filter — Allows you to filter unwanted services (for example, blacklisted or unhealthy services).
-
Service chooser — Allows you to choose the most appropriate service based on factors such as geographical zone, affinity, plans, canary deployments, and SLAs.
-
Load balancer — A preconfigured Service Discovery, Filter, and Chooser intended for a specific runtime (these three features combined as one).
In a nutshell, the EIP pattern sits between your Camel application and the services running in a distributed system (cluster). The pattern hides all the complexity of keeping track of all the physical locations where the services are running and allows you to call the service by a name.
Options
The Service Call eip supports 14 options, which are listed below.
Name | Description | Default | Type |
---|---|---|---|
name |
Required Sets the name of the service to use. |
String |
|
expression |
Configures the Expression using the given configuration. |
ServiceCallExpressionConfiguration |
|
uri |
The uri of the endpoint to send to. The uri can be dynamic computed using the org.apache.camel.language.simple.SimpleLanguage expression. |
String |
|
component |
The component to use. |
http |
String |
pattern |
Sets the optional ExchangePattern used to invoke this endpoint. Enum values:
|
ExchangePattern |
|
configurationRef |
Refers to a ServiceCall configuration to use. |
String |
|
serviceDiscoveryRef |
Sets a reference to a custom ServiceDiscovery to use. |
String |
|
serviceFilterRef |
Sets a reference to a custom ServiceFilter to use. |
String |
|
serviceChooserRef |
Sets a reference to a custom ServiceChooser to use. |
String |
|
loadBalancerRef |
Sets a reference to a custom ServiceLoadBalancer to use. |
String |
|
expressionRef |
Set a reference to a custom Expression to use. |
String |
|
serviceDiscoveryConfiguration |
Required Configures the ServiceDiscovery using the given configuration. |
ServiceCallServiceDiscoveryConfiguration |
|
serviceFilterConfiguration |
Required Configures the ServiceFilter using the given configuration. |
ServiceCallServiceFilterConfiguration |
|
loadBalancerConfiguration |
Required Configures the LoadBalancer using the given configuration. |
ServiceCallServiceLoadBalancerConfiguration |
|
disabled |
Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime. |
false |
Boolean |
description |
Sets the description of this node. |
DescriptionDefinition |
Using Service Call
The service to call is looked up in a service registry of some sorts such as Kubernetes, Consul, Zookeeper, DNS. The EIP separates the configuration of the service registry from the calling of the service.
When calling a service you may just refer to the name of the service in the EIP as shown below:
from("direct:start")
.serviceCall("foo")
.to("mock:result");
And in XML:
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:start"/>
<serviceCall name="foo"/>
<to uri="mock:result"/>
</route>
</camelContext>
Camel will then:
-
search for a service call configuration from the Camel context and registry
-
lookup a service with the name
from an external service registryfoo
-
filter the servers
-
select the server to use
-
build a Camel URI using the chosen server info
By default, the Service Call EIP uses camel-http
so assuming that the selected service
instance runs on host
on port myhost.com
, the computed Camel URI will be:80
http:myhost.com:80
Mapping Service Name to Endpoint URI
It is often needed to build more complex Camel URI which may include options or paths which is possible through different options:name: value
The service name supports a limited uri like syntax, here some examples
Name | Resolution |
---|---|
foo |
|
foo/path |
|
foo/path?foo=bar |
from("direct:start")
.serviceCall("foo/hello")
.to("mock:result");
If you want to have more control over the uri construction, you can use the uri directive:
Name | URI | Resolution |
---|---|---|
foo |
undertow:http://foo/hello |
undertow:http://host:port/hello |
foo |
undertow:http://foo.host:foo.port/hello |
undertow:http://host:port/hello |
from("direct:start")
.serviceCall("foo", "undertow:http://foo/hello")
.to("mock:result");
Advanced users can have full control over the uri construction through expressions:
from("direct:start")
.serviceCall()
.name("foo")
.expression()
.simple("undertow:http://${header.CamelServiceCallServiceHost}:${header.CamelServiceCallServicePort}/hello");
Static Service Discovery
This service discovery implementation does not query any external services to find out the list of services associated to a named service but keep them in memory. Each service should be provided in the following form:
[service@]host:port
The |
This implementation is provided by |
Available options:
Name | Java Type | Description |
---|---|---|
servers |
|
A comma separated list of servers in the form: [service@]host:port,[service@]host2:port,[service@]host3:port |
from("direct:start")
.serviceCall("foo")
.staticServiceDiscovery()
.servers("service1@host1:80,service1@host2:80")
.servers("service2@host1:8080,service2@host2:8080,service2@host3:8080")
.end()
.to("mock:result");
And in XML:
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:start"/>
<serviceCall name="foo">
<staticServiceDiscovery>
<servers>service1@host1:80,service1@host2:80</servers>
<servers>service2@host1:8080,service2@host2:8080,service2@host3:8080</servers>
</staticServiceDiscovery>
</serviceCall>
<to uri="mock:result"/>
</route>
</camelContext>
Consul Service Discovery
To leverage Consul for Service Discovery, maven users will need to add the following dependency to their pom.xml
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-consul</artifactId>
<!-- use the same version as your Camel core version -->
<version>x.y.z</version>
</dependency>
Available options:
Name | Java Type | Description |
---|---|---|
url |
|
The Consul agent URL |
datacenter |
|
The data center |
aclToken |
|
Sets the ACL token to be used with Consul |
userName |
|
Sets the username to be used for basic authentication |
password |
|
Sets the password to be used for basic authentication |
connectTimeoutMillis |
|
Connect timeout for OkHttpClient |
readTimeoutMillis |
|
Read timeout for OkHttpClient |
writeTimeoutMillis |
|
Write timeout for OkHttpClient |
And example in Java
from("direct:start")
.serviceCall("foo")
.consulServiceDiscovery()
.url("http://consul-cluster:8500")
.datacenter("neverland")
.end()
.to("mock:result");
DNS Service Discovery
To leverage DNS for Service Discovery, maven users will need to add the following dependency to their pom.xml
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-dns</artifactId>
<!-- use the same version as your Camel core version -->
<version>x.y.z</version>
</dependency>
Available options:
Name | Java Type | Description |
---|---|---|
proto |
|
The transport protocol of the desired service, default "_tcp" |
domain |
|
The user name to use for basic authentication |
Example in Java:
from("direct:start")
.serviceCall("foo")
.dnsServiceDiscovery("my.domain.com")
.to("mock:result");
And in XML:
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:start"/>
<serviceCall name="foo">
<dnsServiceDiscovery domain="my.domain.com"/>
</serviceCall>
<to uri="mock:result"/>
</route>
</camelContext>
Kubernetes Service Discovery
To leverage Kubernetes for Service Discovery, maven users will need to add the following dependency to their pom.xml
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-kubernetes</artifactId>
<!-- use the same version as your Camel core version -->
<version>x.y.z</version>
</dependency>
Available options:
Name | Java Type | Description |
---|---|---|
lookup |
|
How to perform service lookup. Possible values: client, dns, environment |
apiVersion |
|
Kubernetes API version when using client lookup |
caCertData |
|
Sets the Certificate Authority data when using client lookup |
caCertFile |
|
Sets the Certificate Authority data that are loaded from the file when using client lookup |
clientCertData |
|
Sets the Client Certificate data when using client lookup |
clientCertFile |
|
Sets the Client Certificate data that are loaded from the file when using client lookup |
clientKeyAlgo |
|
Sets the Client Keystore algorithm, such as RSA when using client lookup |
clientKeyData |
|
Sets the Client Keystore data when using client lookup |
clientKeyFile |
|
Sets the Client Keystore data that are loaded from the file when using client lookup |
clientKeyPassphrase |
|
Sets the Client Keystore passphrase when using client lookup |
dnsDomain |
|
Sets the DNS domain to use for dns lookup |
namespace |
|
The Kubernetes namespace to use. By default the namespace’s name is taken from the environment variable KUBERNETES_MASTER |
oauthToken |
|
Sets the OAUTH token for authentication (instead of username/password) when using client lookup |
username |
|
Sets the username for authentication when using client lookup |
password |
|
Sets the password for authentication when using client lookup |
trustCerts |
|
Sets whether to turn on trust certificate check when using client lookup |
Example in Java:
from("direct:start")
.serviceCall("foo")
.kubernetesServiceDiscovery()
.lookup("dns")
.namespace("myNamespace")
.dnsDomain("my.domain.com")
.end()
.to("mock:result");
And in XML:
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:start"/>
<serviceCall name="foo">
<kubernetesServiceDiscovery lookup="dns" namespace="myNamespace" dnsDomain="my.domain.com"/>
</serviceCall>
<to uri="mock:result"/>
</route>
</camelContext>
Using service filtering
The Service Call EIP supports filtering the services using built-in filters, or a custom filter.
Blacklist Service Filter
This service filter implementation removes the listed services from those found by the service discovery. Each service should be provided in the following form:
[service@]host:port
The services are removed if they fully match |
Available options:
Name | Java Type | Description |
---|---|---|
servers |
|
A comma separated list of servers to blacklist: [service@]host:port,[service@]host2:port,[service@]host3:port |
Example in Java:
from("direct:start")
.serviceCall("foo")
.staticServiceDiscovery()
.servers("service1@host1:80,service1@host2:80")
.servers("service2@host1:8080,service2@host2:8080,service2@host3:8080")
.end()
.blacklistFilter()
.servers("service2@host2:8080")
.end()
.to("mock:result");
And in XML:
<camelContext xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:start"/>
<serviceCall name="foo">
<staticServiceDiscovery>
<servers>service1@host1:80,service1@host2:80</servers>
<servers>service2@host1:8080,service2@host2:8080,service2@host3:8080</servers>
</staticServiceDiscovery>
<blacklistServiceFilter>
<servers>service2@host2:8080</servers>
</blacklistServiceFilter>
</serviceCall>
<to uri="mock:result"/>
</route>
</camelContext>
Custom Service Filter
Service Filters choose suitable candidates from the service definitions found in the service discovery.
The service filter have access to the current exchange, which allows you to create service filters comparing service metadata with message content.
Assuming you have labeled one of the services in your service discovery to support a certain type of requests:
serviceDiscovery.addServer(new DefaultServiceDefinition("service", "127.0.0.1", 1003,
Collections.singletonMap("supports", "foo")));
The current exchange has a property which says that it needs a foo service:
exchange.setProperty("needs", "foo");
You can then use a ServiceFilter
to select the service instances which match the exchange:
from("direct:start")
.serviceCall()
.name("service")
.serviceFilter((exchange, services) -> services.stream()
.filter(serviceDefinition -> Optional.ofNullable(serviceDefinition.getMetadata()
.get("supports"))
.orElse("")
.equals(exchange.getProperty("needs", String.class)))
.collect(Collectors.toList()));
.end()
.to("mock:result");
Shared configurations
The Service Call EIP can be configured straight on the route definition or through shared configurations,
here an example with two configurations registered in the CamelContext
:
ServiceCallConfigurationDefinition globalConf = new ServiceCallConfigurationDefinition();
globalConf.setServiceDiscovery(
name -> Arrays.asList(
new DefaultServiceDefinition(name, "my.host1.com", 8080),
new DefaultServiceDefinition(name, "my.host2.com", 443))
);
globalConf.setServiceChooser(
list -> list.get(ThreadLocalRandom.current().nextInt(list.size()))
);
ServiceCallConfigurationDefinition httpsConf = new ServiceCallConfigurationDefinition();
httpsConf.setServiceFilter(
list -> list.stream().filter((exchange, s) -> s.getPort() == 443).collect(toList())
);
getContext().setServiceCallConfiguration(globalConf);
getContext().addServiceCallConfiguration("https", httpsConf);
Each Service Call definition and configuration will inherit from the globalConf
which can be seen as default configuration,
then you can reference the httpsConf
in your route:
from("direct:start")
.serviceCall()
.name("foo")
.serviceCallConfiguration("https")
.end()
.to("mock:result");
This route will leverage the service discovery and service chooser from globalConf
and the service filter from `httpsConf,
but you can override any of them if needed straight on the route:
from("direct:start")
.serviceCall()
.name("foo")
.serviceCallConfiguration("https")
.serviceChooser(list -> list.get(0))
.end()
.to("mock:result");