Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Service Provider Interface without the Provider

I am reading Bloch's Effective java book[1] and came across the following example of SPI:

//Service interface
public interface Service {
  //Service specific methods here
}

//Service provider interface
public interface Provider {
  Service newService();
}

//Class for service registration and access
public class Services {
  private Services(){}

  private static final Map<String, Provider> providers =
    new ConcurrentHashMap<String, Provider>();
  public static final String DEFAULT_PROVIDER_NAME = "<def>";

  //Registration
  public static void registerDefaultProvider(Provider p) {
    registerProvider(DEFAULT_PROVIDER_NAME, p);
  }
  public static void registerProvider(String name, Provider p) {
    providers.put(name, p);
  }

  //Access
  public static Service newInstance() {
    return newInstance(DEFAULT_PROVIDER_NAME);
  }
  public static Service newInstance(String name) {
     // you get the point..lookup in the map the provider by name
     // and return provider.newService();
  }

This my question: why is the Provider interface necessary? Couldn't we have just as easily registered the Service(s) themselves - e.g. maintain a map of the Service implementations and then return the instance when looked up? Why the extra layer of abstraction?

Perhaps this example is just too generic - any "better" example to illustrate the point would be great too.


[1]Second edition, Chapter 2. The first edition example does not refer to the Service Provider Interfaces.

like image 445
oym Avatar asked Jul 07 '12 16:07

oym


People also ask

What is an SPI VS API?

API stands for Application Programming Interface, where API is a means for accessing a service / function provided by some kind of software or a platform. SPI stands for Service Provider Interface, where SPI is way to inject, extend or alter the behavior for software or a platform.

What is SPI pattern?

Service provider interface (SPI) is an API intended to be implemented or extended by a third party. It can be used to enable framework extension and replaceable components.

What is a provider interface in Java?

Service Provider Interface: A set of interfaces or abstract classes that a service defines. It represents the classes and methods available to your application. Service Provider: Called also Provider , is a specific implementation of a service.

What does SPI stand for software?

In a computer, a serial peripheral interface (SPI) is an interface that enables the serial (one bit at a time) exchange of data between two devices, one called a master and the other called a slave . An SPI operates in full duplex mode.


2 Answers

Why is the Provider interface necessary? Couldn't we have just as easily registered the Service(s) themselves - e.g. maintain a map of the Service implementations and then return the instance when looked up?

As others have stated, the purpose of a Provider is to have an AbstractFactory that can make Service instances. You don't always want to keep a reference to all the Service implementations because they might be short lived and/or might not be reusable after they have been executed.

But what is the purpose of the provider and how can you use a "provider registration API" if you don't have a provider

One of the most powerful reasons to have a Provider interface is so you DON'T need to have an implementation at compile time. Users of your API can add their own implementations later.

Let's use JDBC as an example like Ajay used in another answer but let's take it a little further:

There are many different types of Databases and database vendors who all have slightly different ways of managing and implementing databases (and perhaps how to query them). The creators of Java can't possibly create implementations of all these different possible ways for many reasons:

  • When Java was first written, many of these database companies or systems didn't exist yet.
  • Not all these database vendors are open source so the creators of Java couldn't know how to communicate with them even if they wanted to.
  • Users might want to write their own custom database

So how do you solve this? By using a Service Provider.

  • The Driver interface is the Provider. It provides methods for interacting with a particular vendor's databases. One of the methods in Driver is a factory method to make a Connection instance(which is the Service) to the database given a url and other properties (like user name and password etc).

Each Database vendor writes their own Driver implementation for how to communicate with their own database system. These aren't included in the JDK; you must go to the company websites or some other code repository and download them as a separate jar.

To use these drivers, you must add the jar to your classpath and then use the JDK DriverManager class to register the driver.

  • The DriverManager class is the Service Registration.

The DriverManager class has a method registerDriver(Driver) that is used to register a Driver instance in the Service Registration so it can be used. By convention, most Driver implementations register at class loading time so all you have to do in your code is write

Class.forname("foo.bar.Driver"); 

To register the Driver for vendor "foo.bar" (assuming you have the jar with that class in your classpath.)

Once the Database Drivers are registered, you can get a Service implementation instance that is connected to your database.

For example, if you had a mysql database on your local machine named "test" and you had a user account with username "monty" and password "greatsqldb" then you can create a Service implementation like this :

Connection conn =
   DriverManager.getConnection("jdbc:mysql://localhost/test?" +
                               "user=monty&password=greatsqldb");

The DriverManager class sees the String you passed in and finds the registered driver that can understand what that means. (This is actually done using the Chain of Responsibility Pattern by going through all the registered Drivers and invoking their Driver.acceptsUrl(Stirng) method until the url gets accepted)

Notice that there is no mysql specific code in the JDK. All you had to do is register a Driver of some vendor and then pass a properly formatted String to the Service Provider. If we later decide to use a different database vendor (like oracle or sybase) then we just swap jars and modify the our connection string. The code in the DriverManager does not change.

Why didn't we just make a connection once and keep it around? Why do we need the Service?

We might want connect/disconnect after each operation. Or we might want to keep the connection around longer. Having the Service allows us to create new connections whenever we want and does not preclude us from keeping a reference to it to re-use later.

This is a very powerful concept and is used by frameworks to allow many possible permutations and extensions without cluttering the core codebase.

EDIT

Working with multiple Providers and Providers that provide multiple Services:

There is nothing stopping you from having multiple Providers. You can connect to multiple databases created using different database vendor software at the same time. You can also connect to multiple databases produced by the same vendor at the same time.

Multiple services - Some Providers may even provide different Service implementations depending on the connect url. For example, H2 can create both file system based or in-memeory based databases. The way to tell H2 which one you want to use is a different url format. I haven't looked at the H2 code, but I assume the file based and the in memory based are different Service implementations.

Why doesn't the DriverManager just manage Connections and Oracle could implement the OracleConnectionWrapper? No providers!

That would also require you to know that you have an Oracle connection. That is very tight coupling and I would have to change lots of code if I ever changed vendors.

The Service Registration just takes a String. Remember that it uses the chain of Responsiblity to find the first registered Provider that knows how to handle the url. the application can be vendor neutral, and it can get the connection url and Driver class name from a property file. That way I don't have to recompile my code if I change vendors. However, if I hardcoded refernences to "OracleConnectionWrapper" and then I changed vendors, I would have to rewrite portions of my code and then recompile.

There is nothing preventing someone from supporting multiple database vendor url formats if they want. So I can make a GenericDriver that could handle mysql and oracle if I wanted to.

like image 92
dkatzel Avatar answered Oct 10 '22 11:10

dkatzel


If you might need more than one service of each type, you can't just reuse the old Services. (Additionally, tests and the like might want to create fresh services for each test, rather than reusing services that might have been modified or updated by previous tests.)

like image 38
Louis Wasserman Avatar answered Oct 10 '22 09:10

Louis Wasserman