Skip to content

Commit c3d13a3

Browse files
committed
Implement Dynamic DataSource registration
Signed-off-by: Bill DeRusha <[email protected]>
1 parent b3b3189 commit c3d13a3

File tree

51 files changed

+698
-672
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+698
-672
lines changed

Diff for: .vscode/launch.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
"uriFormat": "%s/swagger"
3838
},
3939
"env": {
40-
"ASPNETCORE_ENVIRONMENT": "Development"
40+
"ASPNETCORE_ENVIRONMENT": "Development",
41+
"DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE": "false"
4142
},
4243
"sourceFileMap": {
4344
"/Views": "${workspaceFolder}/Views"

Diff for: docs/architecture/data-sources.md

+16-43
Original file line numberDiff line numberDiff line change
@@ -100,56 +100,28 @@ public class MyNewDataSource: IEmissionsDataSource
100100

101101
### Add Dependency Injection Configuration
102102

103-
The SDK uses dependency injection to load registered data sources based on set
104-
environment variables. For a data source to be registered, it need to have a
105-
Service Collection Extension defined. To do so, add a `Configuration` directory
106-
in your data source project and create a new ServiceCollectionExtensions file.
107-
We have provided a command snippet below:
108-
109-
```sh
110-
cd src/CarbonAware.DataSources/CarbonAware.DataSources.MyNewDataSource/src
111-
mkdir Configuration
112-
touch Configuration\ServiceCollectionExtensions.cs
113-
```
114-
115-
Using the skeleton below, add the data source specific configuration and
116-
implementation instances to the service collection.
103+
The SDK uses dependency injection to load registered data sources based on configuration. For a data source to be registered, it needs to have a
104+
static method `ConfigureDI<T>` defined. We have provided an example below:
117105

118106
```csharp
119-
using Microsoft.Extensions.DependencyInjection;
120-
using Microsoft.Extensions.DependencyInjection.Extensions;
121-
namespace CarbonAware.DataSources.MyNewDataSource.Configuration;
122-
public static class ServiceCollectionExtensions
107+
public static IServiceCollection ConfigureDI<T>(IServiceCollection services, DataSourcesConfiguration dataSourcesConfig)
108+
where T : IDataSource
123109
{
124-
public static void AddMyNewDataSource(this IServiceCollection services)
110+
var configSection = dataSourcesConfig.ConfigurationSection<T>();
111+
AddMyNewDataSourceClient(services, configSection);
112+
services.AddScoped<ISomeInterface, MyDataSourceDependency>();
113+
try
114+
{
115+
services.TryAddSingleton(typeof(T), typeof(MyNewDataSource));
116+
} catch (Exception ex)
125117
{
126-
// ... register your data source with the IServiceCollection instance
118+
throw new ArgumentException($"MyNewDataSource is not a supported {typeof(T).Name} data source.", ex);
127119
}
120+
return services;
128121
}
129122
```
130123

131-
### Register the New Data Source
132-
133-
Once the data source's ServiceCollectionExtensions is configured, it can be
134-
registered as an available data source for the SDK by adding to the switch
135-
statement found in the AddDataSourceService function of
136-
[this file](../../src/CarbonAware.DataSources/CarbonAware.DataSources.Registration\Configuration\ServiceCollectionExtensions.cs).
137-
Note you will need to add a new enum type to the `DataSourceType`
138-
[enum file](../../src/CarbonAware.DataSources/CarbonAware.DataSources.Registration/Configuration/DataSourceType.cs)
139-
to reference in the switch statement.
140-
141-
```csharp
142-
switch (dataSourceType)
143-
{
144-
...
145-
case DataSourceType.MyNewDataSourceEnum:
146-
{
147-
services.AddMyNewDataSource();
148-
break;
149-
}
150-
...
151-
}
152-
```
124+
This function will be called at runtime to configure your data source like `MyNewDataSource.ConfigureDI<IEmissionsDataSource>(services, config);`. For more examples, check out the [implementations of the existing data sources](/src/CarbonAware.DataSources/).
153125

154126
### Adding Tests
155127

@@ -180,7 +152,8 @@ setting:
180152

181153
```bash
182154
DataSources__EmissionsDataSource="MyNewDataSource"
183-
DataSources__Configurations__MyNewDataSource__Proxy__UseProxy=true
155+
DataSources__Configurations__MyNewDataSource__Username="MyNewDataSourceUser123"
156+
DataSources__Configurations__MyNewDataSource__Password="MyNewDataSourceP@ssword!"
184157
```
185158

186159
Both the WebAPI and the CLI read the env variables in so once set, you can spin

Diff for: docs/architecture/overview.md

+8-13
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,14 @@ result.
8484

8585
See the [data source README](./data-sources.md) for more detailed information.
8686

87-
## Dependency Registration
88-
89-
The SDK uses dependency injection to load the data sources based on set
90-
environment variables. To register a new dependency, a new
91-
ServiceCollectionExtension method must be defined. These dependencies are loaded
92-
in a hierarchical structure such that:
93-
94-
1. Each data source defines a `ServiceCollectionExtension` method.
95-
2. All available data sources are registered in the `DataSource.Registration`
96-
project.
97-
3. The GSF library defines a `ServiceCollectionExtension` method where it
98-
registers the data sources for the handlers to use.
99-
4. The `Program.cs` file registers the GSF library classes at startup
87+
### Dependency Registration
88+
89+
The SDK uses dependency injection to load the data sources based on configuration. To register a new dependency, the data source musr define a static method `ConfigureDI<T>`. These dependencies are then loaded in the following manner:
90+
91+
1. Each data source defines a `ConfigureDI<T>` method.
92+
2. The GSF library defines a `ServiceCollectionExtension` method where it
93+
uses the configuration settings to dynamically load and configure the user-specified data sources for the handlers to use.
94+
3. The `Program.cs` file registers the GSF library classes at startup
10095

10196
## Example Call Flow
10297

Diff for: docs/packaging.md

+4-5
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,17 @@ showing how the package can be consumed.
2020

2121
## Included Projects
2222

23-
The current package include 8 projects from the SDK:
23+
The current package include 7 projects from the SDK:
2424

2525
1. "GSF.CarbonAware"
2626
2. "CarbonAware"
2727
3. "CarbonAware.DataSources.ElectricityMapsFree"
2828
4. "CarbonAware.DataSources.ElectricityMaps"
2929
5. "CarbonAware.DataSources.Json"
30-
6. "CarbonAware.DataSources.Registration"
31-
7. "CarbonAware.DataSources.WattTime"
32-
8. "CarbonAware.LocationSources"
30+
6. "CarbonAware.DataSources.WattTime"
31+
7. "CarbonAware.LocationSources"
3332

34-
These 8 projects enable users of the library to consume the current endpoints
33+
These 7 projects enable users of the library to consume the current endpoints
3534
exposed by the library. The package that needs to be added to a new C# project
3635
is `GSF.CarbonAware`.
3736

Diff for: src/CarbonAware.CLI/test/integrationTests/CarbonAware.CLI.IntegrationTests.csproj

-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
<ItemGroup>
1616
<ProjectReference
1717
Include="..\..\..\CarbonAware.DataSources\CarbonAware.DataSources.Json\mock\CarbonAware.DataSources.Json.Mocks.csproj" />
18-
<ProjectReference
19-
Include="..\..\..\CarbonAware.DataSources\CarbonAware.DataSources.Registration\CarbonAware.DataSources.Registration.csproj" />
2018
<ProjectReference
2119
Include="..\..\..\CarbonAware.DataSources\CarbonAware.DataSources.WattTime\mock\CarbonAware.DataSources.WattTime.Mocks.csproj" />
2220
<ProjectReference

Diff for: src/CarbonAware.CLI/test/integrationTests/Commands/Emissions/EmissionsCommandTests.cs

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using CarbonAware.DataSources.Configuration;
21
using NUnit.Framework;
32
using System.Text.Json.Nodes;
43
using System.Text.RegularExpressions;

Diff for: src/CarbonAware.CLI/test/integrationTests/Commands/EmissionsForecasts/EmissionsForecastsCommandTests.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using CarbonAware.DataSources.Configuration;
2-
using NUnit.Framework;
1+
using NUnit.Framework;
32
using System.Text.Json.Nodes;
43

54
namespace CarbonAware.CLI.IntegrationTests.Commands.EmissionsForecasts;

Diff for: src/CarbonAware.CLI/test/integrationTests/Commands/Location/LocationCommandTests.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using CarbonAware.DataSources.Configuration;
2-
using NUnit.Framework;
1+
using NUnit.Framework;
32
using System.Text.Json;
43

54
namespace CarbonAware.CLI.IntegrationTests.Commands.Location;

Diff for: src/CarbonAware.CLI/test/integrationTests/IntegrationTestingBase.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using CarbonAware.DataSources.Configuration;
2-
using CarbonAware.Interfaces;
1+
using CarbonAware.Interfaces;
32
using CarbonAware.DataSources.ElectricityMaps.Mocks;
43
using CarbonAware.DataSources.ElectricityMapsFree.Mocks;
54
using CarbonAware.DataSources.Json.Mocks;
@@ -11,6 +10,15 @@
1110

1211
namespace CarbonAware.CLI.IntegrationTests;
1312

13+
public enum DataSourceType
14+
{
15+
None,
16+
WattTime,
17+
JSON,
18+
ElectricityMaps,
19+
ElectricityMapsFree,
20+
}
21+
1422
/// <summary>
1523
/// A base class that does all the common setup for the Integration Testing
1624
/// Overrides WebAPI factory by switching out different configurations via _datasource

Diff for: src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/src/CarbonAware.DataSources.ElectricityMaps.csproj

-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
<ItemGroup>
1111
<InternalsVisibleTo Include="CarbonAware.DataSources.ElectricityMaps.Mocks" />
1212
<InternalsVisibleTo Include="CarbonAware.DataSources.ElectricityMaps.Tests" />
13-
<InternalsVisibleTo Include="CarbonAware.DataSources.Registration" />
1413
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
1514
</ItemGroup>
1615

Diff for: src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/src/Configuration/ServiceCollectionExtensions.cs

-56
This file was deleted.

Diff for: src/CarbonAware.DataSources/CarbonAware.DataSources.ElectricityMaps/src/ElectricityMapsDataSource.cs

+50-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
using CarbonAware.Configuration;
12
using CarbonAware.DataSources.ElectricityMaps.Client;
3+
using CarbonAware.DataSources.ElectricityMaps.Configuration;
24
using CarbonAware.DataSources.ElectricityMaps.Model;
35
using CarbonAware.Exceptions;
46
using CarbonAware.Interfaces;
57
using CarbonAware.Model;
8+
using Microsoft.Extensions.Configuration;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Microsoft.Extensions.DependencyInjection.Extensions;
611
using Microsoft.Extensions.Logging;
7-
using System.Diagnostics;
12+
using System.Net;
813

914
namespace CarbonAware.DataSources.ElectricityMaps;
1015

@@ -40,6 +45,21 @@ public ElectricityMapsDataSource(ILogger<ElectricityMapsDataSource> logger, IEle
4045
this._locationSource = locationSource;
4146
}
4247

48+
public static IServiceCollection ConfigureDI<T>(IServiceCollection services, DataSourcesConfiguration dataSourcesConfig)
49+
where T : IDataSource
50+
{
51+
var configSection = dataSourcesConfig.ConfigurationSection<T>();
52+
AddElectricityMapsClient(services, configSection);
53+
try
54+
{
55+
services.TryAddSingleton(typeof(T), typeof(ElectricityMapsDataSource));
56+
} catch (Exception ex)
57+
{
58+
throw new ArgumentException($"ElectricityMapsDataSource is not a supported {typeof(T).Name} data source.", ex);
59+
}
60+
return services;
61+
}
62+
4363
/// <inheritdoc />
4464
public async Task<EmissionsForecast> GetCurrentCarbonIntensityForecastAsync(Location location)
4565
{
@@ -176,4 +196,33 @@ private TimeSpan GetDurationFromHistoryDataPoints(IEnumerable<CarbonIntensity> d
176196
// the absolute value of the TimeSpan between the two points.
177197
return first.DateTime.Subtract(second.DateTime).Duration();
178198
}
199+
200+
private static void AddElectricityMapsClient(IServiceCollection services, IConfigurationSection configSection)
201+
{
202+
services.Configure<ElectricityMapsClientConfiguration>(c =>
203+
{
204+
configSection.Bind(c);
205+
});
206+
207+
var httpClientBuilder = services.AddHttpClient<ElectricityMapsClient>(IElectricityMapsClient.NamedClient);
208+
209+
var Proxy = configSection.GetSection("Proxy").Get<WebProxyConfiguration>();
210+
if (Proxy?.UseProxy == true)
211+
{
212+
if (String.IsNullOrEmpty(Proxy.Url))
213+
{
214+
throw new ConfigurationException("Proxy Url is not configured.");
215+
}
216+
httpClientBuilder.ConfigurePrimaryHttpMessageHandler(() =>
217+
new HttpClientHandler() {
218+
Proxy = new WebProxy {
219+
Address = new Uri(Proxy.Url),
220+
Credentials = new NetworkCredential(Proxy.Username, Proxy.Password),
221+
BypassProxyOnLocal = true
222+
}
223+
}
224+
);
225+
}
226+
services.TryAddSingleton<IElectricityMapsClient, ElectricityMapsClient>();
227+
}
179228
}

0 commit comments

Comments
 (0)