We receive a lot of questions about how applications should be assembled, and since we don’t have any XML to "fill in" and everything is to be done programmatically, it escalates the need to provide more hands-on explanation of how this is done.
If you want to reproduce what’s explained in this tutorial, remember to depend on the Core Bootstrap artifact:
At runtime you will need the Core Runtime artifact too. See the Depend on Zest™ in your build tutorial for details.
First let’s recap the structural requirements of Zest;
Ok, that was quite a handful. Let’s look at them one by one.
The first one means that for each Zest™ Runtime you start, there will be exactly one application. As far as we know, Zest is fully isolated, meaning there are no static members being populated and such.
Layers are the super-structures of an application. We have been talking about them for decades, drawn them on paper and whiteboards (or even black boards for those old enough), and sometimes organized the codebases along such boundaries. But, there has been little effort to enforce the Layer mechanism in code, although it is an extremely powerful construct. First of all it implies directional dependency and a high degree of order, spagetti code is reduced if successfully implemented. For Zest, it means that we can restrict access to Composite and Object declarations, so that higher layers can not reach them incidentally. You can enforce architecture to a high degree. You can require all creation of composites to go through an exposed Factory, which doesn’t require the Composite to be public. And so on. Layers have hierarchy, i.e. one layer is top of one or more layers, and is below one or more layers, except for the layers at the top and bottom. You could have disjoint layers, which can’t access each other, meaning a couple of layers that are both the top and bottom.
The Module concept has also been around forever. And in Zest™ we also makes the Modules explicit. Each Module belong to a Layer, and for each Module you declare the Composite and Object types for that Module, together with a Visibility rule, one of; application, layer, module.
The Visibility rules are perhaps the most powerful aspect of the above. Visibility is a mechanism that kicks in whenever a Composite type need to be looked up. It defines both the scoping rules of the client as well as the provider. A lookup is either a direct reference, such as
UnitOfWork unitOfWork = module.currentUnitOfWork(); PersonEntity person = unitOfWork.newEntity( PersonEntity.class );
or an indirect lookup, such as
UnitOfWork unitOfWork = module.currentUnitOfWork(); Person person = unitOfWork.newEntity( Person.class );
where it will first map the Person to a reachable PersonEntity. The algorithm is as follows;
The underlying principle comes down to Rickard’s "Speaker Analogy", you can hear him (and not the other speakers at the conference) because you are in the same room. I.e. if something is really close by, it is very likely that this is what we want to use, and then the search expands outwards.
Ok, that was a whole lot of theory and probably take you more than one read-through to fully get into your veins (slow acting addiction). How to structure your code is beyond the scope of this section. If you are an experienced designer, you will have done that before, and you may have started out with good intentions at times only to find yourself in a spaghetti swamp later, or perhaps in the also famous "Clear as Clay" or "Ball (bowl?) of Mud". Either way, you need to draw on your experience and come up with good structure that Zest™ lets you enforce.
So, for the sake of education, we are going to look at an application that consists of many layers, each with a few modules. See picture below.
Image of Example of Layers
Figure 1. Example of Layers
So, lets see how we code up this bit in the actual code first.
public class Main { private static Energy4Java qi4j; private static Application application; public static void main( String[] args ) throws Exception { // Bootstrap Zest Runtime // Create a Zest Runtime qi4j = new Energy4Java(); // Instantiate the Application Model. application = qi4j.newApplication( new ApplicationAssembler() { public ApplicationAssembly assemble( ApplicationAssemblyFactory factory ) throws AssemblyException { ApplicationAssembly assembly = factory.newApplicationAssembly(); LayerAssembly runtime = createRuntimeLayer( assembly ); LayerAssembly designer = createDesignerLayer( assembly ); LayerAssembly domain = createDomainLayer( assembly ); LayerAssembly messaging= createMessagingLayer( assembly ); LayerAssembly persistence = createPersistenceLayer( assembly ); // declare structure between layers domain.uses( messaging ); domain.uses( persistence ); designer.uses( persistence ); designer.uses( domain ); runtime.uses( domain ); return assembly; } } ); // We need to handle shutdown. installShutdownHook(); // Activate the Application Runtime. application.activate(); } [...snip...] }
The above is the basic setup on how to structure a real-world applicaton, unless you intend to mess with the implementations of various Zest™ systems (yes there are hooks for that too), but that is definitely beyond the scope of this tutorial.
Now, the createXyzLayer() methods were excluded to keep the sample crisp and easy to follow. Let’s take a look at what it could be to create the Domain Layer.
private static LayerAssembly createDomainLayer( ApplicationAssembly app ) { LayerAssembly layer = app.layer("domain-layer"); createAccountModule( layer ); createInventoryModule( layer ); createReceivablesModule( layer ); createPayablesModule( layer ); return layer; }
We just call the layerAssembly() method, which will return either an existing Layer with that name or create a new one if one doesn’t already exist, and then delegate to methods for creating the ModuleAssembly instances. In those method we need to declare which Composites, Entities, Services and Objects that is in each Module.
private static void createAccountModule( LayerAssembly layer ) { ModuleAssembly module = layer.module("account-module"); module.entities(AccountEntity.class, EntryEntity.class); module.addServices( AccountRepositoryService.class, AccountFactoryService.class, EntryFactoryService.class, EntryRepositoryService.class ).visibleIn( Visibility.layer ); }
We also need to handle the shutdown case, so in the main() method we have a installShutdownHook() method call. It is actually very, very simple;
private static void installShutdownHook() { Runtime.getRuntime().addShutdownHook( new Thread( new Runnable() { public void run() { if( application != null ) { try { application.passivate(); } catch( Exception e ) { e.printStackTrace(); } } } }) ); }
This concludes this tutorial. We have looked how to get the initial Zest™ runtime going, how to declare the assembly for application model creation and finally the activation of the model itself.