SOLID: Interface Segregation and Single Responsibility Principles in Database Layer

Greg Radzio
5 min readAug 22, 2020

SOLID is big part of working at Cobiro. It allows us to write scalable and easy to maintain code, when combining it with Test Driven Development and Continuous Deployment, we are able to ship to production multiple times a day and give new features into hands of users faster. It also means that we learn quicker of what the users need and gain the competitive advantage.

SOLID is especially important when you work in a startup or a scale-up, because it allows you to modify your code when the business requirements change (and they change constantly in a reality when you want to build the next unicorn startup!).

Today I wanted to get you more familiar with Interface Segregation Principle.

What is Interface Segregation Principle and how it differs from a regular Interface?

So interfacing is a foundation of ISP. However ISP goes way beyond it. What you need to understand is that you should avoid building big interfaces.

What is a typical interface that I see most of the times?

<?php declare(strict_types=1);

namespace App\Contracts;

use App\User;

interface UserService {
function getAllUsers(): array;
function getActiveUsers(): array;
function getUser(int $id): User;
function createUser(array $data): User;
function deleteUser(int $id): void;
function updateUser(int $id, array $data): User;
}

What are the issues here? well… there are a few:

firstly, it seems like a scope of this class is to manage the user, this is ok for simple cases but I find it quite rigid. For example the getActiveUsers already leaks some information about the database and becomes quite specific, be careful about it — you might lead to tons of helper method to support more complex queries and that might not be the layer to do so. I would advise to keep it simple and generic (it is infrastructure layer after all) so try to use parameters and generic methods because changing implementation of this interface in another means of transport will be easier.

secondly, you force your clients to inject methods that they do not intend to use. well quite often it is just one of these methods. In this case, by implementing this interface, you might end up over-engineering the implementing class by doing way more than expected or even worse: make a bunch of empty method implementations that throw exceptions!

thirdly, you are creating Efferent Couplings which means that a lot of classes will depend on this interface, which might need to a god class code smell. This coupling can increase fragility of your system (changes in a class that implements this interface, may break multiple other classes).

So what should you do instead?

Split it into smaller interfaces of course!

Where is the boundary? the answer is: as small as possible. I tend to think of use cases of my solution and then see what do I need in each client and simply use only these methods.

How does Single Responsibility Principle combine with Interface Segregation?

Actually when building scalable systems, these two combine pretty well.

If you combine it with CQRS or CQS approach, then you can get the full potential of it.

Think about it as Task Based UI (not just a booooring CRUD) in which a single process equals to a single action taken in your system. So let’s assume a following architecture:

Controller (UI layer) > Command Handler (Application layer) > Factory class (Domain layer) > Repository (Infrastructure Layer).

Now you could make a single responsibility classes that deal with creation of the user like this:

CreateUserController (takes POST request and dispatches a CreateUserCommand on CommandBus)

CreateUserCommandHandler (receives a CreateUserCommand, then creates an Aggregate via abstract factory class and)

UserCreatorFactory (creates an aggregate in a valid state and returns it to CreateUserCommandHandler that passes it down to an Infrastructure layer)

UserSaver (saves the user Aggregate in the database database)

So every class mentioned above is re-usable, independent and has only 1 reason to exist. So we follow the SRP principle, so how can we construct UserServer class? Well it can implement a following interface:

<?php declare(strict_types=1);

namespace App\Users\Domain;

interface SavesUser
{
public function save(UserAggregate $user): void;
}

It has only 1 method, so you do not force its clients to use any methods it does not need and it has 1 reason to exist. So in fact you follow both: the Single Responsibility Principle and the Interface Segregation Principle :)

Time for some anti patterns

When starting on Domain Driven Design for the first time in Typo3 or C# I always ended up in a same architecture:

Controller > Service > Repository

This ended up in an anaemic domain model and almost empty Service classes that were one of two following anti patterns:

  • Nothing, it would just a wrapper around Repository and all it did, was calling a method on a Repository — here you deal with a layer that does not do anything, so you should avoid that
  • Too much, it would manage multiple repositories to handle a single request, so I would make repository for a main resource and multiple sub resources e.g. UserRepository, UserRoleRepository, UserAddressRepository and then I would sequentially call each of these to save an object in the database — here you have a problem of incorrect Aggregate boundries. In fact it should be only 1 aggregate that you save in the db. Remember an aggregate has only 1 repository and aggregate does not equal to database table. You can de-couple your database persistence model from your domain model. A perfect example of this would be for example a tree or graph data structure.

Time for some videos

Conclusion

Even though I have touched upon Domain Driven Design and Aggregates, what you should understand from this article is that you should define a smaller well defined interfaces that allow you to switch implementation of your classes. Please keep in mind that ISP is not limited to database access layer. Some time ago I have worked on a Betting system that allow you to Resolve the result of the sport game together with user’s prediction of the result. Once the game ended each user would receive points. In order to do so, I have made a Resolver interface that has only 1 method: resolve. This way I could create a resolver for each game type and result outcome.

I hope this article inspired you and as always, stay curious and hungry for more knowledge, there is more to come soon :)

--

--

Greg Radzio

CTO at Cobiro, promoter of TDD, SOLID, DDD and Design Patterns and Agile.