CDI Produces vs. Service Locator Pattern

998 views Asked by At

Please read my scenario bellow and tell me which approach is better in my case:

  1. CDI with producer method
  2. service locator pattern

Scenario:

  • There is an interface, for example ConfigurationManager.
  • There are three totally different implementations of it. Each of them sits in separated EAR bounded with the necessary libraries. We have MemoryConfigurationManager, FileConfigurationManager and JpaConfigurationManager.
  • There is an application (EAR). It needs to use one inmpementation of ConfigurationManager.

Maybe later, developers will create a 4th implementation of the interface and install it into application server and reconfigure the main App in order to the app uses the newest implementation of the interface. The main App always uses only one implementation of the interface and administrators can reconfigure which implementation is used.

Here I can not use simple CDI because implementations are in different EARs and the application is also in a separated ear so they have different class loaders. Simple @Inject does not work between EARs.

So I have two ways:

  • If I want to use CDI than I need to use @Produces method. It provides a way to inject objects that are not beans.
  • I can use Service Locator Pattern. This kind of lookup works fast because it has an own cache.

Of course, behind the CDI @Produces method I can use the Service Locator Pattern as well. The service URLs for the different implementations come from an external config file. If somebody creates a new implementetion of ConfigurationManager and install it into the Java EE container then he needs to put the new service url into the config file.

So which approach is better? (1) or (2)? Or there is another clear way to implement this functionality? :-)

I prefer (2).
I think CDI + Producers bring more unnecessary classes.

2

There are 2 answers

1
Jan Galinski On BEST ANSWER

I would think less about how to get the Service-Proxy, in that case both of your scenarios will work the same (as you already found out, you could even annotate your ServiceLocator with @Produces).

Think about how the class using the Service will get the proxy instance. Do you want to read the properties and locate the service in every constructor? Will you have a factory with one method per implementation? SPI-Service loading? How are you going to test the classes using the service?

In all that cases, dependency injection has a great advantage because it separates concerns and defines clear dependencies between client and implementation. My best practice: Whenever you have the choice, go for (C)DI. If you combine the ServiceLocator/PropertyReader approach with a @Produces annotation you woudn't even have more classes to write, but you gain much in return.

0
zappee On

So I have created some classes for CDI. It looks nice and works perfectly.

qualifier class with the types :

@Qualifier
@Retention(RUNTIME)
@Target( {TYPE, METHOD, FIELD, PARAMETER} )
public @interface DaoQualifier
{
    public enum DatasourceType { FILE, JPA, MEMORY }

    public DatasourceType type();
}

factory class, it returns with the proper instance of the interface:

@ApplicationScoped
public class ConfigurationReaderDaoFactory implements Serializable
{
    private static final long serialVersionUID = -7097322271912690164L;
    private static final Logger LOGGER = Logger.getLogger( ConfigurationReaderDaoFactory.class.getName() );
    private final String LOG_MESSAGE = "getting ConfigurationReaderDao ejb remote reference for %s datasource";

    @Produces
    @DaoQualifier(type = DatasourceType.MEMORY)
    public ConfigurationReaderDao getMemoryDao() throws NamingException
    {
        LOGGER.finer( String.format(LOG_MESSAGE,  DatasourceType.MEMORY) );

        String jndi = ""java:global/earName/ejbName/MemoryConfigurationReaderDao!a.b.c.ConfigurationReaderDao";";
        return (ConfigurationReaderDao) InitialContextGenerator.getInitialContext().lookup(jndi);
    }

    @Produces
    @DaoQualifier(type = DatasourceType.JPA)
    public ConfigurationReaderDao getJpaDao() throws NamingException
    {
        LOGGER.finer( String.format(LOG_MESSAGE,  DatasourceType.JPA) );

        String jndi = ""java:global/earName/ejbName/JpaConfigurationReaderDao!a.b.c.ConfigurationReaderDao";";
        return (ConfigurationReaderDao) InitialContextGenerator.getInitialContext().lookup(jndi);
    }
    ...
}

easy to use it with an @Inject annotation:

public class ConfigurationReaderService
{
    @Inject
    @DaoQualifier(type = DatasourceType.MEMORY)
    private ConfigurationReaderDao configurationReaderDaoService;

    public String getStringSystemParam(final String key)
    {
        String value = null;

        if (key != null)
        {
            value = configurationReaderDaoService.getStringSystemParam(key);
        }
    }
}

I like this solution because it is so easy to read and understand.

I only have three questions left:

(1) The last small thing what I need to figure out is: in my current implementation the type parameter is hardcoded:

@Inject
@DaoQualifier(type = DatasourceType.MEMORY)
private ConfigurationReaderDao configurationReaderDaoService;

The type parameter comes from an application config which is managed by the administrator. So somehow I need to inject the proper implementation of the interface dynamically ant it deppends on a simple value of a String variable.

I think I need to use the @Any annotation somehow.

(2) I tried to use @RequestScoped annotation at the beginning of the ConfigurationReaderDaoFactory class but it did not work. I got a WELD-001303: No active contexts for scope type javax.enterprise.context.RequestScoped exception.

(3) Why usage of CDI inject is better than use directly my ConfigurationReaderDaoFactory class in my case? If I use my factory class directly I do not need the DaoQualifier class and I do not need to figure out the dynamic inject process. Less class and code is always better :-)