3

I'm defining a class structure for persisting to our cassandra database, and I'm unsure about using a combination of an abstract class and an interface. I have two concrete classes, one for persisting reports and the other for persisting configurations. They both have separate entity classes defined else where that the entityMapper uses. I have read this post which is very similar, but I don't think their example really highlights what I want to know.

My structure looks like this:

Interface

public interface CassandraRepository<T> {

    void save(T object);

    void delete(T object);
}

Abstract class

import com.datastax.driver.core.Session;
import com.datastax.driver.mapping.Mapper;
import com.datastax.driver.mapping.MappingManager;

abstract class AbstractGenericRepository<T> implements CassandraRepository<T> {

    private final Session sesion;
    Mapper<T> entityMapper;

    AbstractGenericRepository(final Session session) {
        this.session = session;
        final MappingManager manager = new MappingManager(this.session);
        this.entityMapper = manager.mapper(getRepositoryClass());
    }

    protected abstract Class<T> getRepositoryClass();
}

Configuration class

import com.model.Configuration;
import com.datastax.driver.core.Session;

public class ConfigurationRepository extends AbstractGenericRepository<Configuration> {

    public ConfigurationRepository(final Session session) {
        super(session);
    }

    @Override
    public void delete(final Configuration object) {
        this.entityMapper.delete(object);
    }

    @Override
    public void save(final Configuration object) {
        this.entityMapper.save(object);
    }

    @Override
    protected Class<Configuration> getRepositoryClass() {
        return Configuration.class;
    }
}

Report class

import com.model.Report;
import com.datastax.driver.core.Session;

public class ReportRepository  extends AbstractGenericRepository<Report> {

    ReportRepository(Session session) {
        super(session);
    }

    @Override
    public void save(Report object) {
        this.entityMapper.save(object);
    }

    @Override
    public void delete(Report object) {
        this.entityMapper.delete(object);
    }

    @Override
    protected Class<Report> getRepositoryClass() {
        return Report.class;
    }
}

My question is: is there a point of having the interface at all? Could I simply define both the save and delete methods as abstract methods in the abstract class itself, and eliminate the interface.

I've also realised while typing this up that I could just move both the save and delete implementations into the abstract itself since they do the same thing. But in a case where they each implemented the methods differnetly, is there any benefit to having the interface at all?

Why could I not always use an abstract class over an interface, since it allows you to provide abstract methods that must be implemented in subclasses (like an interface), but then also allows you to defining common code implementations between them (which interfaces do not allow)?

EDIT

In response to the post being tagged as a possible duplicate, the other post doesn't really clarify anything for me, specifically in the case of if an abstract class should implement an interface, and the pros/cons of doing so

4

1 Answer 1

4

First, let's rename the repository and interface, and then we can talk about why creating an additional layer of abstraction (the interface) is beneficial.

The CassandraRepository<T> interface has a name problem. It has the word "Cassandra" in it, which ties it to a database vendor.

Let's rename this to simply Repository<T> instead.

public interface Repository<T>
{
    void save(T object);
    void delete(T object);
}

Now, the AbstractGenericRepository<T> class has a naming problem too. This is where you should begin coupling your repository classes to a database vendor.

Let's rename this to CassandraRepository<T> and have it implement the interface:

abstract class CassandraRepository<T> implements Repository<T>
{
    private final Session sesion;
    Mapper<T> entityMapper;

    CassandraRepository(final Session session) {
        this.session = session;
        final MappingManager manager = new MappingManager(this.session);
        this.entityMapper = manager.mapper(getRepositoryClass());
    }

    protected abstract Class<T> getRepositoryClass();
}

The ReportRepository class also has a naming problem. It is tied to a database vendor, yet you need specific methods for reports. Rename this class to CassandraReportRepository and have it implement a new interface: `ReportRepository':

public class CassandraReportRepository extends CassandraRepository<Report> implements ReportRepository
{
    ...
}

public interface ReportRepository extends Repository<Report>
{
    ReportResult run(Report reportToRun);
}

The CassandraReportRepository inherits from an abstract class coupled to a particular database vendor, and this is apparent through how these things are named.

This next bit is why you define an interface for an abstract class:

public class ReportService
{
    private final ReportRepository  repository;

    public ReportService(ReportRepository repository) {
        this.repository = repository;
    }

    // Methods that use this.repository
}

The ReportService has a dependency on the ReportRepository interface, not the database vendor specific abstract class (or even the database vendor specific concrete class. This allows you to isolate this service class in a unit test, pass in a mock report repository, and test the behavior of the service class by itself.

This is one of the main benefits of defining an interface for an abstract class. Loose coupling lends itself to easily testable code. If you decide to switch database vendors, then code should only need to be refactored within your data access layer.

You can define a new class called, say, MongoDbRepository<T>. You would create a new class called MongoDbReportRepository that implements the ReportRepository interface. Since the ReportRepository interface did not change, any object that depends on this interface will also not need to change.

8
  • Thanks, this is really helpful and clarifies a lot of my questions. I'm unsure about one thing though. In your ReportService class, you declare the type of repository as the interface type, for reasons you explained. But how does it know which concrete to use at runtime then? Where is ReportRepository actually used? Is that the object that will passed into the ReportService constructor?
    – Eoin
    Apr 17, 2019 at 12:27
  • 1
    @Eoin: Usually a dependency injection container is configured to serve a specific concrete instance for an interface, otherwise the code that creates an instance of the ReportService needs to do this. If you aren't using a DI container, then creating a factory class to return your repositories typed to their interfaces is a good solution. Apr 17, 2019 at 12:36
  • So if we were doing this in Spring, in the class that callsReportService, say a controller class, we'd autowire in something like private ReportRepository reportRepository, and pass that into the constructor? Is that correct?
    – Eoin
    Apr 17, 2019 at 18:20
  • Almost correct. You need to define a more concrete interface for ReportRepository. I can update my answer. Apr 17, 2019 at 19:58
  • @Eoin: I updated my answer to address your question. Apr 17, 2019 at 20:05

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.