Part-3: JPMS/Java Modules - service concept

Beginning by Java 9 the Java Platform Module System (JMPS) provides the concept of services. It separates the service interface from its actual implementation, separation of concerns. Such implementation can be easily replaced without breaking the consumers. The module of the service interface does contain the only classes related to the usage of that particular interfaces. The actual implementation resides in the different module that implements those interfaces, service provider. The “service concept” can be separated to:

  • Service Interface
  • Service Provides ( one or more )
  • Service Consumers

The consumer module can load such service at the run-time by using ServiceLoader. The service consumer code refers only to the service interfaces. It may handle multiple service provides as well as none provider is available.

The section “Part-2: JPMS/Java Modules – descriptor, directives, types” explained the directives “uses” and “provide … with …” in more details. The following example shows the usage.

Car Factory example with engine providers

The simple idea is about to have a car factory that produces a car with engines. The engine my be producer in original or external facility, or by both.

Service Interface module: engine api

The modules exports the interfaces and related classes that are implemented, used  by the service provides, therefore the module uses only “exports” directive. The module descriptor example:

module wits.engine.api {
    exports com.wengnerits.java.modules.engine.api;
    exports com.wengnerits.java.modules.engine.api.model;

    requires wits.api;
} 

Notice that as previously mentioned the parent package does not export the sub-packages. It is required to export them explicitly. The interface example:

 
package com.wengnerits.java.modules.engine.api;

import com.wengnerits.java.modules.api.Producer;
import com.wengnerits.java.modules.engine.api.model.CarEngine;

public interface EngineProducer extends Producer<CarEngine> {
}
 
Service Provider Module: original engine factory

The implementation module “requires” the service interface module “ wits.engine.api” with related classes to implement the specified interface. The implementation module then “provides” its own implementation. The module descriptor example:

module wits.engine.original {
    requires wits.api;
    requires wits.engine.api;

    provides com.wengnerits.java.modules.engine.api.EngineProducer
            with com.wengnerits.java.modules.engine.impl.EngineProducerImpl;
} 

The example service implementation:

 
package com.wengnerits.java.modules.engine.impl;

import com.wengnerits.java.modules.engine.api.EngineProducer;
import com.wengnerits.java.modules.engine.api.model.CarEngine;

public class EngineProducerImpl implements EngineProducer {

    @Override
    public CarEngine produce(String name) {
        return new CarEngine("original:" + name);
    }
} 

Notice that all relevant classes from the “wits.engine.api” module are used that the module can provide proper implementation of the the service.

Service Consumer module: car factory

Having defined the service interface and service provider  the service consumer  “wits.factory” module can be created. The module consumer descriptor example:

module wits.factory {
    …
    requires wits.engine.api;
    requires wits.engine.original;
    …
    uses com.wengnerits.java.modules.engine.api.EngineProducer;
}
 

Notice that the implementation is decoupled from the service api. This means that the code will work when the new engine provider will be chosen without the breaking the consumer code, additional module descriptor example:

 
module wits.factory {
    …
    requires wits.engine.api;
    requires wits.engine.external;
    …
    uses com.wengnerits.java.modules.engine.api.EngineProducer;
} 

The service consumer module loads the service implementation at the run-time by using the ServiceLoader instance:

var serviceWrapper = ServiceLoader.load(CarFactory.class).findFirst();
final CarFactory service;
if (serviceWrapper.isPresent()) {
    service = serviceWrapper.get();
} else {
    throw new IllegalStateException("service not available");
} 

The ServiceLoader tries to find the first available service at the runtime. It delivers the Optional. In case that no implementation is available inside the module descriptor :

 
module wits.factory {
    …
    requires wits.engine.api;
    //Implementation has been removed!
    …
    uses com.wengnerits.java.modules.engine.api.EngineProducer;

} 

the exception is thrown as can be seen from the test code:

 
ERROR] com.wengnerits.java.modules.factory.CarFactoryTests.carFactoryTest  Time elapsed: 0.018 s  <<< ERROR!
java.lang.IllegalStateException: engine producer not available