Discovering Services
Depending on which Discovery service technology (for example, Eureka or Consul) you use, the behavior of the client differs.
With Eureka, once the application starts, the client begins to operate in the background, both registering and renewing service registrations and also periodically fetching the service registry from the server.
With Consul, once the app starts, the client registers any services (if required) and (if configured to do so) starts a health thread to keep updating the health of the service registration. The Consul client fetches no service registrations until you ask to look up a service. At that point, a request is made to the Consul server. As a result, you probably want to use the Steeltoe caching load balancer with the Consul service discovery.
Using HttpClientFactory
The recommended approach for discovering services is to use HttpClient
supplied through HttpClientFactory, augmented with the DiscoveryHttpMessageHandler
(provided by Steeltoe).
DiscoveryHttpMessageHandler
is a DelegatingHandler
that you can use, to intercept requests and to evaluate the URL to see if the host portion of the URL can be resolved from the current service registry. The handler does this for any HttpClient
created by the factory.
After initializing the discovery client, you can configure HttpClient
to include DiscoveryHttpMessageHandler
with the extension AddServiceDiscovery
:
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
...
// Configure HttpClient
services.AddHttpClient("fortunes")
.AddServiceDiscovery()
.AddTypedClient<IFortuneService, FortuneService>();
...
}
...
}
This HttpClient
can be injected into FortuneService
for a nice, clean experience:
public class FortuneService : IFortuneService
{
private const string RANDOM_FORTUNE_URL = "https://fortuneService/api/fortunes/random";
private HttpClient _client;
public FortuneService(HttpClient client)
{
_client = client;
}
public async Task<string> RandomFortuneAsync()
{
return await _client.GetStringAsync(RANDOM_FORTUNE_URL);
}
}
Check out the Microsoft documentation on HttpClientFactory
to see all the various ways you can make use of message handlers.
DiscoveryHttpMessageHandler
has an optionalILoadBalancer
parameter. If noILoadBalancer
is provided through dependency injection, aRandomLoadBalancer
is used. To change this behavior, add anILoadBalancer
to the DI container or use a load-balancer first configuration, as described within section 1.4 on this page.
DiscoveryHttpClientHandler
Another way to use the registry to look up services is to use the Steeltoe DiscoveryHttpClientHandler
with HttpClient
.
This FortuneService
class retrieves fortunes from the Fortune microservice, which is registered under a name of fortuneService
:
using Steeltoe.Discovery.Client;
...
public class FortuneService : IFortuneService
{
DiscoveryHttpClientHandler _handler;
private const string RANDOM_FORTUNE_URL = "https://fortuneService/api/fortunes/random";
public FortuneService(IDiscoveryClient client)
{
_handler = new DiscoveryHttpClientHandler(client);
}
public async Task<string> RandomFortuneAsync()
{
var client = GetClient();
return await client.GetStringAsync(RANDOM_FORTUNE_URL);
}
private HttpClient GetClient()
{
// WARNING: do NOT create a new HttpClient for every request in your code
// -- you may experience socket exhaustion if you do!
var client = new HttpClient(_handler, false);
return client;
}
}
First, notice that the FortuneService
constructor takes an IDiscoveryClient
as a parameter. This is Steeltoe's interface for finding services in the service registry. The IDiscoveryClient
implementation is registered with the service container for use in any controller, view, or service during initialization. The constructor code for this class uses the client to create an instance of Steeltoe's DiscoveryHttpClientHandler
.
Next, notice that when the RandomFortuneAsync()
method is called, the HttpClient
is created with the Steeltoe handler. The handler's role is to intercept any requests made with the HttpClient
and to evaluate the URL to see if the host portion of the URL can be resolved from the service registry. In this example, the fortuneService
name should be resolved into an actual host:port
before letting the request continue.
If the name cannot be resolved, the handler ignores the request URL and lets the request continue unchanged.
By default,
DiscoveryHttpClientHandler
performs random load balancing. That is, if there are multiple instances registered under a particular service name, the handler randomly selects one of those instances each time the handler is invoked.
Using IDiscoveryClient
In the event the handler options do not serve your needs, you can always make lookup requests directly on the IDiscoveryClient
interface.
The methods available on an IDiscoveryClient
provide access to services and service instances available in the registry:
/// <summary>
/// Gets all known service Ids
/// </summary>
IList<string> Services { get; }
/// <summary>
/// Get all ServiceInstances associated with a particular serviceId
/// </summary>
/// <param name="serviceId">the serviceId to lookup</param>
/// <returns>List of service instances</returns>
IList<IServiceInstance> GetInstances(string serviceId);