The ID in SOLID

Machiel Keizer Groeneveld
4 min readJul 12, 2017

--

TL;DR Your code base could be more manageable if you apply the ‘I’ and ‘D’ of SOLID. The Interface Segregation and Dependency Inversion principles are a great way to keep modules clean and dependencies expressive.

SOLID

The SOLID principles have always had a great impact on the way I write and reason about code. Because the main goals of SOLID, for me, are to keep code readable and expressive, hiding details when needed and separating things so you can focus on one thing at a time. Dependencies arise when your class or function has a focussed responsibility, because focus means delegating all the rest to something else.

Delegation

Delegation should be as easy as possible. Ideally it requires little code and effort and should go something like this: “I’ve done my bit, here’s the result, you deal with it now, bye”. If I have to jump through too many hoops to get delegate something, that’s a problem. It ties my code to the other code and any change in the delegatee results in changes for my code.

Let’s take an example: I shouldn’t have to convert my data structure into something that the delegate understands. It should speak my language and not leak into my code (ooze might be a better word). In code that looks like:

//Easy delegation
myResult = {foo: “bar”}
delegatee.doSomething(myResult)
//Not easy delegation
myResult = {foo: “bar”}
delegateeObj = ServiceObj.new //I don't care about your obj!
delegateeObj.foo = myResult.foo
delegatee.doSomething(delegateeObj)

To achieve maximum ease of delegation, SOLID helps. So let’s talk about the ‘I’ and ‘D’ in SOLID.

Dependency Inversion is a funny principle. What kind of a principle is defened by negating something else? Obviously you need to know about how to do regular dependending to know what the inverse is. This is the rebellious yet enticing brain of Bob ‘Clean Code’ Martin at work.

Example

In (Java) web applications you’ll see a very familiair picture. Controller handles web request, service does the actual work. The ‘web stuff’ is separated from ‘all other stuff’. The service can in turn delegate to whatever other things, the Controller doesn’t know nor care.

Controller depends on Service Package

Nothing special so far. But let’s talk about that interface, where does is it usually placed? Inside the same package as the controller or the service?

Let’s raise the stakes a bit more: suppose the service implementation is in a separate module (e.g. jar, gem). Does the interface exist inside the controller module or in the service module?

In any run of the mill Java project, the service interface lives next to the implemenation. And the controller module depends on the service module (or package, I use package and module interchangeably).

Regular Dependency

When applying dependency inversion the dependency inverts. The interface now lives inside a separate module (or package). This coincides with the Interface Segregation principle. This interface inside this new package only specifies the methods this http-controller needs. The service in turn could implement multiple interfaces from different consumers.

From a module/package point you can see the arrows turn around:

Look at the arrow inverting!

Benefits

The benefits of dependency inversion are complete independence of the delegator and the delegatee. You don’t have to do a new release of the controller module if the service module changes or vice versa. As long as the implementation remains backwards compatible with the interface, hypothetically no integration test is needed. Dependencies are explicit and truly encapsulated, including all return types.

In a regular project the controller package would inherit all dependencies from the service because it depends on it. Why is the mysql driver on your controller classpath? It doesn’t need it! With dependency inversion the controller module is clean and has no superfluous transitive dependencies.

The Client Segregation principle has another advantage. Any method implementation in the service can be traced back to one specific consumer. So a method change or removal is a precision operation, in stead of dreading the breakage of a big pile of consumers. If one method changes this cannot affect other consumers, just the consumer that needs that change.

Summary

The benefits of using the ‘ID’ of SOLID:

  1. Clear definition of dependencies
  2. True interchangeability of implementations
  3. Fewer transitive dependencies
  4. A great way to keep large code bases manageable and expressive.

Code

This it what it would look like in code:

The controller:

package io.crystalline.web.controllerimport io.crystalline.api.customer.CustomerService;
import io.crystalline.api.customer.Customer;
public class HttpController { @Autowired
private CustomerService customerService;
@GET("/customers")
public List<Customer> getCustomers() {
return customerService.findCustomers();
}
}

Service interface:

package io.crystalline.api.customerpublic interface CustomerService {
List<Customer> findCustomers();
}

Customer interface:

package io.crystalline.api.customerpublic interface Customer {
String getName();
}

Service and Customer implementation:

package io.crystalline.service;import io.crystalline.api.customer.Customerpublic class SimpleCustomerService implements CustomerService {  @Override
public List<Customer> findCustomer() {
return Arrays.asList(new CustomerDTO("test"));
}
private static class CustomerDTO implements Customer {
private String name;
CustomerDTO(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
}
}

--

--