7.6 Service Autonomy
Service-orientation revolves around building flexible systems. Flexibility is, to a large degree, achieved through making services decoupled and autonomous to enable them to be composed, aggregated, and changed without affecting other parts of the system. For a service to be autonomous, the service must be as independent as possible from other services with which it interacts, both functionally and from a runtime environment perspective. Java and Java EE provide a highly efficient runtime environment that supports service composition. For example, a Java EE application server supports concurrent access to its hosted components, making each access to such a component autonomous from the others.
Well-Defined Functional Boundary
Occasionally, the functional boundary is defined by a certain business domain that a service lives within, as is the case if a service implements a particular business process or task within that domain. Alternatively, the functional boundary of a service can be described by the type of data the service operates on, such as entity services.
Translating this requirement into Java and XML schema utilizes namespaces and Java packages as structuring elements. Checking the list of imported classes and packages for a particular service implementation will help identify dependencies throughout the system and provide an indication of whether the functional boundary of the service is maintained in its implementation.
For example, an entity service called Customer delivers customer data retrieved from a variety of data sources for reuse across many business processes. The service is defined in the http://entity.services.acme.com/Customer namespace and uses com.acme.services.entity as the Java package for its implementation. The Customer service imports a package called com.acme.services.accounting, which immediately identifies that the Java service implementation contains a potentially undesired dependency on a business domain-specific piece of code. This warrants further investigation of the underlying logic and removal of the dependency.
The Customer service has a well-defined functional boundary in delivering relevant customer data to its service consumer. However, a dependency on logic specific to the Accounting service business domain naturally reduces the autonomy of the Customer service.
Runtime Environment Control
The underlying runtime influences the degree of autonomy a service can achieve. For each service to have control over its runtime environment, the environment must be partitioned to allocate dedicated resources accordingly. The JVM offers all internal code a degree of autonomy by isolating the executed code from the operating system and providing controlled access to physical resources, such as files or communication ports. Java EE application servers leverage the concept of a container in which components run.
For SOAP-based Web services, runtime control in Java can be achieved (while maintaining a high degree of autonomy) by exposing plain JavaBeans as Web services or utilizing Stateless Session EJBs. The service implementation and all other relevant artifacts, such as WSDL files, are packaged in a module, such as a JAR or WAR file, which can then be installed on an application server independent of other code running on that server.
The same is true for non-Web services or services implemented as regular EJBs. The components related to the service have individual private deployment descriptors that can be configured on the application server as independent entities.
Java EE allows for the packaging of multiple modules and multiple services in one Enterprise ARchive file. This EAR file is deployed and installed on the application server as an independent enterprise application. To increase a service’s autonomy, use of only one service packaged per EAR file is recommended. This allows each service to be treated as a completely independent unit to configure, start, stop, or replace without affecting any other services running on the same system.
To decrease the number of moving parts in the environment, however, multiple services can be packaged into one enterprise application. Co-locating services is suitable when the services interact frequently with each other in a performance-critical manner.
In JAX-RS, POJOs are more commonly used to model Web resources, although stateless and single session beans can be designated as root resource classes. The JAX-RS runtime packages the resource artifacts into a WAR or EAR file which can be deployed as a standalone module in an application server. Some JAX-RS implementations support embeddable containers, in which a JAX-RS runtime is bootstrapped from inside a driver program for testing purposes.
Developing services for reuse across multiple service consumers is a benefit of implementing service-oriented design. The considerations presented in the Agnostic Functional Contexts and Concurrent Access to Service Logic sections help illustrate the benefits of providing each service consumer exclusive access to a certain instance of the service implementation, which is true regardless of whether the service implementation is located in a JavaBean or is a stateless session EJB.
For REST services, the default lifecycle of root resource classes is per-request. A new instance of a root resource class is created every time the request URL path matches the @Path annotation of the root resource. With this model, resource class fields can be utilized without concern for multiple concurrent requests to the same resource. However, a resource class can also be annotated with the javax.inject.Singleton annotation, creating only one instance per Web application. Using the default lifecycle model for resources is recommended, unless compelling reasons arise to do otherwise.
In general, each Java component that implements a service is accessible concurrently by definition via the application server. However, installing the same service separately on different machines is also acceptable. The ultimate autonomy of a service is achieved if one instance of a service, running in its own process and controlling all of its underlying resources, serves only one specific service consumer. While inefficient from a resource utilization and maintenance perspective, requirements can dictate a service as part of a mission-critical business process which requires high performance and high transaction loads that force a dedicated instance of the service to be deployed for that specific purpose.