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

Feature Request: Early Access to Parsed Command-Line Options for Service Configuration #117

Open
SebaVDP opened this issue Jan 9, 2024 · 1 comment

Comments

@SebaVDP
Copy link

SebaVDP commented Jan 9, 2024

Problem Statement:

In the current Cocona framework, command-line options are accessible primarily within command methods.
This design limits scenarios where global command-line options need to influence the application's setup and service configuration before any command execution.

Proposed Feature:

I propose a feature that allows global command-line options to be parsed and made available during the ServiceCollection initialization phase.
This would enable the use of command-line options for configuring services and other setup processes that occur before command execution.

Use Case Example:

A practical example is configuring a DatabaseFactory class that requires a connection string to create a DBConnection.
The connection string should ideally be provided as a global command-line option.
However, in the current Cocona framework, there's no straightforward way to pass this connection string from the command-line options to the DatabaseFactory service without involving command methods.

Current situation implementation:

public interface IOracleConnectionFactory
{
    OracleConnection Create();
}

public class OracleConnectionFactory : IOracleConnectionFactory
{
    private string? _connectionString;

    public OracleConnection Create()
    {
        if (_connectionString is null)
            throw new InvalidOperationException("Connection string is not set.");
        return new OracleConnection(_connectionString);
    }

    public void SetConnectionString(string connectionString)
    {
        _connectionString = connectionString;
    }
}

public interface IMyRepository
{
    void DoSomethingWithDb();
}

public class MyRepository : IMyRepository
{
    public readonly IOracleConnectionFactory _connectionFactory;

    public MyRepository(IOracleConnectionFactory connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public void DoSomethingWithDb()
    {
        using var connection = _connectionFactory.Create();
        connection.Open();
        // do something with the connection
    }
}

public class MyCommands
{
    private readonly OracleConnectionFactory _connectionFactory;
    private readonly IMyRepository _myRepository;

    public MyCommands(OracleConnectionFactory connectionFactory, IMyRepository myRepository)
    {
        _connectionFactory = connectionFactory;
        _myRepository = myRepository;
    }

    public void DoSomethingCommand([Option('c', Description = "The connection string")] string connectionString)
    {
        _connectionFactory.SetConnectionString(connectionString);
        _myRepository.DoSomethingWithDb();
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        var builder = CoconaApp.CreateBuilder();
        builder.Services.AddSingleton<OracleConnectionFactory>();
        builder.Services.AddSingleton<IOracleConnectionFactory>(sp => sp.GetRequiredService<OracleConnectionFactory>());
        builder.Services.AddTransient<IMyRepository, MyRepository>();
        var app = builder.Build();
        app.AddCommands<MyCommands>();
        app.Run();
    }
}

In this scenario, the DoSomethingCommand method in MyCommands is responsible for setting the connection string in the OracleConnectionFactory. This approach mixes the concerns of command logic and service configuration, which is not ideal.

Ideal implementation

public interface IOracleConnectionFactory
{
    OracleConnection Create();
}

public class OracleConnectionFactory : IOracleConnectionFactory
{
    private string _connectionString;

    public OracleConnectionFactory(GlobalOptions globalOptions)
    {
        _connectionString = globalOptions.ConnectionString;
    }

    public OracleConnection Create()
    {
        return new OracleConnection(_connectionString);
    }


}

public interface IMyRepository
{
    void DoSomethingWithDb();
}
public class MyRepository : IMyRepository
{
    public readonly IOracleConnectionFactory _connectionFactory;
    public MyRepository(IOracleConnectionFactory connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public void DoSomethingWithDb()
    {
        using var connection = _connectionFactory.Create();
        connection.Open();
        // do something with the connection
    }
}

public class MyCommands
{
    private readonly IMyRepository _myRepository;

    public MyCommands(IMyRepository myRepository )
    {
        _myRepository = myRepository;
    }

    public void DoSomethingCommand()
    {
        _myRepository.DoSomethingWithDb();
    }
}

public class GlobalOptions
{
    [Option('c', Description = "The connection string")]
    public string ConnectionString { get; set; }
}

public class Program
{
    public static void Main(string[] args)
    {
        var builder = CoconaApp.CreateBuilder();
        builder.AddGlobalOptions<GlobalOptions>()
        builder.Services.AddSingleton<IOracleConnectionFactory, OracleConnectionFactory>);
        builder.Services.AddTransient<IMyRepository, MyRepository>();
        var app = builder.Build();
        app.AddCommands<MyCommands>();
        app.Run();
    }
}

Conclusion:
This feature would greatly enhance Cocona's usability for applications where command-line options are integral to the configuration and setup.
It aligns with the principles of separation of concerns and would be a valuable addition to Cocona's capabilities.

@jtsai-osa
Copy link

Yes I too would love this please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants