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?

0

2 Answers 2

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.

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!

6
  • Hand-waving about SOLID wouldn't be enough to convince me. Nor is TDD any kind of golden ticket. Sounds like you fully drank the architectural Kool-Aid, but I wonder if you fully understand the reasons why. May 22, 2018 at 18:54
  • @RobertHarvey I wonder if you could condense the reasons why into a short reply. "Sounds like you fully drank the architectural Kool-Aid" - Sounds like you've been awake too long ^^ Mr Grumpypumps
    – Dan Rayson
    May 22, 2018 at 19:07
  • Architectures like this have a lot to do with the nature of Java and large teams, and the ability of each team member to work with digestible bits of the system without being overwhelmed by complexity. I don't think such architectures have all that much to do with the SOLID principles or TDD, except insofar as they help corral some of that complexity. SOLID and TDD are a means to an end, not the end itself. May 22, 2018 at 19:14
  • @RobertHarvey I dunno mate, what I gathered from your answer was that it's useful for creating objects quickly. I gave a broad overview of various reasons why having those interfaces is a good design pattern. Perhaps I did do I little more SOLID hand waving than necessary, but still.
    – Dan Rayson
    May 22, 2018 at 19:54
  • In my answer, I describe interfaces as just an enabler for Dependency Injection and multiple implementations. The "creating objects quickly" part is what the IoC container does. May 22, 2018 at 20:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.