Search code examples
c#dependency-injection.net-corerepository-patternfactory-pattern

Implementing "Repository Rotator" factory


I have .Net Core Web API application. There is some Get method in controller and injected IRepositoryProviderFactory. Nothing special.

[ApiController]
public class DataController : ControllerBase
{
    private readonly ILogger<DataController> _logger;
    private readonly IRepositoryProviderFactory _repositoryProviderFactory;

    #region ctor

    /// <summary>
    /// ctor
    /// </summary>
    public DataController(ILogger<DataController> logger, IRepositoryProviderFactory repositoryProviderFactory)
    {
        _logger = logger;
        _repositoryProviderFactory = repositoryProviderFactory;
    }

    [HttpPost]
    public async Task<IActionResult> GetData([FromBody] SearchModel model)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        try
        {
            var data = await _repositoryProviderFactory.GetDataAsync(model);

            return Ok(data);

        }
        catch (Exception ex)
        {
            return StatusCode((int)HttpStatusCode.InternalServerError);
        }
    }
}

There are repositories based on the same Interface to get data from different data sources.

public Repository1: IDataRepository {}
public Repository2: IDataRepository {}
public Repository3: IDataRepository {}

I'm using DI so all parts are registered in services as Scoped or Transients. Some repositories are using EntityFramework.

services.AddScoped<IDataRepository, Repository1>();
services.AddScoped<IDataRepository, Repository2>();
services.AddScoped<IDataRepository, Repository3>();

Ok, now I need to implement RepositoryProviderFactory to return repository. But there is one required functionality: it must return for every call different repository.

I have injected IEnumerable and I need to return Repository1, Repository2, Repository3 and again Repository1, … etc. So all repositores are used the same time.

/// <summary>
/// ctor
/// </summary>
public RepositoryProviderFactory(
    ILogger<RepositoryProviderFactory> logger,
    IEnumerable<IDataRepository> dataRepositories)
{
    _logger = logger;
    _dataRepositories = dataRepositories;
}

public IDataRepository GetRepository()
{
   // TODO: Logic to cycle repositories

    var instance = dataRepositories.Where();

    return instance;
}

How to do this?

Factory can't be registered as Singleton, because repositories have another dependencies that have Scoped Lifetime (DbContext etc.)

How can I create some thread safe singleton object to be able persists last used repository and serve another one, for another call?


Solution

  • Well, I did it this way.

    I made class GlobalAppData and registered is as Singleton.

    services.AddSingleton<GlobalAppData>();
    

    Then I made simple implementation (not finished yet).

    public class GlobalAppData
        {
            private IDataRepository[] _availableRepositories;
            private IDataRepository_lastUsedRepository;
    
            /// <summary>
            /// ctor
            /// </summary>
            public GlobalAppData()
            {
                _availableRepositories = new IDataRepository[0];
            }
    
            public void TryRegisterRepositories(IEnumerable<IDataRepository> repositories)
            {
                if (_availableRepositories.Length > 0)
                {
                    return;
                }
    
                _availableRepositories = repositories.ToArray();
    
                _lastUsedRepository = null;
            }
    
            public IDataRepository GetNextRepository()
            {
                if (_lastUsedRepository == null)
                {
                    _lastUsedRepository = _availableRepositories[0];
                    return _lastUsedRepository;
                }
    
                var lastUsedIndex = _availableRepositories.IndexOf(_lastUsedRepository);
                if (lastUsedIndex < _availableRepositories.Length - 1)
                {
                    lastUsedIndex++;
                }
                else
                {
                    lastUsedIndex = 0;
                }
    
                _lastUsedRepository = _availableRepositories[lastUsedIndex];
                return _lastUsedRepository;
            }
        }
    

    Then because of DI there will be stored original instances, not injected ones, I made just simple compare in factory.

            var instanceData = _globalAppData.GetNextRepository();
            var instance = _repositories.SingleOrDefault(r => r.GetType() == instanceData.GetType());
    

    Not perfect, but it works as a start.