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:
- Want to do CRUD operations on a database for a specific model/table? Make a view service.
- Made a view service? Make an interface for it.
- View service handles a majority of the logic.
- Said logic actually needs to access the database? Make a repository.
- Made a repository? Make an interface for it.
- 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?