architecture – How to keep C# interfaces organized?

I am working as part of the team on the web api application. The Entity Framework used as ORM, I have the idea to keep all selectors in services. E.g. we have Cat table, then would be CatSelectorsService.

class CatSelectorsService {
    public Expressions<Func<Cat, bool>> NeedToPlay() {
        var date = DateTime.Now.AddHours(8);
        return it => it.LastTimePlayed > date;
    }

    public Expression<Func<Cat, CatView> CatView() {
        var needToPlay = NeedToPlay();
        return it => new CatView {
            Id = it.Id,
            NeedToPlay = needToPlay.Invoke(it)
        }
    }
}

// sample usage
var catViews = context.Cats.Select(catSelectosService.CatView());
var catIdsNeedToPlay = context.Cats.Where(catSelectorsService.NeedToPlay()).Select(it => it.Id);

That approach really helps to avoid code duplication and help organize business logic of the application. Initially was planning to have exactly one selectors service per entity. So, CatSelectorsService

  • Cat – entity name
  • Selectors – what class handles
  • Service – can be recived via constructor DI from interface, e.g.
    public MyClass {
        private readonly ICatSelectorsService catSelectorsService;
        public MyClass(ICatSelectorsService catSelectorsService) {
            this.catSelectorsService = catSelectorsService;
        }
    }

Good organization. On practice have meet two issues with this approach:

  1. Circular depencies, when some selectors want to use other selectors, f.e. if have Cats and Users, we might want to the next inside selectors service
    // inside cats selectors service
    public Expressions<Func<Cat, CatView>> CatView() {
        return it => new CatView {
            Users = it.Users.Select(userSelectorsService.UserView()), // here reference userSelectors in catSelectors
        }
    }
    
    // inside users selectors service
    public Expressions<Func<User, UserViewWithCats>> UserView() {
        return it => new CatView {
            Users = it.Users.Select(catSelectorsService.CatView()), // here reference catSelectors in userSelectors
        }
    }

current solution to create shared selectors and reference them, e.g.

public Expression<Func<Cat, CatView>> CatView() => sharedSelectors.CatView(); // shared selectors can be imported in other services
  1. Testing, in the first sample, I’ll copy it to here:
    class CatSelectorsService {
        public Expressions<Func<Cat, bool>> NeedToPlay() {
            var date = DateTime.Now.AddHours(8);
            return it => it.LastTimePlayed > date;
        }
    
        public Expression<Func<Cat, CatView> CatView() {
            var needToPlay = NeedToPlay();
            return it => new CatView {
                Id = it.Id,
                NeedToPlay = needToPlay.Invoke(it)
            }
        }
    }
    
    // sample usage
    var catViews = context.Cats.Select(catSelectosService.CatView());
    var catIdsNeedToPlay = context.Cats.Where(catSelectorsService.NeedToPlay()).Select(it => it.Id);

CatView uses NeedToPlay when writing tests for cat selectors, I want to mock NeedToPlay, but to do it with xunit, I have to move NeedToPlay to separate service. Or use some workaround like harmony, I have tried harmony, and it is works, however it also make the ecosystem of the application harder and I’d like to keep it simple. Harmony for unit testing is rather advanced library – some issues with running in parallel. So ended with next:

class SharedSelectorsService {
    public Expressions<Func<Cat, bool>> NeedToPlay() {
        var date = DateTime.Now.AddHours(8);
        return it => it.LastTimePlayed > date;
    }

    public Expression<Func<Cat, CatView> CatView() {
        var needToPlay = NeedToPlay();
        return it => new CatView {
            Id = it.Id,
            NeedToPlay = needToPlay.Invoke(it)
        }
    }
}

class CatSelectorsService {
    private readonly ISharedSelectorsService sharedSelectorsService;

    public CatSelectorsService(ISharedSelectorsService sharedSelectorsService) {
        this.sharedSelectorsService = sharedSelectorsService;
    }

    public Expressions<Func<Cat, bool>> NeedToPlay() => sharedSelectorsService.NeedToPlay()
    public Expression<Func<Cat, CatView> CatView() => sharedSelectorsService.CatView();
}

I don’t like that finally have lot of services, and one them is just a documentation(CatSelectorsService), to keep things organized, but for me it seems currently the best solution. However I really wonder, can be something better?

PS. Also have thought about creating the documentation, but I have not experience and seems to me that self documentated the code cheaper then support of actual version of documenation.