Proteus Platform Services Redefining application lifecycle, messaging, and persistence

Preview:

Citation preview

S

Proteus Platform Services

Redefining application lifecycle, messaging, and persistence

Built on the Proteus Framework

Platform Services are built atop the Proteus Framework Inherent plugin architecture means application architects are free

to pick existing service implementations or write their own

Optional Use the Proteus Framework with/without platform services

Service Injection Single Proteus framework delegate provides access to interface

powered service plugins Services can be switched out on the fly in favor of a different

service implementation strategy

Platform Service Categories

Lifecycle Event Management Service intentions Project activities Custom application events

Application Configuration Intrinsic white labeling capabilities Resource declaration

Platform Service Categories

Messaging Enterprise integration

Persistence POJO Persistent Storage Project Management Virtual File System Agnostic Packaging

Theme Abstracting the physical UI from plugin developers

Service Plugins

A Proteus Platform Service plugin is a specialized plugin IServicePlugin

It extends IStandardPlugin by adding the following: void start() void stop() extends IMetadata

Service vs Standard Plugin

Unlike standard plugins, service plugins can be launched during application bootstrap Declare services in /proteus.properties on the CLASSPATH

Unlike standard plugins, service plugins provide lifecycle event notifications known as service intentions to subscribers Notification that a service is being installed, replaced or

terminated

Eventually, service plugins will be able to transfer state when one service is being replaced with another, e.g. switching themes

Message Beans

Platform services embrace POJOs as a first class citizen No framework annotations required to use a POJO No abstract classes to extend or interfaces to implement Existing classes will typically work in the framework without

modification No learning curve

A POJO is assigned the Message Bean nomenclature when It needs to be persisted It intends to participate in messaging

Message beans are used extensively by both the persistence and messaging platform services

Application Deployment

Your application is dependent only upon the core and services libraries, not the actual service providers

Optionally utilize the theme service to abstract the UI

Development Environment(Mavenized)

<dependency> <groupId>org.proteusframework</groupId> <artifactId>proteus-core</artifactId> <version>x.x.x</version></dependency>

<dependency> <groupId>org.proteusframework</groupId> <artifactId>proteus-services</artifactId> <version>x.x.x</version></dependency>

Runtime Environment

Proteus

proteus-core

proteus-services

{default-lifecycle}

{default-persistence}

{default-messaging}

Apache

Derby

Commons DBUtils

Commons Pool

Commons DBCP

Commons Collections

Application Bootstrapping

Every application that consumes Proteus Platform Services must define a /proteus.properties file on the CLASSPATH

That file must use key-value pairs to declare any bootstrapped services (different from dynamically discovered services) The minimum entries include an IAppConfigService

and an ILifecycleService

IConfigurationLoader

Diminished role in an extended application Due to the need to declare bootstrap platform

services via the CLASSPATH with /proteus.properties

Any use in an extended application is completely proprietary; ExtendedProteusApplication offers no direct support of IConfigurationLoader

Bootstrap Sequence

1. Lifecycle Service

2. Application Configuration Service

Service can declare and subscribe to lifecycle events in onDelegateInjected()

3. {Persistence, Theme} Services

Services can declare and subscribe to lifecycle events in onDelegateInjected()

Services can access the application configuration properties in onDelegateInjected()

4. {Messaging Service}

Services can declare and subscribe to lifecycle events in onDelegateInjected()

Service can access the persistence service in onDelegateInjected()

5. {Custom Services}

1. Services can declare and subscribe to lifecycle events in onDelegateInjected()

2. Service can access the persistence service in onDelegateInjected()

3. Service can access the messaging service in onDelegateInjected()

Extended Platform Delegate

IPlatformDelegate only provides access to the Core Framework, e.g. IRuntimeEngine and IStandardPlugin

IExtendedPlatformDelegate is guaranteed whenever an ExtendedProteusApplication is bootstrapped

Core framework’s delegate was expressly built for type-safe casting to more powerful delegates:

<T extends IPlatformDelegate> T adapt(Class<T> delegateClassType) throws IllegalArgumentException;

Caching Service References

In General: Avoid caching a platform service reference

Exceptions: Lifecycle and App Config Service references

All other platform services may fire service intention events and be either replaced or terminated at any time! For this reason, caching service references is evil

incarnate

S

Scoped ServicesControlling the instance count of a custom

service

Custom Service Scoping

Many services may require specific scoping E.g. an Application Singleton service would expect to be

instantiated once and only once

Scoped custom services are fully supported by the Proteus Platform Services; nothing special has to be done in the custom service plug-in to be specifically scoped Custom services may explicitly declare one or more scopes

that they do not support E.g. “I do not support application singleton”

Supported Scopes

Application Singleton A single instance of the custom service is shared across the entire scope of the application

Family Singleton A single instance of the custom service is shared across all objects that share an identical

namespace family

Namespace Singleton A single instance of the custom service is shared across all objects that share an identical

namespace family and ID

Extended Namespace Singleton A single instance of the custom service is utilized for a specific object instance, as defined

by the combination of namespace family, ID, and Reference ID

New Instance Per Request A new instance is always returned

Requesting a Scoped Instance

Only two methods support scoped custom service plugins getPlatformService(Class<T> expectedInterface,

ServiceScope scope) Application Singleton and New Instance Per Request

getPlatformService(Class<T> expectedInterface, ServiceScope scope,

INamespace namespace) Family Singleton, Namespace Singleton, and Extended Namespace Singleton

Invoking any of the other getPlatformService() methods will not result in a scoped instance; only these two methods guarantee scoping

Scoped ServicesClass Diagram

S

Lifecycle EventsNotification of Significant State Transitions

Lifecycle Events Service

Service Intention via LifecycleServiceIntentionEvent Notification that a platform service is being installed, replaced, or

terminated. Pre- and post-intention notification events are triggered

Project Activity via LifecycleActivityEvent Create, Open, Save, Save As, Close, etc. Pre- and post-activity notification events are triggered

Custom Events can be declared, managed, and triggered Event listeners must extend java.util.EventListener Events must extend java.util.EventObject

Custom Events

Built from standard Java event model java.util.EventObject java.util.EventListener

Registration of custom events can occur during or after the IAppConfigService has been bootstrapped Registration must include both the event type (EventObject), the

event listener type (EventListener), and the specific method (java.lang.reflect.Method) to invoke on the listener interface

Registration of listeners and firing of custom events can use either type-specific or anonymous method signatures

Lifecycle ServiceClass Diagram

Default Lifecycle Service

Provides complete support for lifecycle intentions, project activities, and custom events

Nothing to configure beyond declaring it in the application’s /proteus.properties

S

App ConfigurationProviding Application White Label Support

App Config Service

Defines the application name

Defines the default project directory Presumes a persistence service will be employed

Defines a default project name, e.g. ‘New Project’ Presumes a persistence service will be employed

Aggregates metadata used to configure other platform services

Lifecycle Services Listener

Every application configuration is automatically a lifecycle services listener by extending AbstractAppConfigService

Override onPlatformServiceConfiguration() to wire up the app’s configuration with service providers:

App Config Class Diagram

S

PersistenceComplete Project Management via

Message Beans

Persistence Service

May be comprised of any combination of four different Persistence Managers: Project Manager manages project creation VFS Manager manages a virtual file system Package Manager manages agnostic import/export Message Bean Manager manages persistent

POJOs

Persistence Service

Only one Primordial Persistence Service can exist at runtime Declared in /proteus.properties Must support Project Management Cannot be replaced by a different persistence service

0, 1, or many Secondary Persistence Services can exist at runtime Dynamically discoverable Any combination of Persistent Managers are supported

Just VFS Project Management and Message Bean Management etc.

Project Manager

create(projectDescriptor)

open(projectDescriptor)

delete(projectDescriptor)

save()

saveAs(projectDescriptor)

IProjectDescriptor getLastOpenedProject()

Project Manager Class Diagram

Project Descriptor

A Project Descriptor represents a single application project Built around the INamespace construct

Each project may have metadata associated with it Group Key #1

Property Key #1 – Value Property Key #2 – Value

Group Key #2 Property Key #1 – Value Property Key #2 – Value

Supports Java Property Change Listeners via ProjectProperty

Project Descriptor Class Diagram

Virtual File System

VFS is a critical component for associating file within the context of a project descriptor Multimedia and large file types belong in a file

system, not DB Can use a message bean to “point” to a VFS URL Eventually there will be a vfs:// protocol handler

FilePath is the critical construct of the VFS Represents a path within the VFS, regardless of the

physical file system root directory or where the project is stored

Virtual File SystemClass Diagram

Working with Virtual Files

Always ask the VFS manager to create a VFS file IVFSFile file = vfsManager.createFile(filePath)

No physical file created here; virtual wrapper only

Ask the IVFSFile for its actual java.io.File instance Never make any inferences from the physical path in your

code!

Use the File only for use in streams FileInputStream or FileOutputStream

For all other properties, query the IVFSFile, not the File

FilePath Tidbits

new FilePath(“/”);

Reference to the root directory of the VFS

new FilePath(“/foo/bar/test.txt”);

Reference to the a text file under /foo/bar directories

toString() on a FilePath will yield the virtual path, not the absolute path

Equality is determined from virtual path, not absolute path

Package Manager

Agnostic packaging service that bundles message bean descriptions, message bean (events) stored in the DB, all virtual files, and all data related to the project into a format defined by a visitor, not the managers Implements the GoF Visitor design pattern Different visitors can create different persistence service

agnostic file representations, e.g. XML or compressed proprietary (binary)

Agnostic packaging allows projects created under one persistence service to be opened and mined for data by a disparate persistence service

How Packaging Works

Package manager accepts an IPackagingVisitor and a java.io.File that is where the package should be stored

IPackagingVisitor executes 5 methods: void startVisit(packageFile) void visitProjectManager(projectManager) void visitVFSManager(vfsManager) void

visitMessageBeanManager(messageBeanManager) void endVisit()

Agnostic Packaging:Manager Responsibilities

Managers accept their respective visitor

Export: Visitor uses only the public API of the manager to extract all of the data required to create a package file

Import: Visitor uses only the public API of the manager to re-create all of the data required to re-build a project under a (possibly new) persistence service

Concrete visitors define the package format, not the managers!

Packaging Class Diagram

Package VisitorsClass Diagram

Message Beans

Message beans are POJOs. Period. No special annotations No intrusive base classes that need to be extended No interfaces that need to be implemented

Message beans represent significant application events that should be captured within a persistence service’s backing message store

Message beans may be saved, queried, updated, and deleted Message Bean Manager and the App Config Service make the

determination on what operations are permitted at runtime

Message Bean Mapping

Message Beans are POJOs; however, metadata about the POJO must be mapped into the persistence service in order for the POJO to truly be a message bean The morale of the story is this:

All message beans are POJOs; not all POJOs are message beans

Mapping can occur anytime during application execution Only One Requirement: mapping must occur before any

persistence or messaging service invocation is attempted with a specific POJO class

Message Bean Descriptors:Mapping a POJO

Mapped to an INamespace via the actual package name and the actual class name. Failure to follow this rule will result in undefined behaviors!

Each POJO property that is going to be recognized by either the persistence or messaging service must mapped to an IMessageBeanProperty object Yes, you can choose to map only a subset of properties!

Optionally, map the interfaces that the POJO implements The Default Persistence Service does not support interfaces; others may

Optionally, map indexes for retrieval performance hints The Default Persistence Service does not support indexes; others may

Message Bean Descriptors:Mapping a POJO

Message Bean Descriptors:DataType enum

Extended Data Types

Namespace

Namespace Array

UUID

URL

Serializable

Boolean Array

Short Array

Integer Array

Long Array

Float Array

Double Array

Date Array

DataType Considerations

IdentityType can only be mapped to a long The Default Persistence Service requires that each message

bean must have a long property mapped to DataType.IdentityType; others may not

char[] are stored as CLOBs, byte[] as BLOBs in the Default Persistence Service Size limitation is 2GB

INamespace, UUID and URL are considered first class data types

Type arrays are considered first class data types

DataType Considerations

Any properties mapped to the SerializableType must implement Serializable; they will be stored as Blobs in Default Persistence Service

XmlType is a native data type in the Default Persistence Service (Derby). The POJO must only map this to String properties

EnumType must only map to an enum property; implicitly set Default Persistence Service stores enums as int ordinals public MessageBeanBuilder mapProperty(String propertyName,

Class<? extends Enum> actualEnum)

TransientType can be mapped to any property Transients are completely ignore by the persistence and messaging services

MessageBeanBuilder

Implements the GoF Builder design pattern

Requires an INamespace in the constructor

Remember- must be the actual package and class name of the POJO!

Invoke mapProperty() for each property

Remember- DataType.EnumType is implicitly set via its own mapProperty() method

Add an optional description to the message bean, or every/any of the message bean properties

Metadata can added to the message bean, or every/any of the message bean properties

MessageBeanBuilderClass Diagram

Message Bean Properties

Strongly encouraged to accurately map properties A char cannot exceed 254 characters in length in the Default

Persistence Service; map to a String or a char[] if more length is required

Length and Precision in the Default Persistence Service are limited by the underlying message store, Apache Derby

Nullability must be defined

The initial release of the Default Persistence Service requires a long property mapped to an IdentityType

Sample Mapping

IMessageBeanDescriptor myDescriptor= new MessageBeanBuilder(package,clazz,sDescription).mapProperty("id", DataType.IdentityType, true).mapProperty("namespace", DataType.NamespaceType).mapProperty("url", DataType.URLType).mapProperty("uuid", DataType.UUIDType).mapProperty("charValue", DataType.CharType).mapProperty("xmlStanza", DataType.XmlType).mapProperty("charArray", DataType.CharArrayType, 2048).mapProperty("namespaceArray", DataType.NamespaceArrayType).mapProperty(”dataTypeEnum", DataType.class).mapProperty("person", DataType.SerializableType) .addMetadata("key1", "value1”).addMetadata("key2", "value2”).build();

Canonicalization (C14N)

To facilitate storage among various persistence service providers of extended data types, a C14NFactory has been included in the Proteus Platform Services implementation

C14N handler provides robusttoC14N() and fromC14N()

Custom persistence servicesmust utilize C14N routinesin their implementation

Derivative Converters

To facilitate consistent, rapid conversion across various persistence service providers for extended data types, a DerivativeFactory has been included in the Proteus Platform Services implementation

Derivative converters provide a single convert() method for specific target PreparedStatement DataInputStream DataOutputStream Etc.

C14N vs Derivative Converters

C14N converts an extended data type to/from a byte[] How the byte[] is persisted is beyond the scope of the

canonicalization routine

Derivative Converters focus on taking a byte[] and putting it somewhere, e.g. into a PreparedStatement or a DataOutputStream

Relies extensively on C14N routines to ensure that the extended data type is consistently represented, regardless of storage medium

Persistence Managers:Adapt, Narrow

IExtendedPlatformDelegate extDelegate = delegate.adapt(IExtendedPlatformDelegate.class);

IPersistenceService persistService = extDelegate.getPersistenceService();

IMessageBeanManager mbMgr = persistService.narrow(IMessageBeanManager.class);

Message Bean Registrarion

try{ mbMgr.queueMessageBeanRegistration(primitiveDescriptor); mbMgr.queueMessageBeanRegistration(temporalDescriptor); mbMgr.queueMessageBeanRegistration(extendedDescriptor);} catch (UnsupportedMessageBeanException e){ e.printStackTrace();}

mbMgr.processRegistrationQueue();

Message Bean Manager

Used to queue and process IMessageBeanDescriptor instances that represent application POJOs

Used to save, query, update, and delete message beans (events) from the message store

Contains metadata describing what the underlying message store supports Indices Interfaces Data types Expression support

Parameterized queries

Message Bean ManagerClass Diagram

CRUD Operations

Use the saveMessage() method to INSERT a message bean into the backing message store

Use either listMessages() or an executeQuery() variant to SELECT data from the backing message store For large result sets, avoid using listMessages() and instead

rely on the createIterator() method to conserve heap space

Use updateMessage() to UPDATE to the backing message store

Use deleteMessage() to DELETE from the backing message store

Expression Grammar

Applies to both the persistence and messaging services

Grammar is for all intents and purposes a subset of SQL92 WHERE clause No support for subqueries No joins No GROUP BY or HAVING clauses may be attached

Grammar is used to define either a persistence query (WHERE clause) or messaging filter

Grammar Inspector

An IGrammarInspectorFactory can be defined via the app config in the Default Persistence Service

Factory creates an instance of IGrammarInspector that performs the actual inspection

Purpose: validates that the expression filter provided in the executeQuery() method of the message bean manager can be processed by the backing message store

Query Parameterization

The Default Persistence Service supports expression queries and parameterized expression queries Expression queries represent a WHERE clause in

executeQuery(), but with no parameter substitution support

Parameterized queries represent a WHERE clause in executeQuery() with parameter substitution via a ? character

Expression Queries

The Default Persistence Service follows this algorithm when processing expression or parameterized expression queries: Verifies the expression query grammar Verifies the columns in the expression exist in the

matching IMessageBeanDescriptor of the message bean

Verifies that the number of parameters in the expression match the number of parameters passed into the method

Working with Result Sets

Both listMessages() and createIterator() directly use the Java Collections Framework for returning data

The executeQuery() variants use a type-safe IResultSet construct to return data from the query

S

Messaging ServiceDefining Separation of Concerns

Message Sinks

A Message Sink is a dedicated facility for processing a singular type of message bean Sink is defined by an INamespace, a matching message bean

Class type, and an optional set of metadata properties

Message sink request operations are thread-safe First request in defines the sink Second through n request hands out the existing sink

Message sink is responsible for coupling message producers with message consumers

Message ServiceClass Diagram

Message SinkClass Diagram

Message Producers

A message sink can support a virtually unlimited number of producers

Producers can offerMessage(), letting the message sink immediately determine if it has room to accept the message or not without throwing an exception, immediately returning back to the caller

Producers can publishMessage(), forcing the thread to block until the message sink either accepts the message or throws an exception

Message Consumption Models

Message sink listener Singular listener per message sink

One or more subscribers Inherently a multi-threaded consumption model

A single message sink can only support a single consumption model

Both consumption models create a logical queue between application concerns

Both consumption models offer message filtering via an expressive grammar behind an IGrammarInspector

Message Subscriber Consumption Model

To fetch a message from the backing message sink implementation, returning immediately with a null if no message is fetchable, invoke: receiveImmediate()

To fetch a message from the backing message sink implementation, blocking for a predefined period of time until a message arrives or returning a null otherwise, invoke: receiveInterval(long milliseconds)

throws InterruptedException

To fetch a message from the backing message sink implementation, blocking for an indefinite period of time until a message arrives, invoke: receiveIndefinite()

throws InterruptedException

Producer – ConsumerClass Diagram

Message ListenerClass Diagram

Message Bean Wrapper

Every message bean that is published or consumed via a message sink is wrapped with an IMessageBean implementation

Significantly simplifies the messaging architecture without sacrificing type safety courtesy of Java generics

Each IMessageBean has direct access to the message bean’s corresponding message bean descriptor

Message BeanClass Diagram

Default Message Service

Supports either a singular message listener or multiple subscribers

Physically implemented as a Java BlockingQueue<T> ArrayBlockingQueue’s maximumCapacity can be

defined via an app config property, and passed in via metadata under the IMessageService.KEY_MAXIMUM_CAPACITY key

Default maximum capacity is 50 unless otherwise defined

mvn: Default Implementations

<dependency> <groupId>org.proteusframework</groupId> <artifactId>default-persistence</artifactId> <version>${project.version}</version></dependency>

<dependency> <groupId>org.proteusframework</groupId> <artifactId>default-lifecycle</artifactId> <version>${project.version}</version></dependency>

<dependency><groupId>org.proteusframework</groupId><artifactId>default-messaging</artifactId><version>${project.version}</version>

</dependency>