Service Provider Interface without Provider

I am reading a Bloch efficient java book [1] and came across the following SPI example:

//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 is my question: why do I need a provider interface? Could we just as easily register the Service (s) ourselves? save the implementation map of the Service, and then return the instance when searching? Why an extra layer of abstraction?

Perhaps this example is too general - any “best” example to illustrate this point would also be great.


[1] Second edition , chapter 2. The service provider interface is not mentioned in the first release example.

+6
source share
6 answers

Why is the provider interface necessary? Could we just as easily register the Service (s) ourselves? save the implementation map of the Service, and then return the instance when searching?

As others have argued, the goal of the Provider is to have an AbstractFactory that can instantiate the Service . You do not always want to refer to all implementations of the Service, because they can be short-lived and / or cannot be reused after their implementation.

But what is the purpose of the provider and how can you use the "Provider Registration API" if you do not have a provider

One of the most serious reasons to have a provider interface is that you do not need to have an implementation at compile time. Users of your API can add their own implementations later.

Let me use JDBC as an example, like Ajay used in another answer, but let's look at it a bit further:

There are many different types of databases and database providers that have slightly different ways to manage and implement databases (and possibly query them). Java creators cannot create implementations of all these different possible ways for many reasons:

  • When Java was first written, many of these companies or database systems did not yet exist.
  • Not all of these database providers are open source, so Java creators cannot know how to communicate with them, even if they want to.
  • Users can write their own personal database.

So how do you solve this? Using Service Provider .

  • The Driver interface is a Provider . It provides methods for interacting with specific vendor databases. One of the methods in Driver is the factory method to make an instance of Connection (which is Service ) for the database, taking into account the URL and other properties (such as username and password, etc.).

Each database provider writes its own implementation of Driver to communicate with its own database system. They are not included in the JDK; You must go to the company’s websites or some other code repository and upload them as a separate bank.

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

The DriverManager class has a registerDriver(Driver) method that is used to register a driver instance with the registration service so that it can be used. By convention, most Driver implementations are registered during class loading, so all you have to do in your code is write

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

Register the driver for the provider "foo.bar" (if you have a jar with this class in your class path.)

After registering the database drivers, you can get an instance of the service implementation that is connected to your database.

For example, if you had a mysql database on your local computer named "test" and you had a user account with username "monty" and password "greatsqldb", you can create a service implementation as follows:

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

The DriverManager class sees the line you went into and finds a registered driver that can understand what this means. (This is done using the Chain of Responsibility template, going through all the registered drivers and calling their Driver.acceptsUrl(Stirng) method until the URL is accepted)

Note that there is no mysql specific code in the JDK. All you had to do was register the driver of any provider, and then pass the properly formatted string to the service provider. If later we decide to use another database provider (for example, oracle or sybase), we will simply replace the banks and change our connection string. The code in DriverManager does not change.

Why have we not only once established a connection and saved it? Why do we need a service?

We may want to connect / disconnect after each operation. Or we might want to keep in touch longer. Having a service allows us to create new connections whenever we want, and does not prevent us from storing a link to it for reuse later.

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

EDIT

Working with multiple providers and providers that provide multiple Services :

Nothing prevents you from having multiple providers. You can simultaneously connect to multiple databases created using other database provider software. You can also connect to multiple databases produced by the same provider at the same time.

Several services. Some providers may even provide different implementations of the Service , depending on the connection URL. For example, H2 can create filesystem-based or memory-based databases. The way to specify the H2 you want to use is a different URL format. I did not look at the H2 code, but I believe that file-based and memory-based are different service implementations.

Why does DriverManager not only manage Connections, and can Oracle implement OracleConnectionWrapper? No providers!

It will also require you to know that you have an Oracle connection. This is a very tight connection, and I would have to change a lot of code if I changed providers.

Service Registration just takes a string. Remember that it uses chain of Responsiblity to find the first registered provider that knows how to handle the URL. the application can be a neutral provider and get the connection URL and driver class name from the properties file. This way, I do not need to recompile my code if I change the providers. However, if I rigidly linked the links to "OracleConnectionWrapper", and then I changed the providers, I would have to rewrite part of my code and then recompile.

There is nothing to stop anyone from supporting multiple database provider URL formats if they want to. Therefore, I can make a GenericDriver that could handle mysql and oracle if I wanted to.

+2
source

If you may need more than one service for each type, you cannot just reuse old services. (In addition, tests, etc. They can create new services for each test, and not reuse services that could have been modified or updated by previous tests.)

+1
source

I think the answer is listed in Effective Java along with an example.

an optional fourth component of the structure of the service provider is the interface of the service provider, which providers implement to create examples of their implementation. In the absence of a service, the supplier’s interface, sales are registered by class name and (paragraph 53).

In the case of JDBC ,
Connection acts as a service interface,
DriverManager.registerDriver is the provider registration API , DriverManager.getConnection is the service access API and
Driver is a service provider's interface .

So, as you rightly pointed out, it is not necessary to have a provider interface, but only a slightly cleaner approach.

+1
source

It looks like you can have multiple Provider for the same Service and based on a specific provider name you can get different instances of the same service. Therefore, I would say that each provider is like a factory, which creates the service accordingly.

For example, suppose the class PaymentService implements Service , and this requires a Gateway . You have PayPal and Chase gateways that work with these payment processors. Now you create PayPalProvider and ChaseProvider, each of which knows how to create the correct PaymentService instance with the correct gateway.

But I agree, it seems far-fetched.

0
source

As a synthesis of other answers (the fourth component is a text reason), I think this will limit compilation dependencies. With SPI, you have all the tools to exclude an explicit reference to an implementation:

  • The META-INF / services / directory contains files that mention available implementations of the service provider.
  • The standard ServiceLoader class allows resolving the names of available implementations and, thus, the dynamic construction [1] .

SPI was not mentioned in the first release. Perhaps this was the wrong place to include it in an article on static factories. The DriverManager mentioned in the text is a hint, but Bloch does not go deep. In a sense, the platform implements a kind of ServiceLocator pattern to reduce compilation dependencies depending on the environment. With the SPI in your abstract factory, it becomes a ServiceFactory ServiceLocator using a ServiceLoader for modularity.

You can use the ServiceLoader iterator to dynamically display an example service map.


[1] In an OSGi environment, this is a subtle operation .

0
source

Service Provider Interface without Provider

Let's see how it will look without a provider.

 //Service interface public interface Service { //Service specific methods here } //Class for service registration and access public class Services { private Services(){} private static final Map<String, Service> services = new ConcurrentHashMap<String, Service>(); public static final String DEFAULT_SERVICE_NAME = "<def>"; //Registration public static void registerDefaultService(Provider p) { registerService(DEFAULT_SERVICE_NAME, p); } public static void registerService(String name, Provider p) { services.put(name, p); } //Access public static Service getInstance() { return newInstance(DEFAULT_SERVICE_NAME); } public static Service getInstance(String name) { // you get the point..lookup in the map the service by name // and return it; } 

As you can see, you can create a service provider interface without a provider interface. #getInstance(..) subscribers will not notice the difference in the end.

Why do we need a provider?

The Provider interface is Factory Summary and Services#newInstance(String) is Factory . Both design patterns have the advantage that they separate the execution of services from the registration of services.

Principle of shared responsibility

Instead of embedding a service instance in a startup event handler that logs all services, you create one provider for each service. This makes it loosely coupled and easier to refactor, because the service and the service provider can be installed next to each other, for example, in another JAR file.

"Factory is common in toolkits and frameworks where library code must create type objects that can be subclassed by applications that use the framework." [1]

Life cycle management :

You may have understood in the above code without providers that we are registering service instances instead of a provider who may decide to create an instance of a new service instance.

This approach has some disadvantages:

1. Service instances must be created before the first service call. Lazy initialization is not possible. This will delay startup and tie resources to services that are rarely used or even never.

1b. You cannot “close” services after use, because there is no way to restore them. (With a provider, you can create a service interface so that the caller calls #close() , which informs the provider, and the provider decides to save or terminate the service instance.)

2. All callers will use the same instance of the service, so you must ensure that it is thread safe. But making it thread safe will make it slow. Otherwise, the provider may choose to create multiple instances of the service to reduce storage time.

Conclusion

The provider interface is not required, but it encapsulates the instance-specific logic for instantiating and optimizes resource allocation.

-1
source

Source: https://habr.com/ru/post/919941/


All Articles