Skip to content

Commit 190b001

Browse files
leonluc-devraman-m
andauthored
#1225 Update ServiceDiscovery documentation and samples to include Custom Providers (#1656)
* Update servicediscovery documentation to include custom provider * Update servicediscovery.rst Custom Providers paragraph * Update servicediscovery.rst Fix lower/upper case in paragraph name * Added custom service provider sample * Minor clarification to custom service discovery provider docs * Move usings to the top. Use file-scoped namespace declaration * Moved custom service discovery sample Added sample to Ocelot.sln * Added custom service provider sample * Move usings to the top. Use file-scoped namespace declaration * Moved custom service discovery sample Added sample to Ocelot.sln * Add 2 options/ways of solution development via ConfigureServices * Upgrade DownstreamService to ASP.NET 7 * Upgrade ApiGateway to ASP.NET 7 * Update README.md * Removed redundant spring section in config Move urls config from Program.cs to appsettings.json * Workaround for the Categories route * Rename controller: class name should be the same as file name * Upgrade to Web API app: multiple startup profiles, add Docker profile. Basic microservices template * Correct registration of IServiceDiscoveryProviderFactory interface * Update README.md: Fix upstream path because of case sensitivity * Update servicediscovery.rst: Update Custom Providers section. Add sample solution. * Update servicediscovery.rst: Update actual code from the sample * Remove obsolete code * CS8632 The annotation for nullable reference types should only be used in code within a '#nullable' annotations context * Revert to previous state * Revert back to the version from ThreeMammals:develop * Update servicediscovery.rst: English checking --------- Co-authored-by: raman-m <[email protected]>
1 parent fa179bf commit 190b001

27 files changed

+792
-7
lines changed

Ocelot.sln

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio Version 17
4-
VisualStudioVersion = 17.0.32112.339
4+
VisualStudioVersion = 17.6.33723.286
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{5CFB79B7-C9DC-45A4-9A75-625D92471702}"
77
EndProject
@@ -86,6 +86,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "open-tracing", "open-tracin
8686
EndProject
8787
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OcelotOpenTracing", "samples\OcelotOpenTracing\OcelotOpenTracing.csproj", "{C9427E78-4281-4F59-A66E-17C0B66550E5}"
8888
EndProject
89+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "service-discovery", "service-discovery", "{25C30AAA-12DD-4BA5-A53F-9271E54EBAB7}"
90+
EndProject
91+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.ServiceDiscovery.ApiGateway", "samples\OcelotServiceDiscovery\ApiGateway\Ocelot.Samples.ServiceDiscovery.ApiGateway.csproj", "{D37209EA-C13E-42AE-B851-A8604F1FCD0E}"
92+
EndProject
93+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ocelot.Samples.ServiceDiscovery.DownstreamService", "samples\OcelotServiceDiscovery\DownstreamService\Ocelot.Samples.ServiceDiscovery.DownstreamService.csproj", "{E2AC741A-4120-4D59-B5E4-16382ED45E8D}"
94+
EndProject
8995
Global
9096
GlobalSection(SolutionConfigurationPlatforms) = preSolution
9197
Debug|Any CPU = Debug|Any CPU
@@ -188,6 +194,14 @@ Global
188194
{C9427E78-4281-4F59-A66E-17C0B66550E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
189195
{C9427E78-4281-4F59-A66E-17C0B66550E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
190196
{C9427E78-4281-4F59-A66E-17C0B66550E5}.Release|Any CPU.Build.0 = Release|Any CPU
197+
{D37209EA-C13E-42AE-B851-A8604F1FCD0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
198+
{D37209EA-C13E-42AE-B851-A8604F1FCD0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
199+
{D37209EA-C13E-42AE-B851-A8604F1FCD0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
200+
{D37209EA-C13E-42AE-B851-A8604F1FCD0E}.Release|Any CPU.Build.0 = Release|Any CPU
201+
{E2AC741A-4120-4D59-B5E4-16382ED45E8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
202+
{E2AC741A-4120-4D59-B5E4-16382ED45E8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
203+
{E2AC741A-4120-4D59-B5E4-16382ED45E8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
204+
{E2AC741A-4120-4D59-B5E4-16382ED45E8D}.Release|Any CPU.Build.0 = Release|Any CPU
191205
EndGlobalSection
192206
GlobalSection(SolutionProperties) = preSolution
193207
HideSolutionNode = FALSE
@@ -224,6 +238,9 @@ Global
224238
{11C622AD-8C0A-4CF4-811B-3DBB76550797} = {5CFB79B7-C9DC-45A4-9A75-625D92471702}
225239
{731C6A8A-69ED-445C-A132-C638AA93F9C7} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
226240
{C9427E78-4281-4F59-A66E-17C0B66550E5} = {731C6A8A-69ED-445C-A132-C638AA93F9C7}
241+
{25C30AAA-12DD-4BA5-A53F-9271E54EBAB7} = {8FA0CBA0-0338-48EB-B37F-83CA5022237C}
242+
{D37209EA-C13E-42AE-B851-A8604F1FCD0E} = {25C30AAA-12DD-4BA5-A53F-9271E54EBAB7}
243+
{E2AC741A-4120-4D59-B5E4-16382ED45E8D} = {25C30AAA-12DD-4BA5-A53F-9271E54EBAB7}
227244
EndGlobalSection
228245
GlobalSection(ExtensibilityGlobals) = postSolution
229246
SolutionGuid = {21476EFF-778A-4F97-8A56-D1AF1CEC0C48}

docs/features/servicediscovery.rst

Lines changed: 126 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ Ocelot allows you to specify a service discovery provider and will use this to f
77
GlobalConfiguration section which means the same service discovery provider will be used for all Routes you specify a ServiceName for at Route level.
88

99
Consul
10-
^^^^^^
10+
------
1111

1212
The first thing you need to do is install the NuGet package that provides Consul support in Ocelot.
1313

14-
``Install-Package Ocelot.Provider.Consul``
14+
.. code-block:: powershell
15+
16+
Install-Package Ocelot.Provider.Consul
1517
1618
Then add the following to your ConfigureServices method.
1719

@@ -92,7 +94,7 @@ Or
9294
}
9395
9496
ACL Token
95-
---------
97+
^^^^^^^^^
9698

9799
If you are using ACL with Consul Ocelot supports adding the X-Consul-Token header. In order so this to work you must add the additional property below.
98100

@@ -108,13 +110,15 @@ If you are using ACL with Consul Ocelot supports adding the X-Consul-Token heade
108110
Ocelot will add this token to the Consul client that it uses to make requests and that is then used for every request.
109111

110112
Eureka
111-
^^^^^^
113+
------
112114

113115
This feature was requested as part of `Issue 262 <https://github.com/ThreeMammals/Ocelot/issues/262>`_ . to add support for Netflix's Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe <https://steeltoe.io/>`_ which is something to do with `Pivotal <https://pivotal.io/platform>`_! Anyway enough of the background.
114116

115117
The first thing you need to do is install the NuGet package that provides Eureka support in Ocelot.
116118

117-
``Install-Package Ocelot.Provider.Eureka``
119+
.. code-block:: powershell
120+
121+
Install-Package Ocelot.Provider.Eureka
118122
119123
Then add the following to your ConfigureServices method.
120124

@@ -150,7 +154,7 @@ Ocelot will now register all the necessary services when it starts up and if you
150154
Ocelot will use the scheme (http/https) set in Eureka if these values are not provided in ocelot.json
151155

152156
Dynamic Routing
153-
^^^^^^^^^^^^^^^
157+
---------------
154158

155159
This feature was requested in `issue 340 <https://github.com/ThreeMammals/Ocelot/issues/340>`_. The idea is to enable dynamic routing when using a service discovery provider (see that section of the docs for more info). In this mode Ocelot will use the first segment of the upstream path to lookup the downstream service with the service discovery provider.
156160

@@ -242,3 +246,119 @@ Ocelot also allows you to set DynamicRoutes which lets you set rate limiting rul
242246
This configuration means that if you have a request come into Ocelot on /product/* then dynamic routing will kick in and ocelot will use the rate limiting set against the product service in the DynamicRoutes section.
243247

244248
Please take a look through all of the docs to understand these options.
249+
250+
Custom Providers
251+
----------------------------------
252+
253+
Ocelot also allows you to create your own ServiceDiscovery implementation.
254+
This is done by implementing the ``IServiceDiscoveryProvider`` interface, as shown in the following example:
255+
256+
.. code-block:: csharp
257+
258+
public class MyServiceDiscoveryProvider : IServiceDiscoveryProvider
259+
{
260+
private readonly DownstreamRoute _downstreamRoute;
261+
262+
public MyServiceDiscoveryProvider(DownstreamRoute downstreamRoute)
263+
{
264+
_downstreamRoute = downstreamRoute;
265+
}
266+
267+
public async Task<List<Service>> Get()
268+
{
269+
var services = new List<Service>();
270+
//...
271+
//Add service(s) to the list matching the _downstreamRoute
272+
return services;
273+
}
274+
}
275+
276+
And set its class name as the provider type in **ocelot.json**:
277+
278+
.. code-block:: json
279+
280+
"GlobalConfiguration": {
281+
"ServiceDiscoveryProvider": {
282+
"Type": "MyServiceDiscoveryProvider"
283+
}
284+
}
285+
286+
Finally, in the application's **ConfigureServices** method, register a ``ServiceDiscoveryFinderDelegate`` to initialize and return the provider:
287+
288+
.. code-block:: csharp
289+
290+
ServiceDiscoveryFinderDelegate serviceDiscoveryFinder = (provider, config, route) =>
291+
{
292+
return new MyServiceDiscoveryProvider(route);
293+
};
294+
services.AddSingleton(serviceDiscoveryFinder);
295+
services.AddOcelot();
296+
297+
Custom Provider Sample
298+
^^^^^^^^^^^^^^^^^^^^^^
299+
300+
In order to introduce a basic template for a custom Service Discovery provider, we've prepared a good sample:
301+
302+
| **Link**: `samples <../../samples>`_ / `OcelotServiceDiscovery <../../samples/OcelotServiceDiscovery>`_
303+
| **Solution**: `Ocelot.Samples.ServiceDiscovery.sln <../../samples/OcelotServiceDiscovery/Ocelot.Samples.ServiceDiscovery.sln>`_
304+
305+
This solution contains the following projects:
306+
307+
- `ApiGateway <#apigateway>`_
308+
- `DownstreamService <#downstreamservice>`_
309+
310+
This solution is ready for any deployment. All services are bound, meaning all ports and hosts are prepared for immediate use (running in Visual Studio).
311+
312+
All instructions for running this solution are in `README.md <../../samples/OcelotServiceDiscovery/README.md>`_.
313+
314+
DownstreamService
315+
"""""""""""""""""
316+
317+
This project provides a single downstream service that can be reused across `ApiGateway <#apigateway>`_ routes.
318+
It has multiple **launchSettings.json** profiles for your favorite launch and hosting scenarios: Visual Studio running sessions, Kestrel console hosting, and Docker deployments.
319+
320+
ApiGateway
321+
""""""""""
322+
323+
This project includes a custom Service Discovery provider and it only has route(s) to `DownstreamService <#downstreamservice>`_ services in the **ocelot.json** file.
324+
You can add more routes!
325+
326+
The main source code for the custom provider is in the `ServiceDiscovery <../../samples/OcelotServiceDiscovery/ApiGateway/ServiceDiscovery>`_ folder:
327+
the ``MyServiceDiscoveryProvider`` and ``MyServiceDiscoveryProviderFactory`` classes. You are welcome to design and develop them!
328+
329+
Additionally, the cornerstone of this custom provider is the ``ConfigureServices`` method, where you can choose design and implementation options: simple or more complex:
330+
331+
.. code-block:: csharp
332+
333+
builder.ConfigureServices(s =>
334+
{
335+
// Perform initialization from application configuration or hardcode/choose the best option.
336+
bool easyWay = true;
337+
338+
if (easyWay)
339+
{
340+
// Design #1. Define a custom finder delegate to instantiate a custom provider under the default factory, which is ServiceDiscoveryProviderFactory
341+
s.AddSingleton<ServiceDiscoveryFinderDelegate>((serviceProvider, config, downstreamRoute)
342+
=> new MyServiceDiscoveryProvider(serviceProvider, config, downstreamRoute));
343+
}
344+
else
345+
{
346+
// Design #2. Abstract from the default factory (ServiceDiscoveryProviderFactory) and from FinderDelegate,
347+
// and create your own factory by implementing the IServiceDiscoveryProviderFactory interface.
348+
s.RemoveAll<IServiceDiscoveryProviderFactory>();
349+
s.AddSingleton<IServiceDiscoveryProviderFactory, MyServiceDiscoveryProviderFactory>();
350+
351+
// It will not be called, but it is necessary for internal validators, it is also a lifehack
352+
s.AddSingleton<ServiceDiscoveryFinderDelegate>((serviceProvider, config, downstreamRoute) => null);
353+
}
354+
355+
s.AddOcelot();
356+
});
357+
358+
The easy way, lite design means that you only design the provider class, and specify ``ServiceDiscoveryFinderDelegate`` object for default ``ServiceDiscoveryProviderFactory`` in Ocelot core.
359+
360+
A more complex design means that you design both provider and provider factory classes.
361+
After this, you need to add the ``IServiceDiscoveryProviderFactory`` interface to the DI-container, removing the default registered ``ServiceDiscoveryProviderFactory`` class.
362+
Note that in this case the Ocelot core will not use ``ServiceDiscoveryProviderFactory`` by default.
363+
Additionally, you do not need to specify ``"Type": "MyServiceDiscoveryProvider"`` in the **ServiceDiscoveryProvider** properties of the **GlobalConfiguration** settings.
364+
But you can leave this ``Type`` option for compatibility between both designs.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
**/.classpath
2+
**/.dockerignore
3+
**/.env
4+
**/.git
5+
**/.gitignore
6+
**/.project
7+
**/.settings
8+
**/.toolstarget
9+
**/.vs
10+
**/.vscode
11+
**/*.*proj.user
12+
**/*.dbmdl
13+
**/*.jfm
14+
**/azds.yaml
15+
**/bin
16+
**/charts
17+
**/docker-compose*
18+
**/Dockerfile*
19+
**/node_modules
20+
**/npm-debug.log
21+
**/obj
22+
**/secrets.dev.yaml
23+
**/values.dev.yaml
24+
LICENSE
25+
README.md
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net7.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<ProjectReference Include="..\..\..\src\Ocelot\Ocelot.csproj" />
9+
</ItemGroup>
10+
11+
</Project>
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using Microsoft.AspNetCore;
2+
using Microsoft.AspNetCore.Hosting;
3+
using Microsoft.Extensions.Configuration;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.DependencyInjection.Extensions;
6+
using Ocelot.DependencyInjection;
7+
using Ocelot.Middleware;
8+
using Ocelot.ServiceDiscovery;
9+
10+
namespace Ocelot.Samples.ServiceDiscovery.ApiGateway;
11+
12+
using ServiceDiscovery;
13+
14+
public class Program
15+
{
16+
public static void Main(string[] args)
17+
{
18+
BuildWebHost(args).Run();
19+
}
20+
21+
public static IWebHost BuildWebHost(string[] args) =>
22+
WebHost.CreateDefaultBuilder(args)
23+
.ConfigureAppConfiguration((hostingContext, config) =>
24+
{
25+
config
26+
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
27+
.AddJsonFile("appsettings.json", true, true)
28+
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true)
29+
.AddJsonFile("ocelot.json", false, false)
30+
.AddEnvironmentVariables();
31+
})
32+
.ConfigureServices(s =>
33+
{
34+
// Initialize from app configuration or hardcode/choose the best option.
35+
bool easyWay = true;
36+
37+
if (easyWay)
38+
{
39+
// Option #1. Define custom finder delegate to instantiate custom provider
40+
// by default factory which is ServiceDiscoveryProviderFactory
41+
s.AddSingleton<ServiceDiscoveryFinderDelegate>((serviceProvider, config, downstreamRoute)
42+
=> new MyServiceDiscoveryProvider(serviceProvider, config, downstreamRoute));
43+
}
44+
else
45+
{
46+
// Option #2. Abstract from default factory (ServiceDiscoveryProviderFactory) and from FinderDelegate,
47+
// and build custom factory by implementation of the IServiceDiscoveryProviderFactory interface.
48+
s.RemoveAll<IServiceDiscoveryProviderFactory>();
49+
s.AddSingleton<IServiceDiscoveryProviderFactory, MyServiceDiscoveryProviderFactory>();
50+
51+
// Will not be called, but it is required for internal validators, aka life hack
52+
s.AddSingleton<ServiceDiscoveryFinderDelegate>((serviceProvider, config, downstreamRoute)
53+
=> null);
54+
}
55+
56+
s.AddOcelot();
57+
})
58+
.Configure(a =>
59+
{
60+
a.UseOcelot().Wait();
61+
})
62+
.Build();
63+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"iisSettings": {
3+
"windowsAuthentication": false,
4+
"anonymousAuthentication": true,
5+
"iisExpress": {
6+
"applicationUrl": "http://localhost:54060/",
7+
"sslPort": 0
8+
}
9+
},
10+
"profiles": {
11+
"IIS Express": {
12+
"commandName": "IISExpress",
13+
"launchBrowser": true,
14+
"environmentVariables": {
15+
"ASPNETCORE_ENVIRONMENT": "Development"
16+
}
17+
},
18+
"ApiGateway": {
19+
"commandName": "Project",
20+
"launchBrowser": true,
21+
"launchUrl": "categories",
22+
"environmentVariables": {
23+
"ASPNETCORE_ENVIRONMENT": "Development"
24+
},
25+
"applicationUrl": "http://localhost:5000/"
26+
}
27+
}
28+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using Ocelot.Configuration;
2+
using Ocelot.ServiceDiscovery.Providers;
3+
using Ocelot.Values;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Threading.Tasks;
7+
8+
namespace Ocelot.Samples.ServiceDiscovery.ApiGateway.ServiceDiscovery;
9+
10+
public class MyServiceDiscoveryProvider : IServiceDiscoveryProvider
11+
{
12+
private readonly IServiceProvider _serviceProvider;
13+
private readonly ServiceProviderConfiguration _config;
14+
private readonly DownstreamRoute _downstreamRoute;
15+
16+
public MyServiceDiscoveryProvider(IServiceProvider serviceProvider, ServiceProviderConfiguration config, DownstreamRoute downstreamRoute)
17+
{
18+
_serviceProvider = serviceProvider;
19+
_config = config;
20+
_downstreamRoute = downstreamRoute;
21+
}
22+
23+
public Task<List<Service>> Get()
24+
{
25+
26+
// Returns a list of service(s) that match the downstream route passed to the provider
27+
var services = new List<Service>();
28+
29+
// Apply configuration checks
30+
// ... if (_config.Host)
31+
if (_downstreamRoute.ServiceName.Equals("downstream-service"))
32+
{
33+
//For this example we simply do a manual match to a single service
34+
var service = new Service(
35+
name: "downstream-service",
36+
hostAndPort: new ServiceHostAndPort("localhost", 5001),
37+
id: "downstream-service-1",
38+
version: "1.0",
39+
tags: new string[] { "downstream", "hardcoded" }
40+
);
41+
42+
services.Add(service);
43+
}
44+
45+
return Task.FromResult(services);
46+
}
47+
}

0 commit comments

Comments
 (0)