OCP-friendly service layering with Dependency Injection

This is likely to be a controversial post given the complexity tradeoff. I welcome feedback on this idea. In this article I present a brief discussion of the motivation for DRY followed by concrete and consumable advice on how to achieve it using dependency injection and generics.

Code duplication can be okay, but when you find yourself or your staff consuming a lot of time re-implementing fixes across duplicated code or worse, finding the same bugs over and over again in production the opportunity to avoid that is compelling.

Imagine you have an application with a number of HTTP services (maybe RESTful, maybe aspiring to be) that operates a variety of different types of objects with different operations. However, some operations (adding notes, deleting) are common. All operations involve fetching records from the database by ID.

A typical solution to this problem in a JAX-RS context is a series of resource classes which depend of service interfaces whose implementations depend on DTOs which are retrieved using DAOs. On the surface this is a very principled software architecture with a lot going for it.

Regrettably that design doesn't do you many favors if you are concerned about the Open Closed Principle (OCP). The OCP is useful if you want to support a lot of concurrent development on a codebase. Violating it leads to release bottlenecks.

This approach was in fact the original approach and it led to a lot of classes which had to be modified frequently by different feature teams. This in turn led to a lot of merge conflicts and attending release delays. In short, it didn't support business goals particularly well.

The solution I settled on for this problem was to extract common functionality for DAOs into a single generically typed DAO interface and implementation.

abstract class LoadRecordById<K, V>
    implements Function<RecordId<K>, V> {
  private final RecordLoader recordLoader;
  @Inject LoadRecordByIdImpl(RecordLoader recordLoader) {
    this.recordLoader = recordLoader;
  }
  @Override public V apply(RecordId<K> recordId) {
    @SuppressWarnings("unchecked")
    V recordById = (V) recordLoader.loadById(
      getT(getClass()),
      recordId.longValue());
    return recordById;
  }
  private static <V> Class<V> getV(Class<?> theClass) {
    Type type = theClass.getGenericSuperclass();
    ParameterizedType paramType = (ParameterizedType) type;
    @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"})
    Class<V> tClass = (Class<V>) paramType.getActualTypeArguments()[1];
    return tClass;
  }
}

final class LoadContactById<ContactDTO, ContactDAO>
    implements Function<RecordId, ContactDAO> {
  @Inject LoadContactById(RecordLoader recordLoader) {
    super(recordLoader);
  }
}

There is an unsubtle trick in there where it obtains the V type parameter by using subclasses to evade Java's type erasure. Subclasses preserve their type parameters and this can be used to implement code polymorphic on what appears to be it's runtime type.

It would be really nice not to have to have the LoadContactById, but it is blessedly-limited boilerplate with less opportunity for error than copy/pasted DAO logic. This wasn't the original design, it was an improvement after bugs were discovered in the copy/pasta DAOs that then required laboriously copy/pasting fixes.

With this complete you can now load a ContactDAO by creating the subclass, binding it and injecting it a Function<LongId<ContactDTO>, ContactDAO>. This in itself isn't a solution because it doesn't provide a mechanism for insulating the resource classes from the record representation. So that needed to be solved as well to provide the clean layering the DTO/DAO split is intended to provide.

I solved that problem by leaning on another dependency injection abstraction I built: injection-based function composition. I describe it more fully in Composing Functions for Dependency Injection with Guice.

I used injection-based function composition to hide the DAO types from clients. This is possible because you can use it provide a Function<RecordId<ContactDTO>, ContactDTO> given a Function<RecordId<ContactDTO>, ContactDAO> and a Function<ContactDAO, ContactDAO>. We already have the Function<RecordId<ContactDTO>, ContactDAO>, we just need to the conversion.

final class ContactDAOToContactDTO
    implements Function<ContactDAO, ContactDTO> {
  @Override public ContactDTO apply(ContactDAO dao) {
     return new ContactDTO(
        dao.getGivenName(),
        dao.getSurname()
        // ...
     );
  }
}

I then bound the two together:

binder.bind(new Key<Function<RecordId<ContactDTO>, ContactDTO>(){})
  .to(new Key<ComposedFunction<RecordId<ContactDTO>, ContactDAO, ContactDTO>>(){});
binder.bind(new Key<Function<RecordId<ContactDTO>, ContactDAO>(){})
  .to(LoadContactById.class);
binder.bind(new Key<Function<ContactDAO, ContactDTO>(){})
  .to(ContactDAOToContactDTO.class);

This allows our teams to independently add new records and new functionality dependent on them. It also allows teams to independently introduce difference DTOs for the same DAO. This is a useful feature since not all features need all aspects of a DAO and some DTOs need aspects of multiple DAOs.

A quick example of such a second order function is audit logging. Say you have an audit log attached to record. Certain actions may be worthy of being entered into this audit log. This will also double as a good example of how client code uses the abstractions I've described above.

public interface Auditable {
  Function<NoteDTO, AuditEventDAO> getAuditLog();
}
final class AddToAuditLog<K,V extends Auditable>
    implements Function<RecordId<K>, Function<NoteDTO, AuditEventDTO>> {
  private final Function<RecordId<K>, V> getAuditable;
  @Inject AddToAuditLog(Function<RecordId<K>, V> getAuditable) {
    this.getAuditable = getAuditable;
  }
  @Override Function<NoteDTO, AuditEventDTO> apply(RecordId<K> recordId) {
    return getAuditable.apply(recordId).getAuditLog();
  }
}

And then provide a binding for the record type you want to audit:

binder.bind(new Key<Function<RecordId<ContactDAO>,
    Function<NoteDTO, AuditEventDTO>>>(){})
  .to(new Key<AddToAuditLog<RecordId<ContactDTO>, ContactDAO>>(){});

Now you have audit logging implementations on-demand for any record type you have that implements Auditable.

The other neat thing this pattern enables is swapping out DAOs. With a simple binding change to the ComposedFunction bindings you can swap in a different DAO implementation and not even have to recompile the rest of the code. This is useful for cases where the DAO talks to another service using a API version that is being deprecated. You can develop a DAO for the forthcoming API version and test it thoroughly before general release.

There's a lot more to this in practice and I haven't provided a complete implementation here, but I'm happy to advise your team on the specifics should the need arise.

Comments are welcome.