1

I have a number of command handlers defined for doing work against databases on various servers in our network. I am wondering if I should design each command to pass the following information in the command objects, or if any of these should be injected as scoped dependencies.

  • The server information (server name, access credentials, etc)
  • The name of the database.

Option 1: Pass the info in with the commands

// Note: I am passing ISqlServerInfo and databaseName into every method
public interface IDatabaseAnalyzer
{
    DatabaseState GetDatabaseState(ISqlServerInfo serverInfo, string databaseName);
    double GetDatabaseSize(ISqlServerInfo serverInfo, string databaseName);
}

// Note: I am passing ISqlServerInfo and databaseName into every method
public interface ISqlExecutor
{
    void Execute(ISqlServerInfo serverInfo, string databaseName, string sql);
}

public class BackupDatabaseCommand
{
    public string DatabaseName { get; set; }
    public ISqlServerInfo SqlServerInfo { get; set; }
    public string DestinationFolder { get; set; }
    public bool CopyOnly { get; set; }
}

public class BackupDatabase : ICommandHandler<BackupDatabaseCommand>
{
    public BackupDatabase(IDatabaseAnalyzer databaseAnalyzer,
                          ISqlExecutor sqlExecutor)
    {
        _databaseAnalyzer = databaseAnalyzer;
        _sqlExecutor = sqlExecutor;
    }

    public void Execute(BackupDatabaseCommand command) 
    {
        var dbState = _databaseAnalyzer.GetDatabaseState(command.SqlServerInfo, command.DatabaseName);
        if (dbState != DatabaseState.Online)
            throw new CommandFailedException();

        string sql = $"backup database [{command.DatabaseName}] ";
                     $"to disk = '{command.DestinationFolder}\\{command.DatabaseName}_{DateTime.Now}.bak'";
        if (command.CopyOnly)
            sql += " with copy_only";

        _sqlExecutor.Execute(command.SqlServerInfo, command.DatabaseName, sql);
    }
}

public class BackupAllDatabases : ICommandHandler<BackupAllDatabasesCommand>
{
    public BackupAllDatabases(ISqlServerService sqlServerService
                              IDatabaseService databaseService,
                              ICommandHandler<BackupDatabaseCommand> backupDatabaseHandler)
    {
        _sqlServerService = sqlServerService;
        _databaseService = databaseService;
        _backupDatabaseHandler = backupDatabaseHandler;
    }

    public void Execute(BackupAllDatabasesCommand command) 
    {
        var databases = _databaseService.GetAllDatabases();
        foreach (DatabaseInfo databaseInfo in databases)
        {
            var sqlServerInfo = _sqlServerService.Get(databaseInfo.SqlServerId);
            _backupDatabaseHandler.Execute(new BackupDatabaseCommand
            {
                DatabaseName = databaseInfo.DatabaseName,
                SqlServerInfo = sqlServerInfo,
                DestinationFolder = command.DestinationFolder,
                CopyOnly = command.CopyOnly
            });
        }
    }
}

Cons:

  • Every method in the IDatabaseAnalyzer and ISqlExecutor services require the server and database info to be passed in. These are obviously dependencies for these services.

Option 2: Same as Option 1, but use factories for the services

// Note: Much cleaner methods now...
public interface IDatabaseAnalyzer
{
    DatabaseState GetDatabaseState();
    double GetDatabaseSize();
}

public interface IDatabaseAnalyzerFactory
{
    IDatabaseAnalyzer Create(ISqlServerInfo serverInfo, string databaseName);
}

// Note: Much cleaner methods now...
public interface ISqlExecutor
{
    void Execute(string sql);
}

public interface ISqlExecutorFactory
{
    IDatabaseAnalyzer Create(ISqlServerInfo serverInfo, string databaseName);
}

public class BackupDatabaseCommand
{
    public string DatabaseName { get; set; }
    public ISqlServerInfo SqlServerInfo { get; set; }
    public string DestinationFolder { get; set; }
    public bool CopyOnly { get; set; }
}

public class BackupDatabase : ICommandHandler<BackupDatabaseCommand>
{
    public BackupDatabase(IDatabaseAnalyzerFactory databaseAnalyzerFactory,
                          ISqlExecutorFactory sqlExecutorFactory)
    {
        _databaseAnalyzerFactory = databaseAnalyzerFactory;
        _sqlExecutorFactory = sqlExecutorFactory;
    }

    public void Execute(BackupDatabaseCommand command) 
    {
        var dbAnalyzer = _databaseAnalyzerFactory.Create(command.SqlServerInfo, command.DatabaseName);
        var dbState = dbAnalyzer.GetDatabaseState();
        if (dbState != DatabaseState.Online)
            throw new CommandFailedException();

        string sql = $"backup database [{command.DatabaseName}] ";
                     $"to disk = '{command.DestinationFolder}\\{command.DatabaseName}_{DateTime.Now}.bak'";
        if (command.CopyOnly)
            sql += " with copy_only";

        var sqlExecutor = _sqlExecutorFactory.Create(command.SqlServerInfo, command.DatabaseName);
        sqlExecutor.Execute(sql);
    }
}

public class BackupAllDatabases : ICommandHandler<BackupAllDatabases>
{
    public BackupAllDatabases(ISqlServerService sqlServerService
                              IDatabaseService databaseService,
                              ICommandHandler<BackupDatabaseCommand> backupDatabaseHandler)
    {
        _sqlServerService = sqlServerService;
        _databaseService = databaseService;
        _backupDatabaseHandler = backupDatabaseHandler;
    }

    public void Execute(BackupAllDatabases command) 
    {
        var databases = _databaseService.GetAllDatabases();
        foreach (DatabaseInfo databaseInfo in databases)
        {
            var sqlServerInfo = _sqlServerService.Get(databaseInfo.SqlServerId);
            _backupDatabaseHandler.Execute(new BackupDatabaseCommand
            {
                DatabaseName = databaseInfo.DatabaseName,
                SqlServerInfo = sqlServerInfo,
                DestinationFolder = command.DestinationFolder,
                CopyOnly = command.CopyOnly
            });
        }
    }
}

Pros:

  • The helper services look much cleaner.

Cons:

  • Its a bit annoying that I have to create an instance of my dependencies using a factory.

Option 3: Use a child container to create dependency scope

public interface IDatabaseAnalyzer
{
    DatabaseState GetDatabaseState();
    double GetDatabaseSize();
}

public interface ISqlExecutor
{
    void Execute(string sql);
}

public class BackupDatabaseCommand
{
    public string DestinationFolder { get; set; }
    public bool CopyOnly { get; set; }
}

public class BackupDatabase : ICommandHandler<BackupDatabaseCommand>
{
    // Note: IDatabaseInfo is now a dependency
    public BackupDatabase(IDatabaseInfo databaseInfo,
                          IDatabaseAnalyzer databaseAnalyzer, 
                          ISqlExecutor sqlExecutor)
    {
        _databaseInfo = databaseInfo;
        _databaseAnalyzer = databaseAnalyzer;
        _sqlExecutor = sqlExecutor;
    }

    public void Execute(BackupDatabaseCommand command) 
    {
        var dbState = _databaseAnalyzer.GetDatabaseState();
        if (dbState != DatabaseState.Online)
            throw new CommandFailedException();

        string sql = $"backup database [{_databaseInfo.DatabaseName}] "+ 
                     $"to disk = '{command.DestinationFolder}\\{_databaseInfo.DatabaseName}_{DateTime.Now}.bak'";
        if (command.CopyOnly)
            sql += " with copy_only";

        _sqlExecutor.Execute(sql);
    }
}

public class BackupAllDatabases : ICommandHandler<BackupAllDatabases>
{
    public BackupAllDatabases(IContainer container,
                              ISqlServerService sqlServerService
                              IDatabaseService databaseService)
    {
        _container = container;
        _sqlServerService = sqlServerService;
        _databaseService = databaseService;
    }

    public void Execute(BackupAllDatabases command) 
    {
        var databases = _databaseService.GetAllDatabases();
        foreach (DatabaseInfo databaseInfo in databases)
        {
            var sqlServerInfo = _sqlServerService.Get(databaseInfo.SqlServerId);

            // Create child container and inject the dependencies needed
            var childContainer = _container.CreateChildContainer();
            childContainer.RegisterInstance<ISqlServerInfo>(sqlServerInfo);
            childContainer.RegisterInstance<IDatabaseInfo>(databaseInfo);

            var backupDatabase = childContainer.Resolve<ICommandHandler<BackupDatabaseCommand>>();

            backupDatabase.Execute(new BackupDatabaseCommand
            {
                DestinationFolder = command.DestinationFolder,
                CopyOnly = command.CopyOnly
            });
        }
    }
}

Pros:

  • Like the helper services, the database command now has the database/server info as a dependency.

Cons:

  • The BackupAllDatabases command has access to IContainer, which feels godlike.
  • It isn't too obvious what is happening or why I am creating a child container.

Does anyone have alternative advice on this?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.