proteus platform services redefining application lifecycle, messaging, and persistence
TRANSCRIPT
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>