code
docs
tests
The Polygene™ Core API is the primary interface for client application code during the main execution phase, i.e. after the application has been activated.
Table 14. Artifact
Group ID | Artifact ID | Version |
---|---|---|
org.apache.polygene.core | org.apache.polygene.core.api | 3.0.0 |
Composition is at the heart of COP, and refers to two different levels of constructs;
In Polygene, we use the term Assembly for the second case of composition. See separate chapter.
Composition will allow library authors a new level of flexibility in how functionality is provided to client code. More on that later.
Composites should be perceived as single units, although they consist of many Java classes and instances. Some of those Java instances are not even belonging to a particular instance in runtime, but such details can and should be ignored by the developer. Developers should think of the Composite as a single concept, and not be concerned about its internal structure.
The Composite is composed by declaring the parts it forms in the Composite Type interface declaration. Technically speaking, Composite Type is the only Fragment that is required to exist. The other Fragments are optional.
There are 4 types of Fragments in Polygene;
There are one very important thing to know about Fragments;
ONLY Mixins can maintain inter-method state.
That means that Concerns, Constraints and Side Effects can not assume that Java fields between method invocations are preserved. These Fragments must rely on either Mixins or external services for additional state.
There are 4 Composite meta types. Each of these have very different characteristics and it is important to understand these, so the right meta type is used for the right purpose.
In versions of Polygene™ prior to 2.0 (then Qi4j), composite types had to extend one of these 4 meta types, but in 2.0 and later, the meta type interface is added dynamically during Assembly. We can therefor get rid of a lot of additional types, and use Polygene-free interfaces directly;
@Mixins( { BalanceCheckMixin.class } ) public interface BankAccount { Money checkBalance(); [...snip...] }
and declare it with;
public void assemble( ModuleAssembly module ) { module.entities( BankAccount.class ); }
Polygene™ promotes a conventional view of application structure, that computer science has been using for decades.
The definition is as follows;
The principle of this Structure is to assist the programmer to create well modularized applications, that are easily extended and maintained. Polygene™ will restrict access between Modules, so that code can only reach Composites and Objects in Modules (including itself) of the same or lower Layers.
Each Layer has to be declared which lower Layer(s) it uses, and it is not allowed that a lower Layer uses a higher Layer, i.e. cyclic references.
There is one and only one Application instance per Polygene™ runtime instance. But there is nothing preventing code to create additional Polygene™ Runtime instances inside the same JVM. However, these runtimes are isolated from each other.
The main purpose of the Application structure artifact is to keep everything in the same box, and allowing us to navigate the Structure. So, from a client code perspective, the Application is of no use, other than being part of bring Polygene™ to life. Polygene™ doesn’t start automatically and can be run in most environments, by requiring that the bootstrapping of Polygene™ is done by client code. We call this the Bootstrap Phase. The code in the custom bootstrapper will need to access additional Jars from the regular domain code, and we strongly recommend that you make this separation in your project as well.
Assembly is the part of the bootstrap phase where the application Structure is declared (programmatically). The Assembly will be consumed by the ApplicationBuilder, which produces an ApplicationInstance. This instance does not contain any custom objects, and is fully serializable. All the application structure has been built, all the layers and modules been wired up, and all the sub-composite structures are in place to quickly instantiate the various parts of the application.
At this point, where an ApplicationInstance exists, it is possible to initialize the application components with instances created in, data computed in or received from, the controlling bootstrap code.
Once the initialization phase is complete, the bootstrap controller will call the ApplicationInstance.activate() method to start things up.
Recap of sequence;
For really small applications, demos, testcases and so forth, it doesn’t make sense to create a elaborate Application structure. For this purpose, there is a convenient short-cut to establish a single Layer, single Module application. The full code looks like this;
SingletonAssembler polygene = new SingletonAssembler( module -> module.values( MyStuffValueComposite.class ) );
Behind the scenes of the SingletonAssembler a little bit more elaborate bootstrap sequence is happening. The code below shows what is the actual required sequence to start up Polygene.
polygene = new Energy4Java(); applicationInstance = createApplicationInstance(); activateApplication(); [...snip...] private Application createApplicationInstance() { return polygene.newApplication( applicationFactory -> applicationFactory.newApplicationAssembly( SingletonAssembler.this ) ); } private void activateApplication() throws ActivationException { try { beforeActivation( applicationInstance ); applicationInstance.activate(); } catch( Exception e ) { if( e instanceof ActivationException ) { throw ( (ActivationException) e ); } throw new ActivationException( "Could not activate application", e ); } }
In the above example we are only creating an Application with a single Layer and a single Module in that Layer. This is derived from the fact that the factory.newApplicationAssembly() method takes a single Assembler argument.
The Assembler.assemble( ModuleAssembly assembly ) method is called when the Polygene™ Runtime needs to populate the ModuleAssembly with its Composites, Objects, Services and other information.
Another standard setup is applications consisting of a small number of Layers that are directly on top of each other (with out bypassing, forking and converging Layers), you can supply a Assembler[][][], with Layer in the first index, Module in the second index and any number of Assembler instances in the last index. This will look like;
final Assembler[][][] assemblers = { { // web layer { // Customer Module customerListEditAssembler, customerEditAssembler, customerSearchAssembler }, { // Accounts Module accountsListEditAssembler, accountsEditAssembler, accountsSearchAssembler } }, { // domain layer { // Customer Module customerDomainAssembler, }, { // Accounts Module accountsDomainAssembler, } } }; Energy4Java polygene = new Energy4Java(); Application app = polygene.newApplication( factory -> factory.newApplicationAssembly( assemblers ) ); app.activate();
The array initialization feature in Java is used to give us a semi-visual idea of the actual application structure. It has been commented to highlight this further. Also note that one can pass any number of Assembler instances to each Module. This is an important aspect of subsystem creation and re-use.
Finally, we can assemble the Application by manually building up the Modules and Layers. This allow for a totally free structure, as long as the rules for no cyclic reference of the Layers are kept.
private static Energy4Java polygene; public static void main( String[] args ) throws Exception { polygene = new Energy4Java(); ApplicationDescriptor model = polygene.newApplicationModel( factory -> createAssembly( factory ) ); Application application = model.newInstance( polygene.spi() ); } private static ApplicationAssembly createAssembly( ApplicationAssemblyFactory factory ) throws AssemblyException { String applicationName = "Example Application"; ApplicationAssembly app = factory.newApplicationAssembly(); app.setName( applicationName ); LayerAssembly webLayer = createWebLayer( app ); LayerAssembly domainLayer = createDomainLayer( app ); LayerAssembly infraLayer = createInfrastructureLayer( app ); webLayer.uses( domainLayer ); webLayer.uses( infraLayer ); // Accesses the WebService domainLayer.uses( infraLayer ); // For persistence return app; } private static LayerAssembly createWebLayer( ApplicationAssembly application ) { LayerAssembly layer = application.layer( "Web Layer" ); createCustomerWebModule( layer ); return layer; } private static LayerAssembly createDomainLayer( ApplicationAssembly application ) { LayerAssembly layer = application.layer( "Domain Layer" ); createCustomerDomainModule( layer ); // : // : return layer; } private static LayerAssembly createInfrastructureLayer( ApplicationAssembly application ) throws AssemblyException { LayerAssembly layer = application.layer( "Infrastructure Layer" ); createWebServiceModule( layer ); createPersistenceModule( layer ); return layer; } private static void createCustomerWebModule( LayerAssembly layer ) { ModuleAssembly assembly = layer.module( "Customer Web Module" ); assembly.transients( CustomerViewComposite.class ); assembly.transients( CustomerEditComposite.class ); assembly.transients( CustomerListViewComposite.class ); assembly.transients( CustomerSearchComposite.class ); } private static void createCustomerDomainModule( LayerAssembly layer ) { ModuleAssembly assembly = layer.module( "Customer Domain Module" ); assembly.entities( CustomerEntity.class ); assembly.entities( CountryEntity.class ); assembly.transients( AddressComposite.class ); } private static void createWebServiceModule( LayerAssembly layer ) throws AssemblyException { ModuleAssembly assembly = layer.module( "Web Service Module" ); // Someone has created an assembler for a Jetty Web Service. JettyAssembler jetty = new JettyAssembler( 8080 ); jetty.assemble( assembly ); } private static void createPersistenceModule( LayerAssembly layer ) throws AssemblyException { ModuleAssembly assembly = layer.module( "Persistence Module" ); // Someone has created an assembler for the Neo EntityStore NeoAssembler neo = new NeoAssembler( "./neostore" ); neo.assemble( assembly ); } [...snip...] public static class CustomerViewComposite { } public static class CustomerEditComposite { } public static class CustomerListViewComposite { } public static class CustomerSearchComposite { } public static class CustomerEntity { } public static class CountryEntity { } public static class AddressComposite { } public static class JettyAssembler implements Assembler { public JettyAssembler( int port ) { } @Override public void assemble( ModuleAssembly module ) throws AssemblyException { } } public static class NeoAssembler implements Assembler { public NeoAssembler( String s ) { } @Override public void assemble( ModuleAssembly module ) throws AssemblyException { } } }
A Polygene™ Application must consist of at least one layer. More layers are common, often dividing the application along the common architectural diagrams used on whiteboards, perhaps with a UI layer at the top, followed by a service or application layer, then with a domain layer and finally some persistence layer at the bottom.
Polygene™ enforces this layering by requiring the Assembly to declare which layer uses which other layer. And ??? rules define that layers below can not locate composites in layers above. Also, defining that "Layer1 uses Layer2" and "Layer2 uses Layer3" does NOT imply that Layer1 has ??? to Layer3. If that is wanted, then it must be declared explicitly.
Modules are logical compartments to assist developers in creating and maintaining well modularized code. A Module only belongs to a single Layer, but many Modules can exist in the same Layer. Composite access is limited to;
Modules contains a lot of the Polygene™ infrastructure, which are the enforcers of these wise modularization principles.
It is not possible to modify the Modules, their resolution nor binding in any way after the application starts.
Usage of value objects is one of the most ignored and best return-on-investment the programmer can do. Values are immutable and can be compared by value instead of memory reference. Concurrency is suddenly not an issue, since either the value exists or it doesn’t, no need for synchronization. Values are typically very easy to test and very robust to refactoring.
Polygene™ defines values as a primary meta type through the ValueComposite, as we think the benefits of values are great. The ValueComposite is very light-weight compared to the EntityComposite, and its value can still be persisted as part of an EntityComposite via a Property.
The characteristics of a ValueComposite compared to other Composite meta types are;
Any service added, via the ModuleAssembly.addServices(), ModuleAssembly.services() and ModuleAssembly.importServices() methods, will have the ServiceComposite meta type added to it. In Polygene, when we speak of Services we mean instances of ServiceComposite.
Most programmers are familiar with the term "Service", and after the failure of Object Oriented Programming’s promise to encapsulate all the behavior together with the object’s state, programmers learned that the only way to deal with decoupling and re-use was to make the objects into data containers and deploy services that acted upon those data containers. Very much what functions did on structs back in the C and Pascal days.
Polygene™ will bring a lot of the behavior back to the Composite itself, but we still need Services for cross-composite functionality. The Polygene™ Service model is fairly simple, yet powerful and flexible enough to accommodate most service-oriented patterns and ability to integrate well with external systems whether they are in-JVM or remote, such as Spring, OSGi, WS-*, Rest and others.
The characteristics of a ServiceComposite compared to other Composite meta types are;
Services in Polygene™ are singletons, one instance per definition. That means that there may exist multiple instances of the same service type, but they can not be created on the fly in runtime, but has to be explicitly defined during Assembly.
By default, Services are not instantiated until they are used. This means that the ServiceComposite instance itself will not exist until someone calls a method. If a Service needs to be instantiated when the Module is activated, one need to declare/call the instantiateOnStartup() method on the ServiceDescriptor during the bootstrap.
The configuration for a service is well supported in Polygene. See the Service Configuration chapter for details.
Services are activated (injected and instantiated) either on application start-up, or upon first use. This is controlled by calling instantiateOnStartup(), this way;
@Override public void assemble( ModuleAssembly module ) throws AssemblyException { ServiceDeclaration service = module.addServices( MyDemoService.class ); service.instantiateOnStartup();
If this method is not called during assembly, the activation will occur on first service usage.
Passivation occurs when a Module is deactivated, typically because the whole application is shutting down. Passivation occurs in the reverse order of the activation, to ensure that dependent services are still available for a passivating service.
Activators can be assembled with Services to manage their activation. The easiest way is to implement the ServiceActivation interface directly in the ServiceComposite;
@Mixins( MyActivationMixin.class ) public static interface MyActivationDemoService extends ServiceComposite, ServiceActivation { } public static class MyActivationMixin implements ServiceActivation { @Override public void activateService() throws Exception { // Activation code } @Override public void passivateService() throws Exception { // Passivation code } }
The activation code can also be moved outside the composite by using the ServiceActivatorAdapter;
@Activators( MyActivator.class ) public static interface MyOtherActivationDemoService extends ServiceComposite { } public static class MyActivator extends ServiceActivatorAdapter<MyOtherActivationDemoService> { @Override public void afterActivation( ServiceReference<MyOtherActivationDemoService> activated ) throws Exception { // Activation code } @Override public void beforePassivation( ServiceReference<MyOtherActivationDemoService> passivating ) throws Exception { // Passivation code } }
Activators can be registered on Service assembly too, this way;
@Override public void assemble( ModuleAssembly module ) { module.services( MyDemoService.class ).withActivators( MyActivator.class ); }
Activators assembled with the service will get their beforeActivation
and afterActivation
methods called around the
ServiceComposite activation and their beforePassivation
and afterPassivation
around the ServiceComposite
passivation.
Member injection and constructor initialization occur during the activation. The ServiceComposite can be used from the
afterActivation
to the beforePassivation
method.
Services has an Identity, which drives the Service Configuration system and can be used to lookup a particular service instance. Services can also be arbitrarily tagged, via the ServiceDescriptor. Example;
@Override public void assemble( ModuleAssembly module ) throws AssemblyException { ServiceDeclaration service = module.addServices( MyDemoService.class ); [...snip...] service.taggedWith( "Important", "Drain" );
Tags are useful inside the application code to locate a particular service instance, in case we have many. For instance;
@Service private List<ServiceReference<MyDemoService>> services; public MyDemoService locateImportantService() { for( ServiceReference<MyDemoService> ref : services ) { ServiceTags serviceTags = ref.metaInfo( ServiceTags.class ); if( serviceTags.hasTag( "Important" ) ) { return ref.get(); } } return null; }
Configuration in Polygene™ is for Polygene™ ServiceComposite only. The Configuration is stored in a visible Entity Store and is therefor runtime modifiable and not static in properties or XML files as in most other dependency injection frameworks.
The Configuration system itself will handle all the details with interfacing with reading and writing the configuration. The normal UnitOfWork management is used, but handled internally by the configuration system.
In Polygene, Configuration are strongly typed and refactoring-friendly. Configuration is read from the entity store, but if it can not be found, then it will try to bootstrap it from the file system, with the same name as the ServiceDescriptor.identifiedBy(), which is set during Assembly and defaults to the fully qualified classname of the ServiceComposite type, followed by an extension dependent on the file type.
The following file types for default configuration is supported (listed in read priority order);
The Configuration type is simply listing the properties that are available. The standard rules on @UseDefaults and @Optional applies. Example;
public interface MailServiceConfiguration extends ConfigurationComposite { Property<String> hostName(); Property<Integer> port(); }
The default configuration read will happen if the Entity Store backing the Configuration system does not contain the identifiable configuration. That will trigger the reading attempts of the supported configuration formats. Once the configuration is parsed from the file system it is written to the Entity Store, and if the Entity Store is not ephemeral, then on the next start, any changes to the configuration will NOT be detected, and will simply be ignored.
To be able to read JSON, YAML and XML configuration, you must configure a Serialization system that supports the configuration format that you want to use.
Since the regular Value Serialization platform is used, for JSON, YAML and XML, the configuration can contain arbitrary composite types. This is not true for the Java properties file format.
It is important to remember that Configuration is not static values that are set prior to application start-up and therefor applications should not cache the values retrieved forever, but consciously know when the configuration should be re-read.
Configuration is injected via the @This injection scope. One reasonable strategy is to read the configuration on service activation, so by deactivating/reactivating a service, the user have a well-defined behavior to know how configuration changes take effect. Example;
@This private Configuration<MailServiceConfiguration> config; @Override public void sendMail( @Email String to, @MinLength( 8 ) String subject, String body ) { config.refresh(); MailServiceConfiguration conf = config.get(); String hostName = conf.hostName().get(); int port = conf.port().get(); [...snip...] }
Configuration is modifiable, and after the modifications have been made, the save() method on the Configuration type must be called. Example;
void changeExternalMailService( String hostName, int port ); [...snip...] @Override public void changeExternalMailService( String hostName, int port ) { MailServiceConfiguration conf = config.get(); conf.hostName().set( hostName ); conf.port().set( port ); config.save(); } [...snip...] } }
Entities are common in the object oriented programming world, but has never reached the stardom of Class and Object. Instead we have seen many attempts at creating Entities on top of Java, such as EJB (3 incompatible versions), Java Data Objects (JDO, 2 somewhat compatible versions), Java Persistence Architecture (JPA, 2 somewhat compatible versions), Hibernate (4+ somewhat incompatible versions) and many other less known. This seems to suggest that the topic of creating objects that survives over long periods of time is a difficult one.
Eric Evans points out in his book that Entities is a very definite and distinct concept that needs to be handled explicitly. Composite Oriented Programming in general, and Polygene™ in particular, takes this point very seriously and makes Entities a central part of the whole system. And likewise, we are convinced that it is not possible to develop domain-knowledge-rich applications without a conscious and well-defined strategy on Entities. So, instead of spending endless hours trying to get Hibernate mapping to do the right thing, we introduce a Composite meta type called EntityComposite, which all entities must derive from, and by doing so automatically become persistable, searchable, have a lifecycle and support nested undoable modifications.
The characteristics of an EntityComposite compared to other Composite meta types are;
A UnitOfWork is a bounded group of operations performed, typically on entities, where these operations are not visible to other threads until the UnitOfWork is completed. It is also possible to discard these operations, as if they were never executed.
UnitOfWork has many similarities with the Transaction concept used with RDBMSes. But since Polygene™ introduced several deviations to the common definitions of Transactions, we chose to use a different term.
There are several key characteristics of UnitOfWork;
At the moment, they are exclusively used to manipulate EntityComposite composites. All entity operations MUST be done via UnitOfWork, and in fact it is not possible to get this wrong.
UnitOfWork is associated with a thread, and can only be transferred to another thread by a relatively complex operation of pausing a UnitOfWork in one thread, then hand over the UnitOfWork to the other thread and resume it there. Don’t do it!
UnitOfWork is available from the Module, and from the Module you request either a new UnitOfWork or asking for the _current one. Current UnitOfWork means the UnitOfWork that was created earlier within the same thread. So, typically most entity manipulation code only request the current UnitOfWork and the management of creating, completing and aborting the UnitOfWork is handled by the transaction boundary, often in the so called application layer (see Layer)
Since it is very common to have all, or nearly all, methods in the transaction boundary to handle the creation and completion, possibly with retry, in the same class, module or even layer, Polygene™ provides annotations to easily declare UnitOfWork concern: @UnitOfWorkPropagation, @UnitOfWorkDiscardOn and @UnitOfWorkRetry
TransientComposite is a Composite meta type for all other cases. The main characteristics are;
There are times when Apache Polygene needs to interoperate with other systems, which does not have interfaces as their abstraction. Polygene has a notion of Objects, which are Polygene-managed classes and can still be injected with the Polygene runtime model, such as @Structure and @Service.
The characteristics of an Object compared to Composite meta types are;
Objects can be serialized and deserialized using the Serialization API, if and only
if they are used as types in Properties in Values or Entities. It depends on the
Serialization implementation on how the objects are serialized, and
what the requirements are to allow for deserialization. In general, the Spring
POJO setter/getter approach will always work, a default constructor is needed, and
to be safe, make it into java.io.Serializable
.
Polygene has a rather sophisticated dependency injection system, which is based around the ??? and ??? concepts. The dependency injection system also need help to keep the injection scopes separated. The following injection scopes exists, some more common than others;
@This
- injection of fragment from the same Composite instance.
@Structure
- injection of Structure organized types.
@Service
- injection of services.
@Uses
- injection of construction injected objects
@Invocation
- injection of parts related to the current method invocation.
@State
- injection of state of the composite instance
@AppliesTo
and AppliesToFilter
declarations.
@This
is equivalent to the this
pointer in the Java language, but refers to any part of the current
???. This can either be a declared mixin type, or if not declared will be a Private Mixin.
We can simply request the injection of any type of the composite that we belong to, such as;
@Mixins( { OrderMixin.class, ShippingMixin.class } ) public interface Order extends HasShippingInformation { : } public abstract class OrderMixin implements Order { @This private HasShippingInformation shipping; }
But we can have Private Mixin instead, where the injected mixin type will be automatically added to the composite.
@MIxins( OrderMixin.class ) public interface Order { : } public class OrderMixin implements Order { @This private DiscountRate discount;
The @Structure
injection scope is all about the types involved in the Application Structure system.
The possible types are;
Services are injected either in a number of ways, either direct, via List or via ServiceReference types. The following combinations are allowed;
@Service private MyService service; @Service private Iterable<MyService> services; @Service private ServiceReference<MyService> service; @Service private Iterable<ServiceReference<MyService>> services;
If service is not declared instantiateOnStartup
during assembly, then the service will be activated on first
method invocation, and not on injection. This means that any reflection on the injected instance, may result in
unexpected behavior.
Object and ValueComposite can be created with uses()
declarations. This allows injection of
arbitrary types to these meta types. Only type matching will occur, so for instance only one String can be injected
this way.
If a @Uses
declaration can not be satisfied from the injected objects via uses()
builder method, then
if the @Uses
injection is not @Optional
the Polygene runtime will attempt to (in this order)
If the @Uses
is @Optional
then no implict object creation will take place.
@Invocation
injection scope is all about the current method call. It is possible to inject the following types;
Method
being executed.
Iterable
of either of the above
This injection scope can inject either a StateHolder
which allows inspection of current state of the Composite,
or it can inject any declared Property, ???, ManyAssociation or
NamedAssociation.
Mixins are the state-carrying part of a Composite instance. The other Fragments can not retain state between method invocations as they are shared across Composite instances.
The Mixin Type is the interface that declares the Mixin methods. Each Mixin implementation (the classes defined in the @Mixins annotation of a Composite declaration) implements one or more methods from one or more Mixin Types.
Mixin Type can be very simple, like;
public interface BankAccount { Money checkBalance(); }
Or contain hundreds of methods, subclassed from dozens of super interfaces.
The Mixin Types of a Composite are ;
There is not a 1:1 correlation between Mixin Type and Mixin implementation. One can’t even know if there are more or less of one over the other. That is because a Mixin implementation can implement less than one, one, or more than one Mixin Type.
It is also entirely possible that multiple implementation methods exists for a Mixin Type method. The mixin method resolution algorithm will provide a deterministic behavior of which implementation of a method is chosen. The algorithm is as follows;
For each declared method of all Mixin Types of a Composite;
This means that one Mixin implementation can override a single method that a larger mixin implementation implements together with many other methods. So, just because a mixin implements MixinTypeA.method1() and has an implementation of MixinTypeA.method2(), doesn’t mean that method2() is mapped to that mixin. This is very important to remember. The Envisage tool is capable of visualizing how Mixin Type methods are mapped to implementations.
Mixins are the state holders of the composite instance. Public Mixins are the mixins that are exposed to the outside world via the CompositeType interface.
Each method in the CompositeType interface MUST be backed by a mixin class.
Mixins are declared as annotations on the composite interface.
@Mixins( SomethingMixin.class ) public interface Something {}
public class SomethingMixin implements Something { // State is allowed. public void doSomething() { // do stuff... } }
In the above sample, the SomethingMixin will be made part of the Something composite.
If we have many interfaces defining many methods, that all must be backed by a mixin implementation, we simply list all the mixins required.
@Mixins( { StartMixin.class, VehicleMixin.class } ) public interface Car extends Startable, Vehicle {}
public interface Startable { boolean start(); void stop(); }
public interface Vehicle { void turn(float angle); void accelerate(float acceleration); // more methods }
In the example above, the VehicleMixin would need to deal with all methods defined in the Vehicle interface. That interface could be very large, and could be totally independent concerns. So, instead we should use abstract mixins, which are ordinary mixins but are lacking some methods. This is simply done by declaring the class abstract.
@Mixins( { StartMixin.class, SpeedMixin.class, CrashResultMixin.class } ) public interface Car extends Startable, Vehicle {}
public interface Vehicle extends SpeedLocation, Crashable { }
public interface SpeedLocation { void turn(float angle); void accelerate(float acceleration); }
public abstract class SpeedMixin implements SpeedLocation { // state for speed public void accelerate( float acceleration ) { // logic } }
Above the SpeedMixin only implements the accelerate() method, and Polygene™ will only map that method to this mixin. The other method of the SpeedLocation interface is not satisfied as the example is written and will generate a runtime exception.
Public mixins expose their methods in the composite interface, and this is not always desirable. Polygene™ supports Private Mixins, which are only visible within the composite itself. That means that other fragments in the composite can see/use it, but it is not visible to the clients of the composite.
Private Mixins are handled automatically. When Polygene™ detects a @This
annotation referring to a type that is not defined
in the Composite interface, then that is a Private Mixin. The Mixin implementation class, however, must exist in the
list of Mixins in the @Mixins annotation. But often, the Private Mixin only list internal Property methods in the Mixin
Type, which will be satisfied by the standard PropertyMixin and hence always available.
This is particularly useful in Domain Driven Design, where you only want to expose domain methods, which are defined by the context where they are used. But the state of the Mixin should not be exposed out at all. For instance, if we have the Cargo interface like;
@Mixins( CargoMixin.class ) public interface Cargo extends EntityComposite { String origin(); String destination(); void changeDestination( String newDestination ); }
The interface is defined by its context, and not really exposing the internal state. So in the implementation we probably do something like;
public abstract class CargoMixin implements Cargo { @This private CargoState state; public String origin() { return state.origin().get(); } public String destination() { return state.destination().get(); } public void changeDestination( String newDestination ) { state.destination().set( newDestination ); } }
public interface CargoState { Property<String> origin(); Property<String> destination(); }
So, in this typical case, we don’t need to declare the Mixin for the CargoState, as it only defines Property methods, which are handled by the standard PropertyMixin always present.
Mixins, Concerns and SideEffects can either be "typed" or "generic". A Typed Mixin implementation implements one or more Mixin Type interfaces, and one or more of the methods of those interfaces. A Generic Mixin implementation implements java.lang.reflect.InvocationHandler, and can therefor be matched to any method of any interface. Typically, AppliesTo annotation is used to filter the methods that such Generic Mixin implementation is mapped against, and sometimes Generic Mixin implementations are "last resort".
Experience shows that Generic Mixin implementations are rare, and should only be used in extreme cases. They are less frequent than Generic Concern or Generic SideEffect implementations, but inside the Polygene™ API are a couple of Generic Mixin implementations that are always present to make the life of the developer easier, such as PropertyMixin, AssociationMixin, ManyAssociationMixin, NoopMixin. The first 3 are declared on the Composite and EntityComposite interfaces and automatically included if needed. They also serve as excellent example of what they can be used for.
@AppliesTo( { PropertyMixin.PropertyFilter.class } ) public final class PropertyMixin implements InvocationHandler { @State private StateHolder state; @Override public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { return state.propertyFor( method ); } /** * Filter Property methods to apply generic Property Mixin. */ public static class PropertyFilter implements AppliesToFilter { @Override public boolean appliesTo( Method method, Class<?> mixin, Class<?> compositeType, Class<?> modifierClass ) { return Property.class.isAssignableFrom( method.getReturnType() ); } } }
Other examples that we have come across;
which seems to indicate that Generic Mixin implementations are likely to be used in integration of other technologies.
Typed Mixin implementations are much preferred in general business logic, as they will be first-class citizens of the IDE as well, for navigation, find usage, refactoring and many other common tasks. This is one of the main advantages of the Polygene™ way of doing AOP compared to AspectJ et al, where "weaving" is something bolted onto an application’s classes via regular expressions and known naming conventions, which can change in an instance by a developer being unaware of which PointCuts applies to his code.
Concerns are the equivalent of "around advice" in Aspect Oriented Programming. They are chained into an invocation stack for each Mixin Type method and invoked after the Constraints have been executed. Since they are sitting "around" the Mixin implementation method, they also have a chance to modify the returned result, and even skip calling the underlying Mixin method implementation altogether.
To create a concern, you need to create a class that,
You are allowed to modify both the in-arguments as well as the returned value, including throw exceptions if that is suitable, perhaps for post condition checks.
Concerns are applied to composite types in several ways;
As mentioned above, concerns that implements the Mixin Type are called Typed Concerns. They are more common in the business domain, and can be used for many important things in the domain model, such as checking post conditions (i.e. ensure that the state in the entire composite is valid), coordinating services, handling events and much more.
Typed Concerns doesn’t have to implement all the methods in the Mixin Type. By making the class abstract and only implementing the methods of interest, Polygene™ runtime will subclass the concern (otherwise not valid for the JVM), but the generated methods will never be invoked.
In classic AOP, all advice are effectively generic. There is no type information in the advice implementation and the pointcut can be defined anywhere in the code, and the implementation uses proxy InvocationHandlers. Polygene™ supports this construct as well, and we call it Generic Concern.
Generic Concerns will be added to all methods that the AppliesToFilter evaluates to true. By default, that is all methods.
AppliesToFilters is a mechanism to limit, or direct, which methods that the concern should be added to. You have full control over this selection process, via several mechanisms.
This means that we can make the following three samples of concerns. First the generic concern that is added to the methods of the JDBC Connection class;
@AppliesTo( java.sql.Connection.class ) public class CacheConcern extends GenericConcern implements InvocationHandler {
We can also use an AppliesToFilter to define which methods should be wrapped with the concern, like this;
@AppliesTo( BusinessAppliesToFilter.class ) public class BusinessConcern extends GenericConcern implements InvocationHandler { [...snip...] public class BusinessAppliesToFilter implements AppliesToFilter { @Override public boolean appliesTo( Method method, Class<?> mixin, Class<?> compositeType, Class<?> fragmentClass ) { return true; // Some criteria for when a method is wrapped with the concern. } }
And finally an example of how to use annotations to mark indvidual methods for being wrapped by the concern.
@AppliesTo( Audited.class ) public class AuditConcern extends GenericConcern implements InvocationHandler { [...snip...] @Override public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { return null; } } [...snip...] @Retention( RetentionPolicy.RUNTIME ) @Target( { ElementType.METHOD } ) @Documented @InjectionScope public @interface Audited { }
Even if a method fulfills the requirement for the concern, if the concern is not declared for the Composite then the concern will NOT be applied.
The concerns are invoked AFTER all Constraint have been checked. The concerns are executed before the SideEffect are executed in the return path.
The order of execution is defined by the declaration order, interface hierarchy, whether the concern is generic or typed and if they are declared in the interface or declared in the Assembly.
From the perspective of incoming call, i.e. after the <core-api-constraint>> have been checked, the following rules are in place;
SideEffects have no equivalent in Aspect Oriented Programming. They are executed AFTER the method invocation, and
they are potentially concurrent with the method invocation itself. The SideEffect receives the incoming method arguments
and can query the result of the method call by accessing the next
field. SideEffects can NOT influence the method
call in any way, and both return values from the SideEffect, as well as any exceptions thrown, will be ignored.
To create a sideeffect, you need to create a class that,
You are allowed to modify both the in-arguments as well as the returned value, including throw exceptions if that is suitable, perhaps for post condition checks.
SideEffects are applied to composite types in several ways;
As mentioned above, side effects that implements the Mixin Type are called Typed SideEffects.
A Typed SideEffect doesn’t have to implement all the methods in the Mixin Type. By making the class abstract and only implementing the methods of interest, Polygene™ runtime will subclass the side effect (otherwise not valid for the JVM/compiler), but the generated methods will never be invoked.
Generic SideEffects implement the java.lang.reflect.InvocationHandler
and can potentially serve any method it is
applied to. Generic SideEffects will be added to all methods that the AppliesToFilter evaluates to true. By default,
that is all methods.
AppliesToFilters is a mechanism to limit, or direct, which methods that the concern should be added to. You have full control over this selection process, via several mechanisms.
The invocation order of SideEffects is UNDEFINED, and one MUST NOT rely on SideEffects executing in any particular order. They MAY be concurrent and outside the thread that executed the method, so the SideEffect can also not depend on the UnitOfWork that may be observed as present.
To be clear; the method call to the SideEffect is NOT its own Polygene-controlled invocation stack, and any annotations on the SideEffect methods will be ignored (or it is a bug). That means that IF the SideEffect needs a UnitOfWork it either needs to manage one explicitly or call out to a service that has the @UnitOfWorkPropagation annotation.
A little known feature is the DecoratorMixin, which allows any object to become a Mixin. This is useful when for instance the initialization of the object to act as a Mixin is complex, or maybe an instance is shared across many Composites. This functionality is only relevant in Transients, and therefor not available in other Composite meta types.
This is done by declaring the DecoratorMixin on the interface, and add the object to be used via the use() method on the TransientBuilder.
The DecoratorMixin will optimize the invocation for generic mixins, to avoid additional cost of reflection. But otherwise the DecoratorMixin is fairly simple
Let’s say that we have a model, FooModel, whose implementation is simply a POJO. Several different views shares this the same model instance, so any changes to the model will notify the views.
We start with the FooModel interface;
public interface FooModel { String getBar(); void setBar(String value); [...snip...] }
and its implementation is not really relevant for this discussion.
Each of the views looks like this;
@Mixins(View1.Mixin.class) public interface View1 { String bar(); public class Mixin implements View1 { @This FooModel model; @Override public String bar() { return model.getBar(); } } }
Note that the mixin is expecting to have the FooModel as being part of the view. This also simplies the mixin, which can for instance add and remove listeners to model updates in lifecycle methods.
But we need an implementation of the FooModel that uses the actual implementation of the FooModel. So we decorate the FooModel with the DecoratorMixin.
@Mixins(DecoratorMixin.class) public interface FooModel
The DecoratorMixin expects that the implementation is found among the "@Uses" objects, so to create a view we simply do;
public View1 createView1( FooModel model ) { TransientBuilder<View1> builder = transientBuilderFactory.newTransientBuilder( View1.class ); builder.use( model ); return builder.newInstance(); }
And there is nothing special in the assembly of this simple example;
@Override public void assemble( ModuleAssembly module ) throws AssemblyException { module.transients( View1.class ); module.transients( View2.class ); module.transients( FooModel.class ); }
This can now be validated in a small test;
@Test public void testDecoration() { FooModelImpl model = new FooModelImpl( "Init" ); View1 view1 = createView1( model ); View2 view2 = createView2( model ); assertThat( view1.bar(), equalTo( "Init" ) ); assertThat( view2.bar(), equalTo( "Init" ) ); model.setBar( "New Value" ); assertThat( view1.bar(), equalTo( "New Value" ) ); assertThat( view2.bar(), equalTo( "New Value" ) ); }
State can be serialized and deserialized using the Serialization API which is a Service API implemented by SPI and extensions.
Serialization extends Serializer, Deserializer
. See the JavaDocs for interfaces detail.
The Serialization mechanism apply to the following object types :
Nested Plain Values, EntityReferences, Identities, Arrays, Iterables, Streams, Maps, ValueComposites are supported. EntityComposites and EntityReferences are serialized as their identity string.
Plain Values can be one of :
Serialization behaviour can be tuned with options.
Every Serializer
methods can take a Serializer.Options
object that contains flags to change how some
values are serialized. See the JavaDocs for more details.
Values of unknown types and all arrays are considered as java.io.Serializable
and by so are (de)serialized to (from)
base64 encoded bytes using pure Java serialization. If it happens that the value is not Serializable or the input to
deserialize is invalid, a SerializationException
is thrown.
Methods of Serializer
allow to specify if the serialized state should contain extra type information about the
serialized value. Having type information in the serialized payload allows to keep actual ValueComposite types and by so
circumvent AmbiguousTypeException
when deserializing.
Core Runtime provides a default Serialization system based on javax.json
types.
Let’s see how it works in practice.
public interface SomeValue // (1) { Property<String> foo(); } @Override public void assemble( ModuleAssembly module ) { module.values( SomeValue.class ); // (2) [...snip...] module.defaultServices(); // (3) [...snip...] } [...snip...] public void defaultSerialization() { SomeValue someValue = someNewValueInstance(); // (4) String json = someValue.toString(); // (5) SomeValue someNewValue = valueBuilderFactory.newValueFromSerializedState( SomeValue.class, json ); // (6) [...snip...] }
Reading this first example step by step we ;
Serialization
,
ValueComposite#toString()
method to get a JSON representation of the Value,
Module#newValueFromSerializedState()
method to create a new Value instance from the JSON
state.
ValueComposite#toString()
method leverage Value Serialization and by so provide JSON based representation. The Module
API allows to create new Value instances from serialized state.
On top of that, Application assemblies can register different implementation of Serialization as Services to support more formats, see the Extensions section. Note that the default behaviour described above is overriden if a Serialization Service is visible.
Let’s see how to use the Serialization Services.
public interface SomeValue // (1) { Property<String> foo(); } @Override public void assemble( ModuleAssembly module ) { module.values( SomeValue.class ); // (2) [...snip...] new JavaxJsonSerializationAssembler().assemble( module ); // (3) [...snip...] } [...snip...] @Service private Serializer serializer; // (4) @Service private Deserializer deserializer; // (4) [...snip...] public void assembledDefaultServiceSerialization() { SomeValue someValue = someNewValueInstance(); // (5) String json = serializer.serialize( someValue ); // (6) SomeValue someNewValue = deserializer.deserialize( module, SomeValue.class, json ); // (7) [...snip...] }
In this second example, we ;
javax.json
types,
Serializer
and Deserializer
Services injected,
Serializer#serialize()
method to get a JSON representation of the Value,
Deserializer#deserialize()
method to create a new Value instance from the JSON state.
Composite Types Lookup can occurs when you explicitely lookup for a Composite by Type (ex. ServiceFinder.findService(..) methods), when you ask for an injection or when you create a new composite instance.
All theses type lookup start from a Module, are lazy, cached and obey the Polygene™ Visibility rules. Type Lookup works equally accross Composite Types with some subtle differences when it comes to Services and Entities.
When creating or injecting Objects, Transients or Values the Type Lookup does the following:
First, if Object/Transient/Value Models exactly match the given type, the closest one (Visibility then Assembly order) is returned. Multiple exact matches with the same Visibility are forbidden and result in an AmbiguousTypeException.
Second, if Object/Transient/Value Models match a type assignable to the given type, the closest one (Visibility then Assembly order) is returned. Multiple assignable matches with the same Visibility are forbidden and result in an AmbiguousTypeException.
Entity Types Lookup is splitted in two use cases famillies: Creational usecases and Non-Creational usecases.
Creational Entity Types Lookup
This Type Lookup takes place when creating new Entity instances from a UnitOfWork and behave exactly like Object/Transient/Value Types Lookups.
Non-Creational Entity Types Lookup
This Type Lookup takes place when fetching Entities from an EntityStore or writing queries using the Query API. The Type Lookup is different here to allow polymorphic use of Entities and Queries.
First difference is that this Type Lookup returns an ordered collection instead of a single match.
Returned collection contains, in order, Entity Models that:
Multiple exact matches with the same Visibility are forbidden and result in an AmbiguousTypeException.
Multiple assignable matches are allowed to enable polymorphic fetches and queries.
Service Types Lookup works as follow:
Returned collection contains, in order, ServiceReferences that:
Multiple exact matches with the same Visibility are allowed to enable polymorphic lookup/injection.
Multiple assignable matches with the same Visibility are allowed for the very same reason.
The Polygene™ platform defines an advanced Metrics SPI to capture runtime metrics of Polygene’s internals as well be used by application code (via this API) to provide production metrics for operations personnel, ensuring healthy state of the applications.
There are quite a lot of different Metrics components available, which are instantiated via factories. There is one factory for each component type, to allow for additional components to be created in the future without breaking compatibility in the existing implementations.
The MetricsProvider is a standard Polygene™ Service and simply acquired via the @Service annotation on a field or constructor argument.
@Service private MetricsProvider provider;
A Gauge is the simplest form of Metric. It is a value that the application sets, which is polled upon request. The application need to provide the implementation of the value() method. Gauges are genericized for type-safe value handling.
A Gauge can represent anything, for instance, thread pool levels, queue sizes and other resource allocations. It is useful to have separate gauges for percentage (%) and absolute numbers of the same resource. Operations are mainly interested in being alerted when threshold are reached as a percentage, as it is otherwise too many numbers to keep track of.
To create a Gauge, you do something like;
final BlockingQueue queue = new LinkedBlockingQueue( 20 ); [...snip...] MetricsGaugeFactory gaugeFactory = provider.createFactory( MetricsGaugeFactory.class ); MetricsGauge<Integer> gauge = gaugeFactory.registerGauge( "Sample Gauge", () -> queue.size() );
Often we want to track the many counters in a system. This might be "number of HTTP requests" or "filesystem access frequency". By creating a Counter metrics, it is simply a matter of calling the increment or decrement on that metric. This will track both number of counts/steps as well as the rate (per second), and many visualization platforms. such as Kibana, Graphite and Grafana, are made to handle this particular metric very well.
MetricsCounterFactory counterFactory = provider.createFactory( MetricsCounterFactory.class ); MetricsCounter counter = counterFactory.createCounter( "Sample Counter" );
Histograms is about computing the running standard deviations and variances. Please see "Accurately computing running variance" for more detailed information.
MetricsHistogramFactory histoFactory = provider.createFactory( MetricsHistogramFactory.class ); MetricsHistogram histogram = histoFactory.createHistogram( "Sample Histogram" );
The Meter is a more advanced Counter, which measures mean throughput and one-, five-, and fifteen-minute exponentially-weighted moving average throughputs.
Wikipedia has a section "Exponential moving average" in the "Moving Average" article.
MetricsMeterFactory meterFactory = provider.createFactory( MetricsMeterFactory.class ); MetricsMeter meter = meterFactory.createMeter( "Sample Meter" );
Timers capture both the length of some execution as well as rate of calls. They can be used to time method calls, or critical sections, or even HTTP requests duration and similar.
MetricsTimerFactory timerFactory = provider.createFactory( MetricsTimerFactory.class ); MetricsTimer timer = timerFactory.createTimer( "Sample Timer" );
HealthCheck is a metric to report the health of a system or component. The HealthCheck metric will be called upon regularly to report its status, which is then forwarded to the monitoring system.
MetricsHealthCheckFactory healthFactory = provider.createFactory( MetricsHealthCheckFactory.class ); MetricsHealthCheck healthCheck = healthFactory.registerHealthCheck( "Sample Healthcheck", () -> { ServiceStatus status = pingMyService(); if( status.isOk() ) return MetricsHealthCheck.Result.healthOk(); String message = status.getErrorMessage(); Exception error = status.getException(); if( error != null ) { return MetricsHealthCheck.Result.exception(message, error); } return MetricsHealthCheck.Result.unhealthy(message); } );
A lot of metrics are around the time it takes to execute something and Polygene supports this at its core.
There are currently the following possibilities available;
Before looking at the details of these, we need to point out that there are some pre-conditions for Metrics to be working. First of all, you need to install a Metrics Extensions, most likely the Codahale Metrics Extension. See your chosen extension for details on how to do that.
Once the Metrics extension is installed, you will also need a suitable backend to gather all the data out of a production plant and likewise a good front-end to view this. See your chosen Metrics Extension for this as well.
There is a TimingCaptureAllConcern, which when added to a composite will install a Timer for every method call in the composite.
The @TimingCapture
annotation can be placed on any method of the composite, to indicate that
a Timer is wanted on that method.
Example;
public interface Router { @TimingCapture List<Coordinate> route( String source, String destination ); } public class RouterAlgorithm1 implements Router { @Override public List<Coordinate> route( String source, String destination ) { [...snip...] } } public class RouterAlgorithm2 implements Router { @Override public List<Coordinate> route( String source, String destination ) { [...snip...] } [...snip...] @Override public void assemble( ModuleAssembly module ) throws AssemblyException { module.addServices( Router.class ).identifiedBy( "router1" ).withMixins( RouterAlgorithm1.class ); module.addServices( Router.class ).identifiedBy( "router2" ).withMixins( RouterAlgorithm2.class ); [...snip...] } } }
It is valid to annotate either the composite interface methods or the mixin implementation methods. Any of the method declarations should work. From the testcases we have the following example;
public interface Country extends TransientComposite { @Optional Property<String> name(); void updateName( String newName ); } @Mixins( Country1Mixin.class ) public interface Country1 extends Country { } public static abstract class Country1Mixin implements Country1 { @Override public void updateName( String newName ) { name().set( newName ); } } @Mixins( Country2Mixin.class ) public interface Country2 extends Country { } public static abstract class Country2Mixin implements Country2 { @Override public void updateName( String newName ) { name().set( newName ); } } @Mixins( Country3Mixin.class ) public interface Country3 extends Country { @TimingCapture( "Country3.updateName" ) @Override void updateName( String newName ); } public static abstract class Country3Mixin implements Country3 { @Override public void updateName( String newName ) { name().set( newName ); } }