Background

One major purpose of an application server is to host applications. Essentially, applications provide services to other applications hosted on the same application server, to remote clients or remote servers.

Note: On the bottom line, services are nothing else than well definied APIs, expressed by well known Classes and Interfaces.

The next diagram shows a simple use of such a service:
diagram

The major issue with a service-based concept is how a caller actually “finds” a service-instance to invoke. Services are supposed to be managed by the application server: Configuration, instantiation, monitoring etc. Furthermore, a loose coupling between the service-caller and the callee is desired to keep the dependencies to a minumum. There are two basic concepts to solve this:

  • Using a Service-Locator
  • Inversion of control (IOC) by some sort of dependency injection

Note that IOC is often also referred to as Hollywood principle: “don’t call us, we’ll call you.”

Dependency injection means, that a module (code) that requires some sort of service just “states” somehow, that it requires some service and the service access itself is “injected” into the module. The dependency on some service may be expressed by having special constructors, adding special interfaces, having special setter-methods or using java annotations.

All strategies allow the container of the module (the application server) to detect dependencies and initialize, provide and/or add whatever is required to give the module access to the required services.

For example, a setter-injection may look like:

setDatabaseService(DatabaseService dbService) {
      this.dbService = dbService;
  }

During module initialization, the container recognizes, that it must provide a DatabaseService setup through some sort of configuration and calls this setter.

A service-locator, follows a more standard approach. Services have to be requested explicitly by code by calling some globally known instance, e.g.:

DatabaseService dbService = ServiceLocator.getService(...);

It is a some sort of philosophical question, which approach to follow. As Martin Fowler writes:

“Inversion of control is a common feature of frameworks, but it’s something that comes at a price. It tends to be hard to understand and leads to problems when you are trying to debug. So on the whole I prefer to avoid it unless I need it. This isn’t to say it’s a bad thing, just that I think it needs to justify itself over the more straightforward alternative.”

“When building application classes the two are roughly equivalent, but I think Service Locator has a slight edge due to its more straightforward behavior. However if you are building classes to used in multiple applications then Dependency Injection is a better choice.”
To our opinion, service-locators are more straight-forward. The service dependency is explicitly visible and hence better to understand and debug and it’s possible to use services not just in container managed modules but also in e.g. low level utilities.

Call Sequence

The censhare application-server uses a dynamic service-locator (singleton design pattern) as central entry point to provide access to all known services. Since services are defined by java interfaces, consistently interfaces are used as key to request a desired service:

DatabaseService dbService = ServiceLocator.getService(DatabaseService.class);
   dbService.doSomething(...);

As welcome side effect, the given interface class allows to return the service with the requested interface type using java generics.

diagram2

This simple concept is extended to cover some additional requirements:

Service implementations may reside in their own class loader which prevents access from a module loaded by a different class loader.

  • It should be possible to monitor service calls
  • It should be allowed to call services asynchronously
  • Services may be located on a remote server

To cope with that, callers don’t access services directly but through proxies which mediate between caller and callee.

diagram3

Actually the Proxy uses another indirection to allow for remote and asynchronous calls. For each call, a ServiceCall instance is created that houses the call parameters and call target (service key of the requested service).

The ServiceCall is executed asynchronously using an execution-service which uses a queue and pool of threads (command pattern). ServiceCalls may be serialized to call a remote service.

diagram4

Call Activity

The following diagram shows how a request to access a service is resolved into a proxy for the callee:

diagram5

For each service invocation (method call), the invoke method of the Proxy-InvocationHandler is responsible (see http://java.sun.com/j2se/1.5.0//download/attachments/33755916/guide/reflection/proxy.html).

The implementation creates a ServiceCall instance that encapsulates the call arguments and a reference to the requested service. The ServiceCall submits itself to an Executor through a PriorityQueue which performs the actual method call.

diagram6

If the Executor has a free thread available, it takes the ServiceCall from the queue and executes it.

Note: If the working thread of the callee is already one of the Executor threads, a synchronous call is executed directly without asking for another Executor thread to prevent eating up threads just for syncing.

The next diagram fives a more detailed view:

diagram7

Note that execution is tried within a loop (if an execution monitor is setup). For each pass, the service availability is checked using annotations of the method.

Annotations

Interface annotations

Name

Description

isLocalOnlyThe service class can only be used locally and it is not capable to serve as a remote service, usually because call arguments or return values are not serializable.

Method annotations

Name

Description

executeDirectThis method is called synchronously. It is used to optimize calls of simple methods that do not block and where there is no access limit and queuing would be too much overhead.
executeLocalThis method should not be called from a remote server
checkAvailablilityExecute this method only if the service is available (which implies that the services this service depends on, are available as well).
checkDependsAvailablilityExecute this method only if the services this services depends on are available (e.g. used for an “open” service call).
checkForeignDependsExecute this method only if no other services depend on this service (e.g. used for a “close” service call).