Recipe: Spring Boot uncoupled factories

Overview

Dealing with Dependency Injection of services in factories, how many times have you seen kind of code below?

public interface MyService { ... }

@Component
public class MyServiceFirst implements MyService { ... }

@Component
public class MyServiceSecond implements MyService { ... }

@Component
public class MyFactory {

    private final MyServiceFirst myServiceFirst
    private final MyServiceSecond myServiceSecond;

    @Autowired
    public MyFactory(
        MyServiceFirst myServiceFirst, 
        MyServiceSecond myServiceSecond) {

        this.myServiceFirst = myServiceFirst;
        this.myServiceSecond = myServiceSecond;
    }

    public MyService get(String s) {
        switch (s) {
            case "first":
                return myServiceFirst;

            case "second":
                return myServiceSecond;

            default:
                throw new IllegalArgumentException();
        }
    }
}

What is wrong here? We have coupling between the factory and its dependencies. The factory class must know:

  • Supported services
  • Condition to return service

In the sample above, MyFactory has 2 injected services into 2 class attributes: myServiceFirst and myServiceSecond.

Condition to choose myServiceFirst or myServiceSecond is defined in method get, so the responsibility to decide what service will be chosen is delegated to MyFactory.

What if we want to add a new service MyServiceThird to be managed by MyFactory?

@Component
public class MyServiceThird implements MyService { ... }

@Component
public class MyFactory {  
    ...
    private final MyServiceThird myServiceThird;

    @Autowired
    public MyFactory(
        ...
        MyServiceThird myServiceThird) {

        ...
        this.myServiceThird = myServiceThird;
    }

    public MyService get(String s) {
        switch (s) {
            ...
            case "third":
                return myServiceThird;

            default:
                throw new IllegalArgumentException();
        }
    }
}

As you can see in code above, MyFactory must be updated every time a new service is added to be managed. And what if condition to return a service change? MyFactory must be updated as well.

MyFactory should not have the responsibility of knowing managed services neither condition to choose what service is wanted.

Uncoupled factory

This can be done taking advantage of spring DI mechanism. There are 2 different ways to do this

Handled uncoupling

Delegate condition to choose service to service itself instead of factory. Services will implement a method boolean supports(String s) where they will define its own condition to be chosen.

public interface MyService {  
    ...
    boolean supports(String s);
}

@Component
public class MyServiceFirst implements MyService {  
    ...
    @Override
    public boolean supports(String s) {
        return "first".equals(s);
    }
}

@Component
public class MyServiceSecond implements MyService {  
    ...
    @Override
    public boolean supports(String s) {
        return "second".equals(s);
    }
}

Then, let spring inject all services implementing MyService interface into a list. To decide what service must be chosen, just find the one supporting the current condition or throw an IllegalArgumentException if none does. Code below uses java 8 lambda expression

@Component
public class MyFactory {

    private final List<MyService> serviceList;

    @Autowired
    public MyFactory(List<MyService> serviceList) {
        this.serviceList = serviceList;
    }

    public MyService get(String s) {
        return serviceList
                .stream()
                .filter(service -> service.supports(s))
                .findFirst()
                .orElseThrow(IllegalArgumentException::new);
    }
}

As you can imagine, no changes must be done in MyFactory to add a new service MyServiceThird . If condition to choose any service changes, it will impact only to that service and not to the factory, so now we have uncoupled factory and services.

Using spring ServiceLocatorFactoryBean

Spring offers a mechanism to locate services implementing an interface by its name: ServiceLocatorFactoryBean.

If we define services having a name (condition to be chosen) as shown below:

public interface MyService { ... }

@Component("first")
public class MyServiceFirst implements MyService {  
    ...
}

@Component("second")
public class MyServiceSecond implements MyService {  
    ...
}

Just define the factory as an interface

public interface MyFactory {  
    MyService get(String name);
}

Then define a the ServiceLocatorFactoryBean bean. This can be done as a service, configuration or in spring boot main class.

@Bean
public ServiceLocatorFactoryBean slfbForMyFactory() {  
        ServiceLocatorFactoryBean slfb = new ServiceLocatorFactoryBean();
        slfb.setServiceLocatorInterface(MyFactory.class);
        return slfb;
    }

To use MyFactory inject it in a class attribute.

What method do I choose?

Handled method is more flexible. In this tutorial, the condition to choose a service is a string, but we can use an enum or a type or whatever we want.

ServiceLocatorFactoryBean method is less code, more simple but limited. Only a string (service name) can be used as the condition to choose the service.

As always, there is no best solution, it depends on current scenario, requirements and limitations.

Source code

Code for 2 mechanisms can be found in: https://github.com/SaveTheCode/springboot-factories

Handled method is located in namespace com.savethecode.factories.handled

ServiceLocatorFactoryBean method is located in namespace
com.savethecode.factories.located. The bean for ServiceLocatorFactoryBean is defined in main spring boot class FactoriesApplication

Run and test project

This is a Spring Boot 1.5.2, Gradle 3.4 project. To build and run tests just run:

./gradlew clean build

Hope you enjoyed it!