Skip to content

Instantly share code, notes, and snippets.

@SteffenBlake
Created June 26, 2024 00:16
Show Gist options
  • Save SteffenBlake/b7368b5fbd4efb2a15247f4044271782 to your computer and use it in GitHub Desktop.
Save SteffenBlake/b7368b5fbd4efb2a15247f4044271782 to your computer and use it in GitHub Desktop.
using AbstractResolver;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var staticServiceFlag = StaticConfiguredServiceFlag.Ping;
var dynamicConfig = new DynamicConfiguredServiceConfig()
{
// Default to Marco
Flag = DynamicConfiguredServiceFlag.Marco
};
var builder = Host.CreateApplicationBuilder(args);
// This represents a config that is locked in at startup
// Likely loaded in from AppSettings or a Secrets provider, etc etc
// Pros: fastest performance, easiest to code
// Cons: Changing this requires restarting the entire application
_ = staticServiceFlag switch
{
StaticConfiguredServiceFlag.Ping =>
builder.Services.AddSingleton<IStaticConfiguredService, PingService>(),
StaticConfiguredServiceFlag.Pong =>
builder.Services.AddSingleton<IStaticConfiguredService, PongService>(),
_ => throw new InvalidOperationException()
};
// This represents a config that is NOT locked in at startup, and instead can be
// Dynamically changed, however it is changed over in somewhere A, but then accessed
// later in somewhere B
// Example:
// you have exposed /api/config/your-flag to be mutated on one endpoint,
// and then you need to resolve the service over on /api/foos/your-service
// In which case the flag still has to persist in memory
builder.Services.AddSingleton(dynamicConfig);
builder.Services.AddSingleton<MarcoService>(); // Note here how we register the
builder.Services.AddSingleton<PolloService>(); // concrete types, not abstracts
builder.Services.AddTransient<IDynamicConfiguredService>(svcs =>
{
var config = svcs.GetRequiredService<DynamicConfiguredServiceConfig>();
return config.Flag switch
{
DynamicConfiguredServiceFlag.Marco => svcs.GetRequiredService<MarcoService>(),
DynamicConfiguredServiceFlag.Pollo => svcs.GetRequiredService<PolloService>(),
_ => throw new InvalidOperationException()
};
});
// Third option, Factory Pattern
// You use this approach for when the services flag is dynamically "chosen"
// in the exact same pipeline it is requested
// For example if you have you /api/do-the-thing/{strategy:FlaggedEnum}
// in which case you can just "select" the right service with a factory method
builder.Services.AddSingleton<ScopeConfiguredServiceFactory>();
builder.Services.AddSingleton<HelloService>(); // Note once again here that we register
builder.Services.AddSingleton<WorldService>(); // the concrete types, not the abstract
var app = builder.Build();
// Demonstrating resolution
// Statically configured service, there's no way to change which one we get here
// Without restarting the app to load new configs
var staticConfiguredService = app.Services.GetRequiredService<IStaticConfiguredService>();
staticConfiguredService.Run();
// Dynamically configured service
// watch how we can change the config dynamically to switch
// Which one we resolve to on the fly
var dynamicConfiguredServiceA = app.Services.GetRequiredService<IDynamicConfiguredService>();
dynamicConfiguredServiceA.Run();
// Now we change the config
dynamicConfig.Flag = DynamicConfiguredServiceFlag.Pollo;
var dynamicConfiguredServiceB = app.Services.GetRequiredService<IDynamicConfiguredService>();
dynamicConfiguredServiceB.Run();
// Factory selected service, AKA Strategy Pattern
var factory = app.Services.GetRequiredService<ScopeConfiguredServiceFactory>();
var scopeConfiguredServiceA = factory.Resolve(ScopeRequestedServiceFlag.Hello);
scopeConfiguredServiceA.Run();
var scopeConfiguredServiceB = factory.Resolve(ScopeRequestedServiceFlag.World);
scopeConfiguredServiceB.Run();
// OUTPUTS:
// PingService
// MarcoService
// PolloService
// HelloService
// WorldService
namespace AbstractResolver
{
public interface IStaticConfiguredService
{
void Run();
}
public enum StaticConfiguredServiceFlag
{
Ping,
Pong
}
public class PingService : IStaticConfiguredService
{
public void Run()
{
Console.WriteLine(nameof(PingService));
}
}
public class PongService : IStaticConfiguredService
{
public void Run()
{
Console.WriteLine(nameof(PongService));
}
}
public interface IDynamicConfiguredService
{
void Run();
}
public enum DynamicConfiguredServiceFlag
{
Marco,
Pollo
}
public class DynamicConfiguredServiceConfig
{
public required DynamicConfiguredServiceFlag Flag { get; set; }
}
public class MarcoService : IDynamicConfiguredService
{
public void Run()
{
Console.WriteLine(nameof(MarcoService));
}
}
public class PolloService : IDynamicConfiguredService
{
public void Run()
{
Console.WriteLine(nameof(PolloService));
}
}
public interface IScopeRequestedService
{
void Run();
}
public enum ScopeRequestedServiceFlag
{
Hello,
World
}
public class ScopeConfiguredServiceFactory(
HelloService helloService,
WorldService worldService
)
{
public IScopeRequestedService Resolve(ScopeRequestedServiceFlag flag)
{
return flag switch
{
ScopeRequestedServiceFlag.Hello => helloService,
ScopeRequestedServiceFlag.World => worldService,
_ => throw new InvalidOperationException()
};
}
}
public class HelloService : IScopeRequestedService
{
public void Run()
{
Console.WriteLine(nameof(HelloService));
}
}
public class WorldService : IScopeRequestedService
{
public void Run()
{
Console.WriteLine(nameof(WorldService));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment