Services and the module system goals

Since services are a part of the Java module system, how do they align with the two goals of the module system--strong encapsulation and reliable configuration?

Let's begin with strong encapsulation. Services provide an alternative way of having types in modules interact with one another, which does not involve having to expose types to all consuming modules. The service provider packages do not have to be exported and thus they are encapsulated even from modules that read the module containing the service! At the same time, they are published as implementations of a service type, and thus can be used by modules that do not even read the service implementation module. So, in a way, the types are still encapsulated, although not in the same way as what we've seen so far.

How about reliable configuration? Since the service providers and the service consumers declare the fact that they are providing and consuming services respectively, it's easy for the runtime and the ServiceProvider API to make sure that the right consumers get the right services. However, you can easily compile a bunch of modules without any service implementations available in the module path.

For example, you can delete the packt.sort.bubblesort and packt.sort.javasort modules from your source code and compile the rest of the modules. It works! You can execute the Main module. It still works, although the ServiceProvider API does not find any service instances. We are returning null in our example, but we could easily handle this scenario by providing a default service implementation (assuming there's a default implementation called  DefaultSortImpl) in case nothing is found:

    SortUtil util = providers.map(Provider::get)
.findAny() .orElse(new DefaultSortImpl());

Why is this? When there's a module that clearly declares itself as a service consumer, why do the compiler and runtime not check if at least one service implementation is available? The reason is, it is by design. Service dependencies are meant to be optional. Remember the concept of loose coupling we started the chapter with. We want to be able to plug-and-play service consumers and providers at runtime. This works perfectly well with services.

The reliable configuration checking of the platform, however, does come into play when there's a service module that does not have some of its dependencies met. For example, let's say we have a service consumer module C. You can compile and execute this module without the presence of a service provider. In the following picture, the Java platform won't complain:

However, if you do add a provider module, you'll need to make sure it has all the dependencies met. For instance, let's say you drop in a provider module P that provides an implementation for the service that C needs. Now this provider module needs to follow all the rules for reliable configuration. If this module reads module D and module D doesn't exist as an observable module, the platform complains:

It seems strange that while the platform was perfectly fine when there was no provider module available, it complains when a provider module exists, but it doesn't have a dependency met. Why could it not ignore module P then? The answer is, again, reliable configuration. The absence of a provider module could be intentional. But if the platform finds a module that it could technically use, but cannot, because of an unmet dependency, that indicates a broken state, and thus, it errors out accordingly. Even allowing loose coupling through services, the platform is essentially trying the best it can to provide reliable configuration for us.

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

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