Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce OpenFeature.Extensions.Hosting package #264

Open
austindrenski opened this issue Jan 8, 2024 · 8 comments · May be fixed by #310
Open

Introduce OpenFeature.Extensions.Hosting package #264

austindrenski opened this issue Jan 8, 2024 · 8 comments · May be fixed by #310
Assignees
Labels
enhancement New feature or request

Comments

@austindrenski
Copy link
Member

austindrenski commented Jan 8, 2024

I'm eventually planning to offer this upstream as part of a new OpenFeature.Extensions.Hosting package à la OpenTelemetry.Extensions.Hosting, but still working through the realistic use cases and general ergonomics, but just mentioning that so this doesn't seem entirely irrelevant to the project at hand.

This has been suggested before, and I think it's a good idea. I would welcome such a PR, or at least an issue proposing it.

I'll open a new issue to track this, and will see about getting a draft PR put together over the next couple of weeks.

Originally posted by @austindrenski in open-feature/dotnet-sdk-contrib#125 (comment)

austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
Ran across this while working on open-feature/dotnet-sdk-contrib#127,
but tldr; libraries don't need the implementation package, so reduce
to the abstractions package to remove transitive dependencies on:

- Microsoft.Extensions.DependencyInjection
- Microsoft.Extensions.Options
- (and a few others depending on the TFM)

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
Ran across this while working on open-feature/dotnet-sdk-contrib#127,
but tldr; libraries don't need the implementation package, so reduce
to the abstractions package to remove transitive dependencies on:

- Microsoft.Extensions.DependencyInjection
- Microsoft.Extensions.Options
- (and a few others depending on the TFM)

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
Ran across this while working on open-feature/dotnet-sdk-contrib#127,
and tldr; if there's more than one project under `src/`, having the
`<PackageId>` hard-coded in `build/Common.prod.props` causes MSBuild
to panic.

There are two options to fix this:

1. ```diff
   - <PackageId>OpenFeature</PackageId>
   + <PackageId>$(MSBuildProjectName)</PackageId>
   ```
2. ```diff
   - <PackageId>OpenFeature</PackageId>
   ```

Since NuGet defaults `PackageId` to the assembly name, I'm opting
for (2), but if I've overlooked some nuance/custom build steps that
require setting `PackageId` explicitly, then we could just as well
fallback to (1).

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
Ran across this while working on open-feature/dotnet-sdk-contrib#127,
but tldr; libraries don't need the implementation package, so reduce
to the abstractions package to remove transitive dependencies on:

- Microsoft.Extensions.DependencyInjection
- Microsoft.Extensions.Options
- (and a few others depending on the TFM)

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
Ran across this while working on open-feature/dotnet-sdk-contrib#127,
and tldr; if there's more than one project under `src/`, having the
`<PackageId>` hard-coded in `build/Common.prod.props` causes MSBuild
to panic.

There are two options to fix this:

1. ```diff
   - <PackageId>OpenFeature</PackageId>
   + <PackageId>$(MSBuildProjectName)</PackageId>
   ```
2. ```diff
   - <PackageId>OpenFeature</PackageId>
   ```

Since NuGet defaults `PackageId` to the assembly name, I'm opting
for (2), but if I've overlooked some nuance/custom build steps that
require setting `PackageId` explicitly, then we could just as well
fallback to (1).

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
Ran across this while working on open-feature/dotnet-sdk-contrib#127,
and tldr; if there's more than one project under `src/`, having the
`<PackageId>` hard-coded in `build/Common.prod.props` causes MSBuild
to panic.

There are two options to fix this:

1. ```diff
   - <PackageId>OpenFeature</PackageId>
   + <PackageId>$(MSBuildProjectName)</PackageId>
   ```
2. ```diff
   - <PackageId>OpenFeature</PackageId>
   ```

Since NuGet defaults `PackageId` to the assembly name, I'm opting
for (2), but if I've overlooked some nuance/custom build steps that
require setting `PackageId` explicitly, then we could just as well
fallback to (1).

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
Ran across this while working on open-feature/dotnet-sdk-contrib#127,
and tldr; if there's more than one project under `src/`, having the
`<PackageId>` hard-coded in `build/Common.prod.props` causes MSBuild
to panic.

There are two options to fix this:

1. ```diff
   - <PackageId>OpenFeature</PackageId>
   + <PackageId>$(MSBuildProjectName)</PackageId>
   ```
2. ```diff
   - <PackageId>OpenFeature</PackageId>
   ```

Since NuGet defaults `PackageId` to the assembly name, I'm opting
for (2), but if I've overlooked some nuance/custom build steps that
require setting `PackageId` explicitly, then we could just as well
fallback to (1).

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 16, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 17, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 17, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 17, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 17, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 19, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 19, 2024
Ran across this while working on open-feature/dotnet-sdk-contrib#127,
and tldr; if there's more than one project under `src/`, having the
`<PackageId>` hard-coded in `build/Common.prod.props` causes MSBuild
to panic.

There are two options to fix this:

1. ```diff
   - <PackageId>OpenFeature</PackageId>
   + <PackageId>$(MSBuildProjectName)</PackageId>
   ```
2. ```diff
   - <PackageId>OpenFeature</PackageId>
   ```

Since NuGet defaults `PackageId` to the assembly name, I'm opting
for (2), but if I've overlooked some nuance/custom build steps that
require setting `PackageId` explicitly, then we could just as well
fallback to (1).

Signed-off-by: Austin Drenski <[email protected]>
toddbaert referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 19, 2024
Ran across this while working on open-feature/dotnet-sdk-contrib#127,
and tldr; if there's more than one project under `src/`, having the
`<PackageId>` hard-coded in `build/Common.prod.props` causes MSBuild
to panic.

There are two options to fix this:

1. ```diff
   - <PackageId>OpenFeature</PackageId>
   + <PackageId>$(MSBuildProjectName)</PackageId>
   ```
2. ```diff
   - <PackageId>OpenFeature</PackageId>
   ```

Since NuGet defaults `PackageId` to the assembly name, I'm opting
for (2), but if I've overlooked some nuance/custom build steps that
require setting `PackageId` explicitly, then we could just as well
fallback to (1).

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 20, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 22, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 23, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 23, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 23, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 23, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 23, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 27, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 27, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 27, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
austindrenski referenced this issue in austindrenski/open-feature-dotnet-sdk Jan 29, 2024
See: open-feature/dotnet-sdk-contrib#127

Signed-off-by: Austin Drenski <[email protected]>
@askpt askpt transferred this issue from open-feature/dotnet-sdk-contrib Apr 24, 2024
@askpt askpt added the enhancement New feature or request label Apr 24, 2024
@askpt askpt added this to the 2.0 (breaking) milestone Apr 24, 2024
@askpt askpt self-assigned this Apr 24, 2024
@kylejuliandev
Copy link

kylejuliandev commented Sep 23, 2024

Hey guys,

I noticed this was added to the 2.0 release although I don't see it in the changelog. I suspect this was scoped out of 2.0. The abstractions provided would be useful!

Do you have plans to include it in a future release? If so, is there anything I can do to help?

Thanks!

@beeme1mr
Copy link
Member

Hey @kylejuliandev, @askpt is working on a draft PR. Feel free to review the PR and provide feedback.

@arttonoyan
Copy link

Hello everyone,

At ServiceTitan, we're in the process of integrating OpenFeature with LaunchDarkly, and after reviewing this PR, I made some modifications to fit our needs due to time constraints. I created a new package, which we're currently using internally.

During this process, I rewrote certain sections and noticed that the dependency on "Hosting" seemed unnecessary. I've since separated the project into two packages: DependencyInjection and Hosting, and introduced the following interface in the DependencyInjection package:

/// <summary>
/// Defines the contract for managing the lifecycle of a feature API.
/// </summary>
public interface IFeatureLifecycleManager
{
    /// <summary>
    /// Ensures the feature provider is properly initialized and ready for use.
/// </summary>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A Task for the asynchronous initialization operation.</returns>
ValueTask EnsureInitializedAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Gracefully shuts down the feature API.
/// </summary>
/// <param name="cancellationToken">Cancellation token for the operation.</param>
/// <returns>A Task for the asynchronous shutdown operation.</returns>
ValueTask ShutdownAsync(CancellationToken cancellationToken = default);
}

I’d love to share the full code and gather your feedback. If it aligns with the direction of OpenFeature, we can explore how to integrate it directly.

@toddbaert
Copy link
Member

Thanks @arttonoyan !

Frankly, most of my experience in .NET dependency injection is based on older libraries, namely the Unity container. I'm not yet up-to-date on Microsoft.Extensions.Hosting OR Microsoft.Extensions.DependencyInjection. I'm trying to get up to speed as fast as possible on these, but @askpt, @kinyoklion and @lukas-reining might be more familiar than I am with them at the moment.

At the end of the day, I think we want:

  • ability to configure providers in a manner that's idiomatic to these packages (setting a provider for a particular domain, shutdown, etc)
  • ability to inject/locate a client for the domain in question
  • ability to inject a flag value in a request-scoped manner
  • convenience methods like the ones you've mentioned above
  • probably more

I'm not sure if it's necessary to use abstractions from both of these packages to accomplish the above - we may only need the DI package.
I think I speak for the other maintainers when we're open to suggestions here.

@arttonoyan
Copy link

Thank you, @toddbaert, for your response.

It would certainly be ideal to have Dependency Injection (DI) directly implemented in the OpenFeature package. However, since that's not available at the moment, I believe it's important not to modify the core code just yet. For now, having a separate DI package makes integration easier and quicker without tightly coupling it to the main codebase. I've already implemented this approach with a separate package, though the specific name isn't important - it could be something like OpenFeature.DependencyInjection or OpenFeature.Extensions.DependencyInjection or somthing else.

A good reason for not merging the DI and Hosting packages is that Dependency Injection (DI) focuses on providing a mechanism to resolve services during application setup, which can be used independently of runtime environments. Moreover, DI and hosting are fundamentally different concerns. In many cases, teams might want to manage services separately, or there might be different hosting versions in use. In such cases, it would be better to have a separate package for DI, without forcing teams to adopt OpenFeature.Extensions.Hosting, which might be unnecessary. Both serve distinct purposes and should remain separate.

Also, in this case, we can add settings to configure the AddHostedFeatureLifecycle extension to run, for example, at "Starting," "Start," or "Started." Additionally, DI could be merged into the OpenFeature core source code in the future, and in that case, hosting would not be necessary - it should remain a separate package.

Anyway, this is just my opinion, and there may be better arguments for why Hosting should be included.

Currently, we are using the following packages, and many teams have already adopted this setup successfully:

dotnet add package ServiceTitan.Standalone.Platform.OpenFeature
dotnet add package ServiceTitan.Standalone.Platform.OpenFeature.Hosting
dotnet add package ServiceTitan.Standalone.Platform.OpenFeature.LaunchDarkly

Here is a simple Setup:

builder.Services.AddOpenFeature(featureBuilder => {
    featureBuilder
        .AddHostedFeatureLifecycle() // From Hosting package
        .AddContext((context, serviceProvider) => {
            // Context settings are applied here. Each feature flag evaluation will
            // automatically have access to these context parameters.
            // Do something with the service provider.
            context
                .Set("kind", "tenant")
                .Set("key", "<some key>")
        })
        .AddLaunchDarkly(builder.Configuration["LaunchDarkly:SdkKey"], cfg => cfg.StartWaitTime(TimeSpan.FromSeconds(10)));
});

This approach has worked well for us, and I believe it provides flexibility for teams that may want to keep DI and hosting concerns separate.

@askpt
Copy link
Member

askpt commented Oct 8, 2024

Thank you, @toddbaert, for your response.

It would certainly be ideal to have Dependency Injection (DI) directly implemented in the OpenFeature package. However, since that's not available at the moment, I believe it's important not to modify the core code just yet. For now, having a separate DI package makes integration easier and quicker without tightly coupling it to the main codebase. I've already implemented this approach with a separate package, though the specific name isn't important - it could be something like OpenFeature.DependencyInjection or OpenFeature.Extensions.DependencyInjection or somthing else.

A good reason for not merging the DI and Hosting packages is that Dependency Injection (DI) focuses on providing a mechanism to resolve services during application setup, which can be used independently of runtime environments. Moreover, DI and hosting are fundamentally different concerns. In many cases, teams might want to manage services separately, or there might be different hosting versions in use. In such cases, it would be better to have a separate package for DI, without forcing teams to adopt OpenFeature.Extensions.Hosting, which might be unnecessary. Both serve distinct purposes and should remain separate.

Also, in this case, we can add settings to configure the AddHostedFeatureLifecycle extension to run, for example, at "Starting," "Start," or "Started." Additionally, DI could be merged into the OpenFeature core source code in the future, and in that case, hosting would not be necessary - it should remain a separate package.

Anyway, this is just my opinion, and there may be better arguments for why Hosting should be included.

Currently, we are using the following packages, and many teams have already adopted this setup successfully:

dotnet add package ServiceTitan.Standalone.Platform.OpenFeature
dotnet add package ServiceTitan.Standalone.Platform.OpenFeature.Hosting
dotnet add package ServiceTitan.Standalone.Platform.OpenFeature.LaunchDarkly

Here is a simple Setup:

builder.Services.AddOpenFeature(featureBuilder => {
featureBuilder
.AddHostedFeatureLifecycle() // From Hosting package
.AddContext((context, serviceProvider) => {
// Context settings are applied here. Each feature flag evaluation will
// automatically have access to these context parameters.
// Do something with the service provider.
context
.Set("kind", "tenant")
.Set("key", "")
})
.AddLaunchDarkly(builder.Configuration["LaunchDarkly:SdkKey"], cfg => cfg.StartWaitTime(TimeSpan.FromSeconds(10)));
});
This approach has worked well for us, and I believe it provides flexibility for teams that may want to keep DI and hosting concerns separate.

@arttonoyan, I would be more than happy if you could add a PR for the DI implementation. At the moment, I am fighting the hosting one, but I think we can benefit more if you have a cleaner and ready approach. We could consider expanding your implementation later to all OpenFeature "configuration" requirements like multi-provider and hook/context per provider. At this point, having just a simple implementation and gradually adding more configuration settings is more beneficial.

I understand your points around hosting versus DI, and I fully agree with them.

@arttonoyan
Copy link

Thanks, @askpt! I'll create the PR within the next two days. This is our first simple version, and it currently covers our needs. However, I believe that in the near future, we will require a multi-provider version to connect to different projects in LaunchDarkly (as each project has its own SDK key). We will also need to use event handling.
I also want to mention that you've done a lot of great work, which has been very helpful for me to get started-I’ve copied much of your logic.

@arttonoyan
Copy link

Team! @askpt @toddbaert @kinyoklion @lukas-reining
I’ve created the #PR-310 that we previously discussed regarding the OpenFeature Dependency Injection and Hosting support. Could you kindly review it and share your feedback on the approach? I’m eager to hear your thoughts and suggestions to ensure we’re aligned before moving forward.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment