code
docs
tests
The Zest™ Core API is the primary interface for client application code during the main execution phase, i.e. after the application has been activated.
Composition is at the heart of COP, and refers to two different levels of constructs;
In Zest, 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.
There are 4 types of Fragments in Zest;
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 Zest™ 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 Zest-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 ); }
Zest™ 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. Zest™ 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.
Every Zest™ runtime has one and only one Application in it. It is possible to run multiple Zest™ runtimes in the same JVM, and it is even possible to embed a Zest™ runtime within a Zest™ Application, but there can only be one Application in a Zest™ runtime.
An Application is then broken into layers and modules are placed within those layers. Composites are placed within modules. This forms the Application Structure and is enforced by the Zest™ runtime.
A Zest™ 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.
Zest™ enforces this layering by requiring the Assembly to declare which layer uses which other layer. And Visibility 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 Visibility 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 Zest™ 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.
Zest™ 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;
Value objects can be serialized and deserialized using the ValueSerialization API which is a Service API implemented by SPI and extensions.
ValueSerialization extends ValueSerializer, ValueDeserializer
. See the JavaDocs for interfaces detail.
The ValueSerialization mechanism apply to the following object types :
Nested Plain Values, EntityReferences, Iterables, 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 ValueSerializer
methods can take a ValueSerializer.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 ValueSerializationException
is thrown.
Methods of ValueSerializer
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 ValueSerialization system based on the org.json Java library producing and consuming JSON.
Let’s see how it works in practice.
public interface SomeValue // (1) { Property<String> foo(); } @Override public void assemble( ModuleAssembly module ) throws AssemblyException { module.values( SomeValue.class ); // (2) [...snip...] } [...snip...] public void defaultValueSerialization() { SomeValue someValue = someNewValueInstance( module ); // (3) String json = someValue.toString(); // (4) SomeValue someNewValue = module.newValueFromSerializedState( SomeValue.class, json ); // (5) [...snip...] }
Reading this first example step by step we ;
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 ValueSerialization as Services to support more formats, see the Extensions section. Note that the default behaviour described above is overriden if a ValueSerialization Service is visible.
Let’s see how to use the ValueSerialization Services.
public interface SomeValue // (1) { Property<String> foo(); } @Override public void assemble( ModuleAssembly module ) throws AssemblyException { module.values( SomeValue.class ); // (2) new OrgJsonValueSerializationAssembler().assemble( module ); // (3) } [...snip...] @Service private ValueSerializer valueSerializer; // (4) @Service private ValueDeserializer valueDeserializer; // (4) [...snip...] public void assembledDefaultServiceSerialization() { SomeValue someValue = someNewValueInstance( module ); // (5) String json = valueSerializer.serialize( someValue ); // (6) SomeValue someNewValue = valueDeserializer.deserialize( SomeValue.class, json ); // (7) [...snip...] }
In this second example, we ;
org.json
package,
ValueSerializer
and ValueDeserializer
Services injected,
ValueSerializer#serialize()
method to get a JSON representation of the Value,
ValueDeserializer#eserialize()
method to create a new Value instance from the JSON state.
Many applications need to stream data. The ValueSerialization API support such use cases in two ways.
The first one use classic streams.
public void assembledServiceStreamingSerialization() { [...snip...] // (1) Iterable<AcmeValue> data = dataSource; // Eg. Entities converted to Values OutputStream output = targetStream; // Eg. streaming JSON over HTTP // (2) valueSerializer.serialize( data, output ); [...snip...] // (3) InputStream input = sourceStream; // Eg. reading incoming JSON // (4) List<AcmeValue> values = valueDeserializer.deserialize( CollectionType.listOf( AcmeValue.class ), input ); [...snip...] }
OutputStream
,
OutputStream
,
InputStream
,
InputStream
.
The second one use the I/O API:
public void assembledServiceIOSerialization() throws IOException { [...snip...] // (1) Iterable<AcmeValue> queryResult = dataSource; // Eg. Entities converted to Values Writer writer = outputWriter; // Eg. to pipe data to another process or to a file // (2) Function<AcmeValue, String> serialize = valueSerializer.serialize(); // (3) Inputs.iterable( queryResult ).transferTo( Transforms.map( serialize, Outputs.text( writer ) ) ); [...snip...] // (4) Reader reader = inputReader; List<AcmeValue> values = new ArrayList<AcmeValue>(); // (5) Function<String, AcmeValue> deserialize = valueDeserializer.deserialize( AcmeValue.class ); // Deserialization of a collection of AcmeValue from a String. // One serialized AcmeValue per line. // (6) Inputs.text( reader ).transferTo( Transforms.map( deserialize, Outputs.collection( values ) ) ); [...snip...] }
Writer
,
Function
,
Reader
and create a new empty List
of values,
Function
,
Any service added, via the ModuleAssembly.addServices(), ModuleAssembly.services() and ModuleAssembly.importServices() methods, will have the ServiceComposite meta type added to it. In Zest, 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.
Zest™ will bring a lot of the behavior back to the Composite itself, but we still need Services for cross-composite functionality. The Zest™ 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 Zest™ 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 Zest. 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 Zest™ is for Zest™ 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 Zest, 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 a properties file, with the same name as the ServiceDescriptor.identifiedBy(), which is set during Assembly and defaults to the fully qualified classname of the ServiceComposite type.
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(); }
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 Zest™ 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 Zest™ 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, Zest™ 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;
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 Zest™ 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. Zest™ 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 Zest™ 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 Zest™ 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 Zest™ 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.
As mentioned above, concerns that implements the Mixin Type are called Typed Mixins. 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, Zest™ 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. Zest™ 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;
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 = module.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" ) ); }
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 Zest™ 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 Zest™ platform defines an advanced Metrics SPI to capture runtime metrics of Zest’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 Zest™ 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 reach 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( getClass(), "Sample Gauge", new MetricsGauge<Integer>() { @Override public Integer value() { return queue.size(); } } );
MetricsCounterFactory counterFactory = provider.createFactory( MetricsCounterFactory.class ); MetricsCounter counter = counterFactory.createCounter( getClass(), "Sample Counter" );
MetricsHistogramFactory histoFactory = provider.createFactory( MetricsHistogramFactory.class ); MetricsHistogram histogram = histoFactory.createHistogram( getClass(), "Sample Histogram" );
MetricsMeterFactory meterFactory = provider.createFactory( MetricsMeterFactory.class ); MetricsMeter meter = meterFactory.createMeter( getClass(), "Sample Meter", "requests", TimeUnit.MINUTES );
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( getClass(), "Sample Timer", TimeUnit.SECONDS, TimeUnit.HOURS );
MetricsHealthCheckFactory healthFactory = provider.createFactory( MetricsHealthCheckFactory.class ); MetricsHealthCheck healthCheck = healthFactory.registerHealthCheck( getClass(), "Sample Healthcheck", new MetricsHealthCheck() { @Override public Result check() throws Exception { ServiceStatus status = pingMyService(); return new Result( status.isOk(), status.getErrorMessage(), status.getException() ); } } );