Pattern 4 - Optional dependencies using services

While reading about optional module dependencies, a thought might have occurred to you--how about using services? We've learned in Chapter 7, Introducing Services, about how using services in Java modules provides a flexible and loosely coupled way of having modules work with each other. With services, you don't have to specify a readability relationship with requires. Modules that provide services are optional, not just in runtime, but also during compile time! So aren't services already better than optional dependencies?

The simple answer is yes. Services are definitely the preferred way of decoupling modules and removing hard dependencies. This is what we'll examine in this pattern. However, they do have a problem and won't work as well as you'd imagine. Let's explore why.

Here's how it works:

When using services, you typically achieve abstraction by creating two types--the interface (which is the service) and the implementations of the interface (which are the providers of the service). You don't have to do this, of course. You can have a Java class itself be the service type. But what we are discussing now applies to that too.

Given two modules A and B, if you'd like module A to optionally depend on B, you could use the previous pattern and use requires static B in the module definition of A. However, if you'd like to use services, you'll need to assign one or more Java interfaces or classes as service types. The module A needs to specify that it uses these types:

    module A { 
      uses <service-type>; 
    } 

And module B needs to provide the services:

    module B { 
      provides <service-type> with <implementation-type>; 
    } 

Now, B is technically optional. The application can run irrespective of whether module B exists in the module path or not! Seems simple enough, doesn't it? But there's a catch! Which module should the service type reside in? Is it module A or module B? It cannot be module B, because in that case, module A would need to require module B to access the service type in B, which defeats the whole purpose of making B optional! It could reside in module A, but now module B should depend on module A to access the service type. Thus module B requires A and module A exports the package containing the service type.

But wait! Our original goal was to make module A optionally depend on B. What we've ended up with now is module B depending on A! That seems the wrong way around, but if you think about the service dynamic, A is still using the implementation provided by B, and B is only dependent on A to get the service type. It is still confusing and it's not obvious what's going on just by looking at the module definitions. One way to solve the problem is to move the service types to a third module C which is required by both A and B. Now both A and B have access to the service types, and thus, are fully decoupled. This option might not always be feasible, and it is awkward to have a separate module just to solve this problem. But when it is possible, this approach of using services is one of the best ways to achieve flexibility and the drop-anything-you-need mechanism that exists with some libraries and frameworks in Java 8 and earlier.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.148.103.210