dimanche 20 février 2022

Repository Interface with (or without) IProgress

I've got a repository interface (simplified example code):

public interface IPersonRepository
{
    Task<PersonDTO> Get();
}

With two implementations.

One for a direct connection to a database:

public SqlPersonRepository : SqlRepository, IPersonRepository
{
    public SqlPersonRepository(IDbConnectionProvider dbCon) : base(dbCon) { }

    public async Task<PersonDTO> Get()
    {
        // use dbCon and dapper to get PersonDTO from database
    }
}

And another one for remote access via web api:

public ApiPersonRepository : ApiRepository, IPersonRepository
{
    public ApiPersonRepository(IApiConnectionProvider apiCon) : base(apiCon) { }

    public async Task<PersonDTO> Get()
    {
        // use apiCon (contains base url and access token) to perform an HTTP GET request
    }
}

The interface makes sense here, because the server can use the SqlPersonRepository. And the remote (native) client can use the ApiPersonRepository. And for most all of the the use cases, this is all I need.

However, my application supports an extraction of a subset of data from the server so that the client application can run while offline. In this case, I'm not just grabbing one person, I'm grabbing a large set of data (several to tens of megabytes) which many times will be downloaded over a slow mobile connection. I need to pass in an IProgress implementation so I can report progress.

In those cases, I need an ApiDatabaseRepository that looks like this:

public ApiDatabaseRepository : ApiRepository, IDatabaseRepository
{
    public ApiDatabaseRepository(IApiConnectionProvider apiCon) : base(apiCon) { }

    public async Task<DatabaseDTO> Get(IProgress<int?> progress)
    {
        // use apiCon (contains base url and access token) to perform an HTTP GET request
        // as data is pulled down, report back a percent downloaded, e.g.
        progress.Report(percentDownloaded);
    }
}

However the SqlDatabaseRepository does NOT need to use IProgress (even if Dapper COULD report progress against a database query, which I don't think it can). Regardless, I'm not worried about progress when querying the database directly, but I am worried about it when making an API call.

So the easy solution, is that the SqlDatabaseRepository implementation accepts the IProgress parameter, with a default value of null, and then the implementing method just ignores that value.

public SqlDatabaseRepository : SqlRepository, IDatabaseRepository
{
    public SqlDatabaseRepository(IDbConnectionProvider dbCon) : base(dbCon) { }

    public async Task<DatabaseDTO> Get(IProgress<int?> progress = null)
    {
        // use dbCon and dapper to get DatabaseDTO from database
        // progress is never used
    }
}

But that smells funny. And when things smell funny, I wonder if I'm doing something wrong. This method signature would give the indication that progress will be reported, even though it won't.

Is there a design pattern or a different architecture I should be using in this case?

Aucun commentaire:

Enregistrer un commentaire