Anzo Service Container
Motivation
The current method for architecting Anzo-systems presents two challenges to Openanzo providing a next-generation Semantic SOA platform.
- Specifying Anzo Semantic Service endpoints requires code rather than configuration
- No mechanism exists for introducing new Semantic Services to the Combus and Registry
The Anzo StandaloneServer represents only a single example of how Anzo components may be wired together in order to build a service endpoint. StandaloneServer has ModelService, a ReplicationService, a NotificaitonService, an AuthenticationService an Atom Publishing Protocol endpoint, a SPARQL endpoint, a BayeuxJMSBridge, as well as a context for serving static content and javascript libraries. However, the configuration of these components, and the way they interact is fully hard coded, and non-reusable. The ability to produce customized Anzo servers, as well as the ability to plug new Semantic Middleware services into the Anzo Communication Bus and Registry is locked up by the current design.
The goal then, of this document is to design a ServiceContainer with the following properties:
- A service is a self-contained component
- For each service, the container must provide for the decoupling of front-end connectivity, common implementation, swappable implementation, and datasource connectivity.
- For a given service, the container must be able to manage multiple core implementations.
- The container should manage front-end connectivity endpoints (Combus, HTTP, Web Services) and automatically bind these endpoints to services that require them
- The container must manage datasource connectivity (JDBC,LDAP), and supply connection objects to the core implementations.
- The container must provide a bidirectional interface so that services may advertise capabilities to a central registry, as well as store and retrieve configuration from the central registry. The centrality of the registry is in fact a configuration decision. The registry will itself be a service implemented in a ServiceContainer.
- The container must provide a mechanism for services to depend on one another, as well as specify a particular instance for that dependency.
Here are a couple example use cases
- StandaloneServer contains a ModelService using NodeCentricModelService implementation, connection to an RDBNodeCentric datasource configured to use DB2. The ModelService also exposes itself over JMS and REST
- An HttpJMS bridge that exposes the standard Anzo services over Http, but proxies into the Combus itself.
- A general purpose server that exposes non-semantic stores as RDF via special ModelService datasources and a front-end connection to the Combus
Dependencies between components are used to wire components together and can be cyclic in nature. The dependencies defined in no way affect the initialization order of the ServiceContainer, they serve as a means to tell the service container which other components a component will ask for during its life-cycle. By defining the dependencies within the configuration, one is able to define which instances of a dependent component a component while receive when it asks the service container for a reference.
Architecture
To illustrate, at the most basic level, how the ServiceContainer works, one can look at the example trig files containing the configuration data.
Core Constructs
org.openanzo.server.container.ServiceContainer
The ServiceContainer is the main entry point for the server. It is in charge of life-cycle management for components within the container as well as loading the configuration needed to wire the components together. The startup of the ServiceContainer is a multi-phase process.
- 1.) The first stage is to load the configuration data into a graph, or use an already populated graph.
- 2.) Each of the components defined in the configuration are instantiated and placed in an ordered list, based on whether or not an initOrder predicate was defined for the component.
- 3.) Each of the components are initialized with the initialized method in the above defined order. This is where components should create references to the other services to which they depend. Since the other components may or may not be themselves initialized yet, no methods should be called on these referenced components yet.
- 4.) Once all the components are initialized, start is called on them in the initOrder defined in the configuration.
- 5.) At this point the ServiceContainer should be up and running.
- 6.) When the server is stopped, stop is called on each of the components in the reverse initOrder.
The ServiceContainer has methods such as getComponent() which take a Class object as a parameter and an optional instance URI. The container will try to find a component that fulfills the request. There are 2 versions of these methods, a singleton version that returns the first matching component, and a collection version that returns a collection of components that fulfill the request.
The components have a getDependency() method which will attempt to load a component that fulfills one of the components defined dependencies. If a dependency cannot be met within the ServiceContainer, and exception is thrown.
org.openanzo.server.container.IContainerComponent
Base interface for all components the service container uses that allows for life cycle management of components.
org.openanzo.server.container.IOperationContext
The IOperationContext is an object that is passed through the stack during the life of a call. These context objects are used to store attributes that the different layers of the call stack need as the call makes it way from the endpoint, threw the service endpoint, to the service, and finally to a backend. The 2 core attributes that all context objects contain is an ID, such as the JMSCorrelation ID, and the calling user's authenticated principal object. Layers such as the NodeCentric? services wrap the endpoint's context object with a NodeCentricOperationContext? in order to provide attributes like the JDBC connection and other objects related to the RDB layer. By having this context object as a call makes it way through the stack, it is possible to include context information in debug/trace statements such that it is possible to follow a call through the stack. It would also be possible to add in statistics on a per call basis, ie how long it took for a call to make its way through the stack. Some of the core information contained within the context is also placed within the logging MDC thread object, which allows for things like the operationID, user and operation to be reflected in any logging event.
Non Server Uses
Within the client, a ServiceContainer is used to manage the services that proxy between the AnzoClient? class and the server's services. The services that are used on the client can either be proxy type services, where the service is is acting as a proxy between the caller and the service on the server, or embedded services, where the services are the same classes that run on the server. This is the same as the old way of using classes like JMSModelService vs EmbeddedModelService?. Now instead of defining the classes to use for a service within the properties file, ie JMSModelService, the type of container is defined, ie JMS,REST,WS or EMBEDDED. These constants are used to load an rdf file that contains the configuration details for the new ServiceContainer.


