Prev Next

Service Oriented Systems Concepts

This chapter outlines how to develop for and with OSGi enRoute, which is foremost Service Oriented Systems. This chapter is intended to be pragmatic and will not try to explain why certain mechanisms work.

Concepts

Services

Service Oriented Systems are built from the lowest level from a very simple concept: a service. A service is an instance registered with a broker whose role is defined by a service contract; a contract is specified in a Java package. A contract defines a number of roles and their behavior in the collaboration scenarios supported by that service. For example, in the Event Admin specification there are three roles, the event Handler, the Event Admin provider, and the event source. The contract specifies what happens when an event is posted and what the responsibilities of the specified roles are. Some roles, but not all, are specified in Java interfaces so that their syntactic rules can be enforced by the compiler.

The broker is dynamic, services can be registered at any time and also withdrawn. Services can be found by their role as well as service properties related to their roles. Services are a type of a publish-find-bind model. In OSGi terminology you register a service (publish), you get a service (bind), and you listen to a service. Services are represented with the following symbol:

Service Legend

The purpose of a service is to abstract a well defined responsibility, including its state.

The primary reason that services work so well is that they minimize the surface where different modules touch each other. By minimizing this surface, you get fewer dependencies and thus fewer bugs. This surface is minimized because it generally only defines the collaboration API. In more traditional software development libraries provide a collaboration API that is most of the time mixed with configuration and house keeping. For example, packages mixing a factory class and a collaboration interfaces are prevalent in Java. However, life cycle and the collaboration are quite different aspects and should never be mixed in a cohesive package.

Service oriented systems do not specify an API for their own life cycle nor their configuration aspects. These aspects are regarded as internal details and should never be mixed with the collaboration API. For example, what is the collaboration model of something like sending events? In the OSGi Event Admin we have 3 roles with two very simple interfaces (the third role, the sender, does not even have an interface):

EventAdmin		– Registered by the provider to send/post events
EventHandler	– Registered by an event consumer

Besides this simplicity, there are very powerful and flexible Event Admin implementations that widely differ in their non-functional capabilities. The reason that this is possible is that the configuration and life cycle management (e.g factories) are strictly kept out of the collaboration API. In general, an Event Admin implementation defines a configuration type that is configured by the deployer. Since the consumers of Event Admin API have not clue about these internal details they tend to work in a large number of very different constellations.

The larger the API, the harder it becomes to faithfully implement that API causing the consumers of that API to lock into certain implementations (e.g. JPA). The OSGi specifications focus on highly cohesive service APIs. They can get away with this simplification because the OSGi Core Framework provides the life cycle and configuration management API.

Service Components

A service component is an object that implements a number of service roles, potentially from different services. A service component can depend on a number of other services and uses any number of implementation classes. However, a service component should only collaborate with other components through services. Service components are active, that is, they can start running without requiring a central registration. Service components can use libraries (defined later) but should never use those libraries to exchange or share state. They should also not use static variables.

Components (diagram: parallelograms) can interact with external resources.

Service Legend

Though services can be created with several alternative systems, in OSGi enRoute Service Components are based on Declarative Services. A class that wants to become a Service Component can just add an @Component annotation:

package com.acme;

@Component
public class Foo {
	@Activate void open() { System.out.println("Here I am!"); }
}

Declarative services are extremely powerful and therefore have their own introductory chapter.

Libraries

A library is a bundle without internal state and should therefore have no components or Bundle Activator. A good library provides an API to do certain tasks but all observable state is maintained in instance variables and never in statics. Libraries should be started like all bundles but will not act differently whether they are started or not because they lack state. Libraries never register or use services.

The purpose of a library is to provide convenience functions for a specific area. Libraries do not provide substitution.

Bundles

Service Components and libraries are packaged in bundles, potentially many components in one bundle. A bundle is a JAR file with Java code and general resources described by a manifest. A bundle is a module where all its code is by default private. The manifest can declare some of its packages exported. Since the module will depend on other packages, the manifest explicitly declares the packages that are needed by its contents. Exported and imported packages should preferably be restricted to service contracts and libraries.

A bundle is a reified entity in runtime with its own life cycle. When installed it will not be able to run until it is resolved. The OSGi Framework has the responsibility to wire bundles together so that the imports match corresponding exports. Before a bundle is active, none of its components may be active.

Capabilities

Computers are quite good at repetitive work but they utterly lack common sense. Any developer can tell you many war stories of how computers messed up. A big source of problem is dependency management. Almost all bugs are caused by an invalid assumption of the developer.

Statically typed languages take a cost to provide additional information about the system which allows many of those assumptions to be verified before the program is run. OSGi, already so long ago, took this a step further and required many of these dependencies to be made more explicit in the manifest header. The goal being that a bundle would not install until its assumptions would not be violated. For example, we specify the Execution Environment which allows a bundle to fail early when it was compiled against a later API.

Over time it became clear that this model did not properly scale. We were adding more and more diverse headers to express the dependencies. We therefore developed the Capabilities model. Instead of creating unique headers for each specific capability we created a language that could express any type of capability generically.

A capability consists of a name space and set of properties. Assumptions can now be expressed with a name space and a filter. A few specifications ago we mapped all the special headers (Import-Package, Require-Bundle, Fragment-Host, etc.) to this generic Require-Provide Capability model. For example, the name space osgi.wiring.package is a package capability. The property osgi.wiring.package property is set to the package name. With this definition we can provide a package:

osgi.wiring.package;osgi.wiring.package=com.acme.foo;version=1.2

And in the same vein require a capability:

osgi.wiring.package;filter:="(&(osgi.wiring.package=com.acme.foo)(&(version>=1.2)(!(version>=2))))"

Generalizing this capability language allowed us to specify repositories that describe artifacts that way. It allowed the framework to be based on a general resolver that has no knowledge of the sometimes quirky semantics of class loading. (Ok, we had to put a few warts on the capability language to support all OSGi aspects.)

When the framework resolves bundles it actually creates wires between these bundles. These wires are used by the class loading model but can also have other use. For example, OSGi enRoute Web Resources use capabilities to let Javascript and CSS dependencies be handled with annotations.

Since the language is not easy to read for humans, tools like bnd usually generate this metadata from the code and annotations.

The Capability model today provides verification tool that detects many errors before deployments. However, it has also become very useful as an assembly tool. In bnd(tools), the capability language is used to help assembling runtime components. This assemble significantly simplifies the error prone and often painful process of creating the list of used dependencies.


Prev Next