1

In an in-house solution I've been working on, I've been unable to understand the benefit of how interfaces are frequently implemented throughout the project. Which is as follows:

  1. Want to do CRUD operations on a database for a specific model/table? Make a view service.
  2. Made a view service? Make an interface for it.
  3. View service handles a majority of the logic.
  4. Said logic actually needs to access the database? Make a repository.
  5. Made a repository? Make an interface for it.
  6. Repository uses dependency injection to establish an overarching database context object.

Here's an example and then I'll share what the original devs told me (and why I don't understand the reasoning):

ILicenseViewService

public  interface ILicenseViewService
{
    Task AssignLicenseAsync( ... );
    Task CreateLicenseAsync( ... );
    Task DeleteLicenseAsync( ... );
    Task<License> GetLicenseAsync( ... );
    ...
}

LicenseViewService

public class LicenseViewService : ILicenseViewService
{
    private readonly ILicenseRepository licenseRepository_;

    public LicenseViewService(ILicenseRepository licenseRepository)
    {
        licenseRepository_ = licenseRepository;
    }

    public async Task AssignLicenseAsync( ... )
    {
       if (DateTime.UtcNow > license.ExpirationDate)
       {
          throw new Exception("License is expired.");
       }

       await licenseRepository_.AssignLicenseAsync( ... );
    }
}

ILicenseRepository

public interface ILicenseRepository
{
   Task AssignLicenseAsync( ... );
   Task RevokeLicenseAsync( ... );
   void DeleteLicense( ... );
}

LicenseRepository

public class LicenseRepository : ILicenseRepository
{
   private readonly ApplicationDbContext context_;

   public LicenseRepository(ApplicationDbContext context)
   {
      context_ = context;
   }

   public async Task AssignLicenseAsync( ... )
   {
      ...
      await context_.SaveChangesAsync();
   }
}

As I understand it, interfaces supply programmers with several benefits like the relief of not being concerned with derived class(es) like Wolf and Monkey when sending those objects into some routine that expects IAnimal, when wanting to enforce a contract on how a class is used, and when wanting to utilize dependency injection on unique objects.

I have seen almost none of that in the use of these CRUD interfaces. No inherited objects, no derived classes, and no need to enforce a contract. One developer explained that the intention was for dependency injection, in which I asked why something like:

using (var myLicenseDbContext = new LicenseRepository()) { }

or

using (var myLicenseDbContext = new LicenseViewService()) { }

wouldn't do. The argument was that a programmer would want to be able to swap out a mock class and not have actual database access. Another reason was with regards to test harnessing in which case dependency injection is necessary. Aren't there other solutions to achieve that? Following this design ideology means that each model class has two interfaces, a view service, and a repository for CRUD. Isn't this unnecessary?

8protons
  • 1,379

2 Answers2

2

Dependency injection relies on a concept called "Inversion of Control." Instead of each class creating its own concrete dependencies (i.e. using the "new" keyword), you hand those dependencies to the constructor of the class instead.

For that, you need interfaces in your class constructors.

public LicenseViewService(ILicenseRepository licenseRepository)
{
    licenseRepository_ = licenseRepository;
}

This gives you two abilities. The first is the ability to change the implementation of the dependency passed to the class. This allows you to mock a dependency for unit testing, among other things.

ILicenseRepository mockRepository = new LicenseMockRepository();
var service = new LicenseViewService(mockRepository);

The second is the ability to defer construction of types to an IoC container.

var licenseRepository = Container.Resolve<ILicenseRepository>();

The container will map the interface to a class that implements that interface, and return an instance of the class. That means you need to provide those mappings in some way to the container. One way is to register types with their respective interface:

Container.Register<ILicenseRepository, LicenseRepository>();

So why would you do all this instead of just using the new keyword?

Well, one reason is that the container, given a particular interface, will find the respective class and resolve all of its dependencies recursively. That is, it will resolve the dependencies for the class being requested, and also all of its dependencies' dependencies, and so on. So an interface implementation that might require a dozen new keywords to resolve all of the necessary dependencies would only require one Container.Resolve call.

Containers also consolidate your dependency configuration into a single location for ease of maintenance and provide some lifetime management for your objects.

So how does this work in practice? I haven't personally stood up an enterprise-wide architecture, but I do use an IoC container in my current project, and it is kinda nice to know that you can use interfaces and the IoC container will more or less just manage the creation of the necessary types for you automatically and transparently.

Robert Harvey
  • 200,592
0

The use of interfaces lends itself hugely to SOLID design principles. I'd advise some research into that, and understand why interfaces would be useful, for example when;

  • Designing a user friendly API (like, CRUD)
  • Future proofing your app to change (can change implementations quickly without changing other code)
  • Keeping interfaces simple keeps classes simple (and the classes that use those classes simple, too!)
  • The ability to MOCK the interface in unit tests (TDD is the golden ticket of software development)

To return to your question,

I have seen almost none of that in the use of these CRUD interfaces

  • Your LicenseViewService can be unit tested because of the abstraction of the repository.
  • You can change how your service receives data in the future.
  • When you make a UserViewService, which may use a ILicenseViewService, you will be able to [unit test/develop/fix bugs on] that in isolation from other code, because you can mock out your LicenseViewService.
  • You didn't have to write your Create, Read, Update and Delete methods, because they come from an IRepo interface and impl... (if they didn't, then they're not doing it right.)

I could provide links to various resources for SOLID principles and why interfaces play such a huge role in it all, but tbh, google exists, and I'd recommend an entire evening of youtube based SOLID content @150% speed...

Finally, I'd like to say that NOT doing that way, NOT having interfaces and NOT allowing for unit testing and future change, will make your life hell in about 3 months time, so well worth understanding why it's done this way!

Dan Rayson
  • 182
  • 7