Theses tutorials are based on actual code found in the tutorials/
directory of the
Zest™ SDK sources. You should start your favorite editor and find the code related to
this tutorial, run it and play with it.
Throughout this set of tutorials it will be shown how to create and work with Composites, which is the basic element in Zest™. We will refactor one HelloWorld class to take advantage of the various features in Zest™. These refactorings will make it easier to reuse parts of the class, and introduce new features without having to change existing code. We will also look at some of the existing classes, or Fragments, available in Zest™ that you can reuse so that you don’t have to write everything yourself.
Each tutorial step in this series starts with the result from the previous tutorial, so you can always look at the next tutorial step for guidance on what to do.
At the bottom of each tutorial step, the is Solutions section, which list the files you should have come to if you have followed the instructions.
If you want to reproduce what’s explained in this tutorial, remember to depend on the Core Runtime artifact that depends on Core API, Core SPI, Core Bootstrap and Core Functional & I/O APIs:
See the Depend on Zest™ in your build tutorial for details.
This whole tutorial describes how to step-by-step modify a typical HelloWorld "application" into a full-fledged Zest™ Composite Oriented application. Here is the initial code of HelloWorld.
/** * Initial HelloWorld implementation. Everything is mixed up * into one class, and no interface is used. */ public class HelloWorld { String phrase; String name; public String getPhrase() { return phrase; } public void setPhrase( String phrase ) throws IllegalArgumentException { if( phrase == null ) { throw new IllegalArgumentException( "Phrase may not be null " ); } this.phrase = phrase; } public String getName() { return name; } public void setName( String name ) throws IllegalArgumentException { if( name == null ) { throw new IllegalArgumentException( "Name may not be null " ); } this.name = name; } public String say() { return phrase + " " + name; } }
In this step we start with a basic Java class, which when invoked will concatenate the two properties "phrase" and "name". If invoked with the properties set to "Hello" and "World" respectively it will hence return "Hello World".
Zest™ relies heavily on the use of interfaces. This makes it possible for an object to externally implement a number of interfaces which internally is backed by a number of Mixins, some of which you may have written yourself, and some of which may have been reused. This also makes it easy to introduce Modifiers (aka "interceptors", aka "advice"), which are Fragments which execute before and/or after the method on the Mixin is invoked.
The first task is therefore to refactor the code so that the method is implemented from an interface instead. We should then also separate the state into one interface and the behaviour into another. This will make things easier for us later when state and behaviour becomes implemented by separate Mixins.
Steps for this tutorial:
If you have successfully completed the task, you should end up with the following artifacts;
HelloWorld.java
/** * This interface aggregates the behaviour and state * of the HelloWorld sub-interfaces. To a client * this is the same as before though, since it only * has to deal with this interface instead of the * two sub-interfaces. */ public interface HelloWorld extends HelloWorldBehaviour, HelloWorldState { }
HelloWorldState.java
/** * This interface contains only the state * of the HelloWorld object. * The exceptions will be thrown by Zest automatically if * null is sent in as values. The parameters would have to be declared * as @Optional if null is allowed. */ public interface HelloWorldState { void setPhrase( String phrase ) throws IllegalArgumentException; String getPhrase(); void setName( String name ) throws IllegalArgumentException; String getName(); }
HelloWorldBehaviour.java
/** * This interface contains only the behaviour * of the HelloWorld object. */ public interface HelloWorldBehaviour { String say(); }
HelloWorldMixin.java
/** * This is the implementation of the HelloWorld * interface. The behaviour and state is mixed. */ public class HelloWorldMixin implements HelloWorld { String phrase; String name; @Override public String say() { return getPhrase() + " " + getName(); } @Override public void setPhrase( String phrase ) throws IllegalArgumentException { if( phrase == null ) { throw new IllegalArgumentException( "Phrase may not be null" ); } this.phrase = phrase; } @Override public String getPhrase() { return phrase; } @Override public void setName( String name ) throws IllegalArgumentException { if( name == null ) { throw new IllegalArgumentException( "Name may not be null" ); } this.name = name; } @Override public String getName() { return name; } }
Next step is Step 2 - Creating a Transient Composite
Previous step was Step 1 - Interface Refactoring.
In this step we will create a TransientComposite interface that ties all pieces together. The TransientComposite interface is a regular Java interface which extends the interfaces you want to expose from your domain model, and which uses various annotations to declare what Fragments to include. Fragments include Mixins, Concerns, SideEffects and Constraints. In this tutorial we will only use Mixins. When a TransientComposite is instantiated at runtime the framework will inspect the interface to determine what the TransientComposite instance should look like in terms of used Fragments.
In Zest™ all method parameters are considered mandatory unless marked as @Optional. Therefore you can remove the null checks in the Mixin. If a null value is passed in an exception will be thrown by Zest™.
Steps for this tutorial:
These ones remain unchanged:
HelloWorld.java
HelloWorldBehaviour.java
HelloWorldState.java
HelloWorldComposite.java
/** * This Composite interface declares all the Fragments of the HelloWorld composite. * <p> * Currently it only declares one Mixin. * </p> */ @Mixins( HelloWorldMixin.class ) public interface HelloWorldComposite extends HelloWorld, TransientComposite { }
HelloWorldMixin.java
/** * This is the implementation of the HelloWorld * interface. The behaviour and state is mixed. */ public class HelloWorldMixin implements HelloWorld { String phrase; String name; @Override public String say() { return getPhrase() + " " + getName(); } @Override public void setPhrase( String phrase ) throws IllegalArgumentException { if( phrase == null ) { throw new IllegalArgumentException( "Phrase may not be null" ); } this.phrase = phrase; } @Override public String getPhrase() { return phrase; } @Override public void setName( String name ) throws IllegalArgumentException { if( name == null ) { throw new IllegalArgumentException( "Name may not be null" ); } this.name = name; } @Override public String getName() { return name; } }
Next step is Step 3 - Mixins
Previous step was Step 2 - Creating a Transient Composite.
In this step we refactor the Mixin from the previous steps into two, one which serves the behaviour interface and one which serves the state interface. This makes it possible to reuse the interfaces independently and also makes it easier to exchange one interface implementation with another. This also allows us to specify the new Mixins as default implementations of the interfaces by adding @Mixins annotations on them.
Steps for this tutorial:
Only HelloWorld.java remains unchanged.
HelloWorldComposite.java
/** * This Composite interface declares all the Fragments * of the HelloWorld composite. * <p> * The Mixins annotation has been moved to the respective sub-interfaces. * The sub-interfaces therefore declare themselves what mixin implementation * is preferred. This interface could still have its own Mixins annotation * with overrides of those defaults however. * </p> */ public interface HelloWorldComposite extends HelloWorld, TransientComposite { }
HelloWorldBehaviour.java
/** * This interface contains only the behaviour of the HelloWorld object. * <p> * It declares what Mixin to use as default implementation. * </p> */ @Mixins( HelloWorldBehaviourMixin.class ) public interface HelloWorldBehaviour { String say(); }
HelloWorldBehaviourMixin.java
/** * This is the implementation of the HelloWorld behaviour interface. * <p> * It uses a @This Dependency Injection * annotation to access the state of the Composite. The field * will be automatically injected when the Composite * is instantiated. Injections of resources or references * can be provided either to fields, constructor parameters or method parameters. * </p> */ public class HelloWorldBehaviourMixin implements HelloWorldBehaviour { @This HelloWorldState state; @Override public String say() { return state.getPhrase() + " " + state.getName(); } }
HelloWorldState.java
/** * This interface contains only the state * of the HelloWorld object. * The exceptions will be thrown by Zest automatically if * null is sent in as values. The parameters would have to be declared * as @Optional if null is allowed. */ @Mixins( HelloWorldStateMixin.class ) public interface HelloWorldState { void setPhrase( String phrase ) throws IllegalArgumentException; String getPhrase(); void setName( String name ) throws IllegalArgumentException; String getName(); }
HelloWorldStateMixin.java
/** * This is the implementation of the HelloWorld * state interface. */ public class HelloWorldStateMixin implements HelloWorldState { String phrase; String name; @Override public String getPhrase() { return phrase; } @Override public void setPhrase( String phrase ) { this.phrase = phrase; } @Override public String getName() { return name; } @Override public void setName( String name ) { this.name = name; } }
Next step is Step 4 - Concerns
Previous step was Step 3 - Mixins.
In this step we refactor the mixin from the previous steps so that the result of the say() method is modified to be prefixed with "Simon says:". To do this we need to implement a Concern for the say() method. Concerns are a type of Modifier which modify the behaviour of the methods in Mixins. They do this by intercepting the invocation of the TransientComposite. This allows them to change the invocation parameters, return their own values or throw their own exceptions, and do other things which directly affect the invocation of the method.
Concerns should not perform any side-effects, such as updating state in another TransientComposite, Mixin or similar. Any side-effects are done in SideEffects, which is another type of Modifier, which are allowed to perform side-effects but, in contrast to Concerns, cannot change the parameters or in any other way change the result of the invocation.
Concerns are implemented in one of two ways: either create a class which directly implements the interface whose methods should be modified, or create a generic Modifier by implementing the InvocationHandler interface (or subclass GenericConcern which does this for you). Add an @ConcernFor dependency injection, as a field, constructor parameter or method parameter, which has the same type as the interface the Concern implements. When the TransientComposite is invoked the Concern will be called, allowing it to perform it’s work. If the call should proceed, then invoke the method again on the injected object. The preferred way to do all of this is to subclass ConcernOf which does all of this for you.
Concerns are applied by adding an @Concerns annotation on the TransientComposite, the domain interface, or the Mixin implementation. Any of these works, and where to put it is a matter of design choice.
Steps for this tutorial:
If you have successfully completed the task, you should end up with the following artifacts;
These ones remain unchanged:
HelloWorld.java
HelloWorldBehavior.java
HelloWorldComposite.java
HelloWorldState.java
HelloWorldStateMixin.java
HelloWorldBehaviourMixin.java
/** * This is the implementation of the HelloWorld * behaviour interface. * <p> * It uses a @This Dependency Injection * annotation to access the state of the Composite. The field * will be automatically injected when the Composite * is instantiated. Injections of resources or references * can be provided either to fields, constructor parameters or method parameters. * </p> */ @Concerns( HelloWorldBehaviourConcern.class ) public class HelloWorldBehaviourMixin implements HelloWorldBehaviour { @This HelloWorldState state; @Override public String say() { return state.getPhrase() + " " + state.getName(); } }
HelloWorldBehaviourConcern.java
/** * This is a concern that modifies the mixin behaviour. */ public class HelloWorldBehaviourConcern extends ConcernOf<HelloWorldBehaviour> implements HelloWorldBehaviour { @Override public String say() { return "Simon says:" + next.say(); } }
Next step is Step 5 - Constraints
Previous step was Step 4 - Concerns.
In this step we will look at how to use Constraints. When we pass parameters to methods in regular Java code the only restriction we can make is to denote a type. Any other constraints on the input value, such as whether the parameter is optional, integer ranges, string regular expressions, and so on, cannot be expressed, and so we have to put this into Javadoc, and then manually add these checks in the implementation class.
In Zest™ there is the option to use Constraints, which are further restrictions on the parameters. This is implemented by having an annotation that describes what the Constraint does, and then an implementation class that checks whether a specific value fulfills the Constraint or not.
The previous steps had a dependency to the Core API only. The constraints you’ve used in this step, introduce a new dependency to the Constraints Library, where all the constraint related classes reside. So update your classpath settings accordingly.
There are a number of pre-written constraints in Zest™ which you can use. The null check of the original HelloWorld version is already handled by default since Zest™ considers method parameters to be mandatory if not explicitly marked with the @Optional annotation. So, instead of doing that check we will add other checks that are useful to make, such as ensuring that the passed in string is not empty.
The only thing you have to do is add the annotation @NotEmpty to the method parameters you want to constrain in this way. The annotation has a default implementation declared in it by using the @Constraints annotation. You can either just use this, which is the common case, or override it by declaring your own @Constraints annotation in the TransientComposite type.
You can add as many Constraint annotations you want to a parameter. All of them will be checked whenever a method is called.
Steps for this tutorial:
If you have successfully completed the task, you should end up with the following artifacts;
These ones remain unchanged:
HelloWorld.java
HelloWorldComposite.java
HelloWorldStateMixin.java
HelloWorldBehaviour.java
/** * This interface contains only the behaviour * of the HelloWorld object. * <p> * It declares what Mixin to use as default implementation, and also the extra * concern to be applied. * </p> */ @Concerns( HelloWorldBehaviourConcern.class ) @Mixins( HelloWorldBehaviourMixin.class ) public interface HelloWorldBehaviour { String say(); }
HelloWorldBehaviourMixin.java
/** * This is the implementation of the HelloWorld * behaviour interface. * <p> * It uses a @This DependencyModel Injection * annotation to access the state of the Composite. The field * will be automatically injected when the Composite * is instantiated. Injections of resources or references * can be provided either to fields, constructor parameters or method parameters. * </p> */ public class HelloWorldBehaviourMixin implements HelloWorldBehaviour { @This HelloWorldState state; @Override public String say() { return state.getPhrase() + " " + state.getName(); } }
HelloWorldBehaviourConcern.java
/** * This Concern validates the parameters * to the HelloWorldState interface. */ public class HelloWorldBehaviourConcern extends ConcernOf<HelloWorldBehaviour> implements HelloWorldBehaviour { @Override public String say() { return "Simon says:" + next.say(); } }
HelloWorldState.java
/** * This interface contains only the state * of the HelloWorld object. * <p> * The parameters are declared as @NotEmpty, so the client cannot pass in empty strings * as values. * </p> */ @Mixins( HelloWorldStateMixin.class ) public interface HelloWorldState { void setPhrase( @NotEmpty String phrase ) throws IllegalArgumentException; String getPhrase(); void setName( @NotEmpty String name ) throws IllegalArgumentException; String getName(); }
Next step is Step 6 - SideEffects
Previous step was Step 5 - Constraints.
The current say() method has a Concern that modifies its value. What if we instead want the value to be intact, but log that value to System.out? That would be considered a side-effect of the say() method, and should hence not be done in a Concern. It would be better to implement this in a SideEffect. SideEffects are executed after the Mixin and all Concerns for a method are done, which means that the final result has been computed. A SideEffect can access this result value, and then use that for further computation, but it should not change the value or throw an exception.
SideEffects can be either typed or generic, just like Concerns. In the typed case we are interested in specifying SideEffects for one or more particular methods, whereas in the generic case the SideEffect is not really relying on what method is being invoked. Both are useful in different scenarios.
The easiest way to implement a typed SideEffect is to subclass the SideEffectOf class. This gives you access to the result of the real method invocation by using the "next" field, which has the same type as the interface of the method you want the code to be a side-effect of. Note that calling "next" does not actually do anything, it only returns the value (or throws the exception, if one was thrown from the original method) that has already been computed. Similarly, since the method is already done, you can return anything from the SideEffect method. The framework will simply throw it away, and also ignore any exceptions that you throw in your code.
To declare that the SideEffect should be used you add the @SideEffects annotation to either the TransientComposite type, the Mixin type, or the Mixin implementation. Either works.
Steps for this tutorial:
If you have successfully completed the task, you should end up with the following artifacts;
These ones remain unchanged:
HelloWorld.java
HelloWorldBehaviourMixin.java
HelloWorldStateMixin.java
HelloWorldBehaviour.java
/** * This interface contains only the behaviour * of the HelloWorld object. */ public interface HelloWorldBehaviour { String say(); }
HelloWorldBehaviourSideEffect.java
/** * As a side-effect of calling say, output the result. */ public class HelloWorldBehaviourSideEffect extends SideEffectOf<HelloWorldBehaviour> implements HelloWorldBehaviour { @Override public String say() { System.out.println( result.say() ); return null; } }
HelloWorldComposite.java
/** * This Composite interface declares transitively * all the Fragments of the HelloWorld composite. * <p> * It declares that the HelloWorldBehaviourSideEffect should be applied. * </p> */ @Mixins( { HelloWorldBehaviourMixin.class, HelloWorldStateMixin.class } ) @SideEffects( HelloWorldBehaviourSideEffect.class ) public interface HelloWorldComposite extends HelloWorld, TransientComposite { }
HelloWorldState.java
/** * This interface contains only the state * of the HelloWorld object. * <p> * The parameters are declared as @NotEmpty, so the client cannot pass in empty strings * as values. * </p> */ public interface HelloWorldState { void setPhrase( @NotEmpty String phrase ) throws IllegalArgumentException; String getPhrase(); void setName( @NotEmpty String name ) throws IllegalArgumentException; String getName(); }
Next step is Step 7 - Properties
Previous step was Step 6 - SideEffects.
One of the goals of Zest™ is to give you domain modeling tools that allow you to more concisely use domain concepts in code. One of the things we do rather often is model Properties of objects as getters and setters. But this is a very weak model, and does not give you any access to metadata about the property, and also makes common tasks like UI binding non-trivial. There is also a lot of repetition of code, which is unnecessary. Using JavaBeans conventions one typically have to have code in five places for one property, whereas in Zest™ the same thing can be achieved with one line of code.
But lets start out easy. To declare a property you have to make a method in a mixin type that returns a value of the type Property, and which does not take any parameters. Here’s a simple example:
Property<String> name();
This declares a Property of type String with the name "name". The Property interface has methods "get" and "set" to access and mutate the value, respectively.
For now you will be responsible for implementing these methods, but later on these will be handled automatically, thus reducing Properties to one-liners!
In the Mixin implementation of the interface with the Property declaration you should have an injection of the Property, which is created for you by Zest™. The injection can be done in a field like this:
@State Property<String> name;
The State dependency injection annotation means that Zest™ will inject the Property for you. The field has the name "name", which matches the name in the interface, and therefore that Property is injected. You can then implement the method trivially by just returning the "name" field.
Properties can have Constraints just like method parameters. Simply set them on the Property method instead, and they will be applied just as before when you call "set".
Steps for this tutorial:
If you have successfully completed the task, you should end up with the following artifacts;
Only HelloWorldBehavior.java
remains unchanged.
Theses ones are deleted:
HelloWorld.java
HelloWorldConcern.java
HelloWorldBehaviourMixin.java
/** * This is the implementation of the HelloWorld * behaviour interface. * <p> * This version access the state using Zest Properties. * </p> */ public class HelloWorldBehaviourMixin implements HelloWorldBehaviour { @This HelloWorldState state; @Override public String say() { return state.phrase().get() + " " + state.name().get(); } }
HelloWorldComposite.java
/** * This Composite interface declares transitively * all the Fragments of the HelloWorld composite. */ @Mixins( { HelloWorldBehaviourMixin.class, HelloWorldStateMixin.class } ) public interface HelloWorldComposite extends HelloWorldBehaviour, HelloWorldState, TransientComposite { }
HelloWorldState.java
/** * This interface contains only the state * of the HelloWorld object. * <p> * The state is now declared using Properties. The @NotEmpty annotation is applied to the * method instead, and has the same meaning as before. * </p> */ public interface HelloWorldState { @NotEmpty Property<String> phrase(); @NotEmpty Property<String> name(); }
HelloWorldStateMixin.java
/** * This is the implementation of the HelloWorld * state interface. */ public class HelloWorldStateMixin implements HelloWorldState { @State private Property<String> phrase; @State private Property<String> name; @Override public Property<String> phrase() { return phrase; } @Override public Property<String> name() { return name; } }
Next step is Step 8 - Generic Mixins
Previous step was Step 7 - Properties.
In this step we will look at how to use generic Fragments. So far all Fragments, i.e. the Concerns, SideEffects, and Mixins, have directly implemented the domain interface. But sometimes it is useful to be able to provide a generic implementation of an interface. An example of this is the HelloWorldState interface. Since it only handles properties, and the old version used the JavaBean rules for naming getters and setters we could create a mixin that handles invocations of such methods automatically for us by storing the properties in a map and use the methods to look them up.
Implementing a generic Fragment is done by creating a class that implements the interface java.lang.proxy.InvocationHandler. This has a single "invoke" method which is passed the object that was invoked (the TransientComposite in this case), the method, and the arguments. The Fragment is then allowed to implement the method any way it wants.
Since interfaces with only Properties is such a common case Zest™ already has a generic Mixin that implements the Properties management described above, but for the builtin Property type instead of the getter/setter variant. The class is aptly named PropertyMixin.
While we could use it, for now we will implement it ourselves to get a feel for how generic Mixins work.
Steps for this tutorial:
If you have successfully completed the task, you should end up with the following artifacts;
These ones remain unchanged:
HelloWorldBehaviour.java
HelloWorldState.java
GenericPropertyMixin.java
@AppliesTo( { GenericPropertyMixin.PropertyFilter.class } ) public class GenericPropertyMixin implements InvocationHandler { @State private StateHolder state; @Override public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { return state.propertyFor( method ); } public static class PropertyFilter implements AppliesToFilter { @Override public boolean appliesTo( Method method, Class<?> mixin, Class<?> compositeType, Class<?> modifierClass ) { return Property.class.isAssignableFrom( method.getReturnType() ); } } }
HelloWorldBehaviourMixin.java
/** * This is the implementation of the HelloWorld * behaviour interface. */ public class HelloWorldBehaviourMixin implements HelloWorldBehaviour { @This HelloWorldState state; @Override public String say() { return state.phrase().get() + " " + state.name().get(); } }
HelloWorldComposite.java
/** * This Composite interface declares transitively * all the Fragments of the HelloWorld composite. * <p> * All standard declarations have been moved to * the StandardAbstractEntityComposite so we don't have to repeat * them in all Composites. * </p> */ @Mixins( { HelloWorldBehaviourMixin.class, GenericPropertyMixin.class } ) public interface HelloWorldComposite extends HelloWorldBehaviour, HelloWorldState, TransientComposite { }
Next step is Step 9 - Private and Abstract Mixins
Previous step was Step 8 - Generic Mixins.
Now we’re going to turn around and see how we can reduce the code needed to implement the HelloWorld example. We will also look at how to hide the Properties from the client code, since Properties are often considered to be implementation details that should not be exposed to clients.
The first thing we will do is remove the behaviour interface, and move the say() method to the TransientComposite type. This forces the mixin to implement the TransientComposite type, which would normally mean that it would have to implement all methods, including those found in the TransientComposite interface. However, since we are only really interested in implementing the say() method we will mark this by declaring that the Mixin "implements" the TransientComposite type, but is also "abstract". This, using pure Java semantics, makes it possible to avoid having to implement all methods. Zest™ will during the initialization phase detect that the Mixin only handles the say() method, and therefore only map it to that specific method. In order to instantiate the Mixin it will generate a subclass which implements the remaining methods in the TransientComposite type, as no-ops. These will never be called however, and is there purely for the purpose of being able to instantiate the Mixin. The Mixin is considered to be an Abstract Fragment.
To hide the state from the client we need to use what is called Private Mixins. A Private Mixin is any mixin that is referenced by another Mixin by using the @This injection, but which is not included in the TransientComposite type. As long as there is a Mixin implementation declared for the interface specified by the @This injection it will work, since Zest™ can know how to implement the interface. But since it is not extended by the TransientComposite type there is no way for a user of the TransientComposite to access it. That Mixin becomes an implementation detail. This can be used either for encapsulation purposes, or for referencing utility mixins that help the rest of the code implement some interface, but which itself should not be exposed.
Since the state is now hidden the initialization of the TransientComposite is a bit more tricky. Instead of just instantiating it you have to create a TransientBuilder first, then set the state using .prototypeFor(), and then call newInstance(). This ensures that the state can be set during construction, but not at any later point, other than through publicly exposed methods.
Steps for this tutorial:
If you have successfully completed the task, you should end up with the following artifacts only;
HelloWorldComposite.java
/** * This Composite interface declares transitively all the Fragments of the HelloWorld composite. * <p> * The Fragments are all abstract, so it's ok to * put the domain methods here. Otherwise the Fragments * would have to implement all methods, including those in Composite. * </p> */ @Mixins( { HelloWorldMixin.class } ) public interface HelloWorldComposite extends TransientComposite { String say(); }
HelloWorldMixin.java
/** * This is the implementation of the say() method. The mixin * is abstract so it doesn't have to implement all methods * from the Composite interface. */ public abstract class HelloWorldMixin implements HelloWorldComposite { @This HelloWorldState state; @Override public String say() { return state.phrase().get() + " " + state.name().get(); } }
HelloWorldState.java
/** * This interface contains only the state * of the HelloWorld object. */ public interface HelloWorldState { @NotEmpty Property<String> phrase(); @NotEmpty Property<String> name(); }