4

I currently have an ASP.Net MVC 5 app that uses 3 external datasources (calls are made to external APIs, responses are deserialized, and mapped to business POCOs).

The app currently uses SimpleInjector to inject concrete repositories for each datasource into a business logic layer for consumption.

The problem is, as more datasources are added (potentially 20-30), the constructor will be huge and injecting all these repositories seems cumbersome.

Is there a better pattern/approach to consuming all the datasources rather than using different repositories?

Would a facade or some other pattern be more appropriate?

Very generic examples:

    public class MyObject(){
         public IEnumerable<Cat> Cats { get; set; }
         public IEnumerable<Dog> Dogs { get; set; }
         public IEnumerable<Fish> Fish { get; set; }

    }

        public class BusinessLogic{
               private readonly ISourceARepository _sourceA;
               private readonly ISourceBRepository _sourceB;
               private readonly ISourceCRepository _sourceC;

            public BusinessLogic(ISourceARepository sourceA, ISourceBRepository sourceB, ISourceCRepository sourceC){
                    _sourceA = sourceA;
                    _sourceB = sourceB;
                    _sourceC = sourceC;
}

private Dog MapSourceARecordToDog(SourceARecord record){
    var result = new Dog();    

    if(record != null){
        result.Name = record.NameField;
        result.Age = record.Age;
    }     

    return result;
}

private Cat MapSourceBRecordToCat(SourceBRecord record){
    var result = new Cat();

    if(record != null){
         result.Name = record.NameField;
         result.Weight = record.WeightField;
    }

    return result;
}

private Fish MapSourceCRecordToFish(SourceCRecord record){
    var result = new Fish();

    if(record != null){
        result.ID = record.IDField;
        result.Name = record.NameField;

    }

    return result;
}

            public MyObject GetResults(){
                  var result = new MyObject();

                  result.Dogs = _sourceA.GetAll().Select(MapSourceARecordToDog).ToList();
                  result.Cats = _sourceB.GetAll().Select(MapSourceBRecordToCat).ToList();
                  result.Fish = _sourceC.GetAll().Select(MapSourceCRecordToFish).ToList();
                  return result;
            }
        }

        public class SourceARespository : ISourceARepository{
             public IEnumerable<SourceAResult> GetAll(){
                 return new List<SourceAResult>();
             }
         }

        public class SourceBRespository : ISourceBRepository{
             public IEnumerable<SourceBResult> GetAll(){
                 return new List<SourceBResult>();
             }
         }

        public class SourceCRespository : ISourceCRepository{
             public IEnumerable<SourceCResult> GetAll(){
                 return new List<SourceCResult>();
             }
         }

Update: This is not a duplicate of the constructor madness question, because in this scenario, a class needs many different datasources, but still has single responsibility. Hence, it warrants its own explanation and answer.

user2966445
  • 1,267
  • 16
  • 39

1 Answers1

1

You should only be injecting one repository per entity into a consumer that depends on it. You may also choose to adapt the repository with a business class intermediary.

UPDATE:

Based on the information provided in the question and the problem statement, here is one possible solution. Define your core infrastructure like this:

public abstract class Entity<TEntity, TDomainObject, TIRepository>
    where TEntity       : Entity<TEntity, TDomainObject, TIRepository>
    where TDomainObject : Entity<TEntity, TDomainObject, TIRepository>.BaseDomainObject, new()
    where TIRepository  : Entity<TEntity, TDomainObject, TIRepository>.IBaseRepository
{

    public class BaseDomainObject {}

    public interface IBaseRepository
    {
        IEnumerable<TDomainObject> GetAll();
        IEnumerable<T> GetAllMapped<T>(Func<TDomainObject, T> mapper);
    }

    public class BaseRepository : IBaseRepository
    {
        public IEnumerable<TDomainObject> GetAll()
        {
            return new List<TDomainObject>();
        }
        public IEnumerable<T> GetAllMapped<T>(Func<TDomainObject, T> mapper)
        {
            return this.GetAll().Select(mapper);
        }
    }

}

Define your source entities like this:

public class SourceA : Entity<SourceA, SourceA.DomainObject, SourceA.IRepository>
{
    public class DomainObject : BaseDomainObject
    {
        public  string  Name;
        public  int     Age;
    }
    public interface IRepository : IBaseRepository {}
    public class Repository : BaseRepository, IRepository {}
}

public class SourceB : Entity<SourceB, SourceB.DomainObject, SourceB.IRepository>
{
    public class DomainObject : BaseDomainObject
    {
        public  string  Name;
        public  decimal Weight;
    }
    public interface IRepository : IBaseRepository {}
    public class Repository : BaseRepository, IRepository {}
}

public class SourceC : Entity<SourceC, SourceC.DomainObject, SourceC.IRepository>
{
    public class DomainObject : BaseDomainObject
    {
        public  Guid    Id;
        public  string  Name;
    }
    public interface IRepository : IBaseRepository {}
    public class Repository : BaseRepository, IRepository {}
}

Then define an ISourceRepositoryContext interface like this and add each source repository interface here:

public interface ISourceRepositoryContext
{

    SourceA.IRepository SourceARepository { get; }
    SourceB.IRepository SourceBRepository { get; }
    SourceC.IRepository SourceCRepository { get; }

}

Then define a default implementation for the interface:

public class DefaultSourceRepositoryContext : ISourceRepositoryContext
{
    public SourceA.IRepository SourceARepository => new SourceA.Repository();
    public SourceB.IRepository SourceBRepository => new SourceB.Repository();
    public SourceC.IRepository SourceCRepository => new SourceC.Repository();
}

Define your result transport objects:

public class Dog
{
    public  string  Name;
    public  int     Age;
}

public class Cat
{
    public  string  Name;
    public  decimal Weight;
}

public class Fish
{
    public  Guid    Id;
    public  string  Name;
}

public class MyObject
{
     public IEnumerable<Cat>  Cats { get; set; }
     public IEnumerable<Dog>  Dogs { get; set; }
     public IEnumerable<Fish> Fish { get; set; }
}

Then consume the ISourceRepositoryContext in your BusinessLogic class:

public class BusinessLogic
{
    protected ISourceRepositoryContext repositories;

    public BusinessLogic(ISourceRepositoryContext repositories)
    {
        this.repositories = repositories;
    }

    public MyObject GetResults(string param1)
    {
        return new MyObject()
        {
            Dogs    = this.repositories.SourceARepository.GetAllMapped
            (domainObject=>new Dog
            {
                Age     = domainObject.Age,
                Name    = domainObject.Name
            }),

            Cats    = this.repositories.SourceBRepository.GetAllMapped
            (domainObject=>new Cat
            {
                Name    = domainObject.Name,
                Weight  = domainObject.Weight
            }),

            Fish    = this.repositories.SourceCRepository.GetAllMapped
            (domainObject=>new Fish
            {
                Id      = domainObject.Id,
                Name    = domainObject.Name
            }),
        };
    }
}

I've confirmed that the above compiles under C# 6.0.

I would recommend changing IRepository to IBusiness in Entity and split out the data access concerns from into an IDataAccess interface that only the IBusiness implementors receive via their constructors. And then change the ISourceRepositoryContext to ISourceEntities and change the IRepository properties in that interface to IBusiness properties instead.

The BusinessLogic class is the part that really concerns me. Are you sure this one class won't be taking on too many concerns? Is this supposed to be a UoW class?

For a more complete solution based on similar techniques, check out my answer to this other question: .NET Managing Layers Relationships

Community
  • 1
  • 1
Tyree Jackson
  • 2,588
  • 16
  • 22
  • @user2966445 I'll be happy to elaborate, but first can you provide class definitions for MyObject, SourceAResult, SourceBResult, and SourceCResult. Do you really need all 200 sources for you BusinessLogic class? Or is BusinessLogic intended to be some sort of base class? – Tyree Jackson Aug 02 '15 at 01:50
  • @user2966445 As a general guideline, if you are adding more than 3-5 dependencies into a class, it is generally considered a code smell and may indicate that your class is taking on too much responsibilities. You may want to consider refactoring it. – Tyree Jackson Aug 02 '15 at 01:51
  • Thanks, but I'm looking for an actual alternative. I realize there is a problem (hence asking the question). The example should provide enough to understand the concept. As more datasources are added, the constructor grows. – user2966445 Aug 02 '15 at 02:08
  • @user2966445 The example unfortunately does not provide enough information on how MyObject relates to SourceAResult, SourceBResult and SourceCResult. Without that information it is very difficult to analyze your situation and recommend an alternative – Tyree Jackson Aug 02 '15 at 02:25
  • @user2966445 I've provided an update to my answer that hopefully provides you a solution or at the very least gives you some ideas on how you can refactor your code. – Tyree Jackson Aug 02 '15 at 03:42
  • @user2966445 I just saw your update. I will make adjustments to my answer to accommodate what you are trying to accomplish with as DRY as an implementation as I can. – Tyree Jackson Aug 02 '15 at 03:44
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/84908/discussion-between-tyree-jackson-and-user2966445). – Tyree Jackson Aug 02 '15 at 05:13
  • @user2966445 Answer has been updated to incorporate details and intent expressed in your last update. – Tyree Jackson Aug 02 '15 at 05:16
  • @user2966445 Did the updates to this answer solve your issue? – Tyree Jackson Aug 10 '15 at 15:25