Convention Over Configuration for Repositories

I oftentimes call this as module discovery but what really happens when I speak about this is that I follow the design principle called Convention over Configuration or Coding by Convention. What does this mean? It’s forcing the developers to follow certain conventions enforced by the framework. An example would be in ASP.NET MVC or ASP.NET Core, when we create a controller, we always post fix it with the word “Controller”. Then, it will be treated in some special way by the framework.

Now, it has been common to use dependency injection especially, in ASP.NET Core, it is quite convenient and a norm to use it. But then, everytime you create a new repository, you’ll end up specifying which types to use when your code needs it:

services.AddScoped<IUserRepository, UserRepository>();
services.AddScoped<IRoleRepository, RoleRepository>();
services.AddScoped<IPermissionRepository, PermissionRepository>();

You’ll might end up with a long list of repositories. And it might not be just repositories that you are trying to specify. You may also have a different layer for your business logic, of which, you also need to specify them one by one.

So, to resolve this painful ceremony of doing this repeatedly, let’s have a separate library for our interfaces and another one for our repositories. Then, let’s agree upon a convention: always end our repository with the word “Repository”. Well, I bet every organization and any developer has been using this convention anyway so, there is really no “forced” convention here, isn’t it?

The next step is to register those repositories in our web application or web API. But instead of registering them one by one, we will capitalize with the fact that we have a convention and let’s discover the types on our libraries instead using reflection.

// Add repositories
var repositoryTypes =
    from t in typeof(UserRepository).Assembly.GetTypes()
    where t.Name.EndsWith("Repository")
    select t;

foreach(var repositoryType in repositoryTypes)
{
    var repositoryInterfaces =
        from t in repositoryType.GetInterfaces()
        where t.Name.EndsWith("Repository")
        select t;

    foreach(var repositoryInterface in repositoryInterfaces)
    {
        services.AddScoped(repositoryInterface, repositoryType);
    }
}

Let’s itemize what’s going on here:

  1. First up, it assumes that our UserRepository is inside our repository DLL. You can replace this class with a different repository that is present in your DLL or you can be explicit with your repository DLL.
  2. The repositoryTypes variable will look into the types in that DLL that ends with “Repository”.
  3. Then, for each of these repository types, take all the interfaces that also ends with “Repository”.
  4. Register each of the discovered repository type to their corresponding interfaces.

Thus, any repository in that DLL following the convention will be registered automatically, which will save you a lot of code and time as well registering them manually.

Place this code either directly inside the ConfigureServices() method of the Startup.cs file or you can place it on a separate method that is called in the ConfigureServices() method.

This method can be employed on any other module or layer so if you want to follow the same concept to your business logic layer or other layers, you can choose a convention, e.g. all business logic modules should end with “Manager” or “Logic”.