Skip to content

Commit

Permalink
Implement Dynamic DataSource registration
Browse files Browse the repository at this point in the history
Signed-off-by: Bill DeRusha <[email protected]>
  • Loading branch information
bderusha committed Jun 30, 2023
1 parent b3b3189 commit c3d13a3
Show file tree
Hide file tree
Showing 51 changed files with 698 additions and 672 deletions.
3 changes: 2 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"uriFormat": "%s/swagger"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_HOSTBUILDER__RELOADCONFIGONCHANGE": "false"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
Expand Down
59 changes: 16 additions & 43 deletions docs/architecture/data-sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,56 +100,28 @@ public class MyNewDataSource: IEmissionsDataSource

### Add Dependency Injection Configuration

The SDK uses dependency injection to load registered data sources based on set
environment variables. For a data source to be registered, it need to have a
Service Collection Extension defined. To do so, add a `Configuration` directory
in your data source project and create a new ServiceCollectionExtensions file.
We have provided a command snippet below:

```sh
cd src/CarbonAware.DataSources/CarbonAware.DataSources.MyNewDataSource/src
mkdir Configuration
touch Configuration\ServiceCollectionExtensions.cs
```

Using the skeleton below, add the data source specific configuration and
implementation instances to the service collection.
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
static method `ConfigureDI<T>` defined. We have provided an example below:

```csharp
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace CarbonAware.DataSources.MyNewDataSource.Configuration;
public static class ServiceCollectionExtensions
public static IServiceCollection ConfigureDI<T>(IServiceCollection services, DataSourcesConfiguration dataSourcesConfig)
where T : IDataSource
{
public static void AddMyNewDataSource(this IServiceCollection services)
var configSection = dataSourcesConfig.ConfigurationSection<T>();
AddMyNewDataSourceClient(services, configSection);
services.AddScoped<ISomeInterface, MyDataSourceDependency>();
try
{
services.TryAddSingleton(typeof(T), typeof(MyNewDataSource));
} catch (Exception ex)
{
// ... register your data source with the IServiceCollection instance
throw new ArgumentException($"MyNewDataSource is not a supported {typeof(T).Name} data source.", ex);
}
return services;
}
```

### Register the New Data Source

Once the data source's ServiceCollectionExtensions is configured, it can be
registered as an available data source for the SDK by adding to the switch
statement found in the AddDataSourceService function of
[this file](../../src/CarbonAware.DataSources/CarbonAware.DataSources.Registration\Configuration\ServiceCollectionExtensions.cs).
Note you will need to add a new enum type to the `DataSourceType`
[enum file](../../src/CarbonAware.DataSources/CarbonAware.DataSources.Registration/Configuration/DataSourceType.cs)
to reference in the switch statement.

```csharp
switch (dataSourceType)
{
...
case DataSourceType.MyNewDataSourceEnum:
{
services.AddMyNewDataSource();
break;
}
...
}
```
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/).

### Adding Tests

Expand Down Expand Up @@ -180,7 +152,8 @@ setting:

```bash
DataSources__EmissionsDataSource="MyNewDataSource"
DataSources__Configurations__MyNewDataSource__Proxy__UseProxy=true
DataSources__Configurations__MyNewDataSource__Username="MyNewDataSourceUser123"
DataSources__Configurations__MyNewDataSource__Password="MyNewDataSourceP@ssword!"
```

Both the WebAPI and the CLI read the env variables in so once set, you can spin
Expand Down
21 changes: 8 additions & 13 deletions docs/architecture/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,14 @@ result.

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

## Dependency Registration

The SDK uses dependency injection to load the data sources based on set
environment variables. To register a new dependency, a new
ServiceCollectionExtension method must be defined. These dependencies are loaded
in a hierarchical structure such that:

1. Each data source defines a `ServiceCollectionExtension` method.
2. All available data sources are registered in the `DataSource.Registration`
project.
3. The GSF library defines a `ServiceCollectionExtension` method where it
registers the data sources for the handlers to use.
4. The `Program.cs` file registers the GSF library classes at startup
### Dependency Registration

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:

1. Each data source defines a `ConfigureDI<T>` method.
2. The GSF library defines a `ServiceCollectionExtension` method where it
uses the configuration settings to dynamically load and configure the user-specified data sources for the handlers to use.
3. The `Program.cs` file registers the GSF library classes at startup

## Example Call Flow

Expand Down
9 changes: 4 additions & 5 deletions docs/packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,17 @@ showing how the package can be consumed.

## Included Projects

The current package include 8 projects from the SDK:
The current package include 7 projects from the SDK:

1. "GSF.CarbonAware"
2. "CarbonAware"
3. "CarbonAware.DataSources.ElectricityMapsFree"
4. "CarbonAware.DataSources.ElectricityMaps"
5. "CarbonAware.DataSources.Json"
6. "CarbonAware.DataSources.Registration"
7. "CarbonAware.DataSources.WattTime"
8. "CarbonAware.LocationSources"
6. "CarbonAware.DataSources.WattTime"
7. "CarbonAware.LocationSources"

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
<ItemGroup>
<ProjectReference
Include="..\..\..\CarbonAware.DataSources\CarbonAware.DataSources.Json\mock\CarbonAware.DataSources.Json.Mocks.csproj" />
<ProjectReference
Include="..\..\..\CarbonAware.DataSources\CarbonAware.DataSources.Registration\CarbonAware.DataSources.Registration.csproj" />
<ProjectReference
Include="..\..\..\CarbonAware.DataSources\CarbonAware.DataSources.WattTime\mock\CarbonAware.DataSources.WattTime.Mocks.csproj" />
<ProjectReference
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using CarbonAware.DataSources.Configuration;
using NUnit.Framework;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using CarbonAware.DataSources.Configuration;
using NUnit.Framework;
using NUnit.Framework;
using System.Text.Json.Nodes;

namespace CarbonAware.CLI.IntegrationTests.Commands.EmissionsForecasts;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using CarbonAware.DataSources.Configuration;
using NUnit.Framework;
using NUnit.Framework;
using System.Text.Json;

namespace CarbonAware.CLI.IntegrationTests.Commands.Location;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using CarbonAware.DataSources.Configuration;
using CarbonAware.Interfaces;
using CarbonAware.Interfaces;
using CarbonAware.DataSources.ElectricityMaps.Mocks;
using CarbonAware.DataSources.ElectricityMapsFree.Mocks;
using CarbonAware.DataSources.Json.Mocks;
Expand All @@ -11,6 +10,15 @@

namespace CarbonAware.CLI.IntegrationTests;

public enum DataSourceType
{
None,
WattTime,
JSON,
ElectricityMaps,
ElectricityMapsFree,
}

/// <summary>
/// A base class that does all the common setup for the Integration Testing
/// Overrides WebAPI factory by switching out different configurations via _datasource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
<ItemGroup>
<InternalsVisibleTo Include="CarbonAware.DataSources.ElectricityMaps.Mocks" />
<InternalsVisibleTo Include="CarbonAware.DataSources.ElectricityMaps.Tests" />
<InternalsVisibleTo Include="CarbonAware.DataSources.Registration" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
</ItemGroup>

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
using CarbonAware.Configuration;
using CarbonAware.DataSources.ElectricityMaps.Client;
using CarbonAware.DataSources.ElectricityMaps.Configuration;
using CarbonAware.DataSources.ElectricityMaps.Model;
using CarbonAware.Exceptions;
using CarbonAware.Interfaces;
using CarbonAware.Model;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using System.Net;

namespace CarbonAware.DataSources.ElectricityMaps;

Expand Down Expand Up @@ -40,6 +45,21 @@ public ElectricityMapsDataSource(ILogger<ElectricityMapsDataSource> logger, IEle
this._locationSource = locationSource;
}

public static IServiceCollection ConfigureDI<T>(IServiceCollection services, DataSourcesConfiguration dataSourcesConfig)
where T : IDataSource
{
var configSection = dataSourcesConfig.ConfigurationSection<T>();
AddElectricityMapsClient(services, configSection);
try
{
services.TryAddSingleton(typeof(T), typeof(ElectricityMapsDataSource));
} catch (Exception ex)
{
throw new ArgumentException($"ElectricityMapsDataSource is not a supported {typeof(T).Name} data source.", ex);
}
return services;
}

/// <inheritdoc />
public async Task<EmissionsForecast> GetCurrentCarbonIntensityForecastAsync(Location location)
{
Expand Down Expand Up @@ -176,4 +196,33 @@ private TimeSpan GetDurationFromHistoryDataPoints(IEnumerable<CarbonIntensity> d
// the absolute value of the TimeSpan between the two points.
return first.DateTime.Subtract(second.DateTime).Duration();
}

private static void AddElectricityMapsClient(IServiceCollection services, IConfigurationSection configSection)
{
services.Configure<ElectricityMapsClientConfiguration>(c =>
{
configSection.Bind(c);
});

var httpClientBuilder = services.AddHttpClient<ElectricityMapsClient>(IElectricityMapsClient.NamedClient);

var Proxy = configSection.GetSection("Proxy").Get<WebProxyConfiguration>();
if (Proxy?.UseProxy == true)
{
if (String.IsNullOrEmpty(Proxy.Url))
{
throw new ConfigurationException("Proxy Url is not configured.");
}
httpClientBuilder.ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler() {
Proxy = new WebProxy {
Address = new Uri(Proxy.Url),
Credentials = new NetworkCredential(Proxy.Username, Proxy.Password),
BypassProxyOnLocal = true
}
}
);
}
services.TryAddSingleton<IElectricityMapsClient, ElectricityMapsClient>();
}
}
Loading

0 comments on commit c3d13a3

Please sign in to comment.