This project has retired. For details please refer to its Attic page.
Core Bootstrap
Zest™
Introduction
Tutorials
Javadoc
Samples
Core
Libraries
Extensions
Tools
Glossary 

Core Bootstrap

code

docs

tests

Zest™ has a distinct bootstrap phase, also known as the Assembly of an application, where the applications structure is defined programmatically. Once all the layers, modules and all the composite types in each module have been defined the model is instantiated into an application. This enables the entire structure system in Zest, where types "belongs" to a module and visibility rules define default behaviors, enforcement of architectural integrity and much more.

The assembly is preceeded by the creation of the Qi4j Runtime. The assembly can be declared fully by defining all modules and layers, and how the layers are sitting on top of each other, OR one can utilize one of the two convenience assemblies, one for a pancake pattern, where all layers are top on each other, or one with a single module in a single layer, useful for small applications, spikes and tests. The bootstrap system has several ways to acheive this, and they are listed below in Layered Application Assembler.

During assembly, the application (JVM level) architecture and the application model is defined. You define which layers exist and how they relate to each other. For each layer, you define which modules it contains. And for each module, you define which composites are in it, and what are the visibility rules for each of these composites.

You can also;

Table 16. Artifact

Group IDArtifact IDVersion

org.qi4j.core

org.qi4j.core.bootstrap

2.1


Defining Objects

@Override
public void assemble( ModuleAssembly module )
        throws AssemblyException
{
    module.objects( MyObject.class ).visibleIn( Visibility.layer );
}

Defining Transients

@Override
public void assemble( ModuleAssembly module )
        throws AssemblyException
{
    module.transients( MyTransient.class ).visibleIn( Visibility.layer );
}

Defining Values

@Override
public void assemble( ModuleAssembly module )
        throws AssemblyException
{
    module.values( MyValue.class ).visibleIn( Visibility.layer );
}

Defining Entities

@Override
public void assemble( ModuleAssembly module )
        throws AssemblyException
{
    module.entities( MyEntity.class ).visibleIn( Visibility.layer );
}

Defining Services

@Override
public void assemble( ModuleAssembly module )
        throws AssemblyException
{
    module.services( MyService.class ).visibleIn( Visibility.layer );
}
Tagging Services
@Override
public void assemble( ModuleAssembly module )
    throws AssemblyException
{
    module.services( MyService.class ).taggedWith( "foo", "bar" );
}
Importing external Services
@Override
public void assemble( ModuleAssembly module )
    throws AssemblyException
{
    module.importedServices( MyService.class ).
        importedBy( InstanceImporter.class ).
        setMetaInfo( new MyService() );

    // OR

    module.objects( MyService.class );
    module.importedServices( MyService.class ).
        importedBy( NewObjectImporter.class );
}

Defining default values for Properties

@Override
public void assemble( ModuleAssembly module )
    throws AssemblyException
{
    module.values( MyValue.class );
    MyValue myValueDefaults = module.forMixin( MyValue.class ).declareDefaults();
    myValueDefaults.foo().set( "bar" );

    module.entities( MyEntity.class );
    MyEntity myEntityDefaults = module.forMixin( MyEntity.class ).declareDefaults();
    myEntityDefaults.cathedral().set( "bazar" );
}

Adding additional interfaces to composites

Adding concerns, mixins, constraints and side effects

Setting meta information on assembled types

Using Assemblers

Many libraries and extensions provides a cookie-cutter Assembler, to simplify the set up of such component. Often these are suitable, but sometimes they won’t fit the application in hand, in which case the source code at least provides information of what is needed for the component to be used.

Assemblers are typically just instantiated and then call the assemble() method with the ModuleAssembly instance, such as;

@Override
public void assemble( ModuleAssembly module )
    throws AssemblyException
{
    RestServerAssembler assembler = new RestServerAssembler();
    assembler.assemble( module );
}

Defining an Entity Store

Defining an Entity Store is in principle as simple as defining a ServiceComposite implementing the EntityStore interface. The problem is that most Entity Stores require Service Configuration, and configuration requires an Entity Store. This chicken-and-egg problem is resolved by having an entity store available that does not require any Service Configuration. Many Assemblers for entity store implementations uses the MemoryEntityStore, and effectively leaves the configuration in the properties file where Service Configuration bootstraps from. It is possible to chain this, so that for instance the Neo4J Entity Store uses the Preferences Entity Store for its configuration, and the Preferences Entity Store uses the Memory Entity Store (i.e. the properties file).

The point is that the entity store used for the configuration of the primary entity store used in the application is that it must not be visible to the application itself. Sometimes it is easier to put a Memory Entity Store in the same module, with Visibility set to module. Sometimes it makes sense to have an additional Configuration layer below the infrastructure layer, which has this setup.

As mentioned above, most entity stores defines a reasonable default Assembler, possibly with some constructor arguments or methods to define certain aspects. An example is the popular JdbmEntityStore, which Assembler can be used like;

@Override
public void assemble( ModuleAssembly module )
    throws AssemblyException
{
    new JdbmEntityStoreAssembler().assemble( module );
}

Layered Application Assembler (RECOMMENDED!)

In 2.1, a new way to instantiate Zest™ applications was introduced. It starts with subclassing the LayeredApplicationAssembler, and implementing the assembleLayers() method.

In the assembleLayers() method, one is epected to either call the createLayer() method in the super class with the Class of the LayerAssembler,

    LayerAssembly domainLayer = createLayer( DomainLayer.class );

OR manually instantiate and call the LayerAssembler.

    LayerAssembly infraLayer = new InfrastructureLayer( configModule ).assemble( assembly.layer( InfrastructureLayer.NAME  ));

This is to make the normal case as simple as possible, yet allow the special needs that occssionally surfaces.

Each LayerAssembler implementation may optionally extend the LayeredLayerAssembler, to get access to the createModule() method, which again simplifies the creation of modules in the assemble() method.

    createModule( layer, InvoicingModule.class );

ModuleAssembler implementations typically use Assembler classes to put together, or call the entities(), values() methods described elsewhere on this page. There is no superclass to use.

ModuleAssembler implementations should have a name ending with "Module" and the naming will insert a human-readable space within the module name, e.g. InvoicingModule will be named "Invoicing Module".

For example code, see the tutorial Assemble an Application.

Singleton Assembler

Every Zest™ runtime instance consist of One Application, with one or more Layers and one or more Modules in each Layer. So the minimal application is still one layer with one module. This is not recommended other than for testing purposes and really trivial applications.

Let’s take a closer look at how it is put together.

SingletonAssembler assembler = new SingletonAssembler()
{

    @Override
    public void assemble( ModuleAssembly module )
            throws AssemblyException
    {
        module.services( MyService.class ).identifiedBy( "Foo" );
        module.services( MyService.class ).identifiedBy( "Bar" );
        module.objects( Stuff.class );
    }

};
Module module = assembler.module();
Stuff stuff = module.newObject( Stuff.class );

Once the SingletonAssembler constructor returns, the Zest™ application is up and running.

The SingletonAssembler also makes common system resources available from the bootstrap code, such as Module, UnitOfWorkFactory and others. This is possible since there is only one Module.

Application Builder

Some applications has no need for runtime determination of the exact application structure, and no need for advanced alterations to a staright-forward layered application structure. By using the ApplicationBuilder it is possible to define the application structure from a JSON document, AND call the provided main() class, taking the JSON document as input on System.in.

The format of the JSON document, directly reflects the application structure, such as

{
    "name": "Build from JSON test.",
    "layers": [
        { "name": "service", "uses": [ "domain", "config"] },
        { "name": "donfig" },
        {
            "name": "domain",
            "modules" : [
                {
                    "name" : "Invoicing",
                    "assemblers" : [
                        "org.hedhman.niclas.bootstrap.InvoicingAssembler"
                    ]
                }
            ]
        }
    ]
}

At the moment, the JSON format only support Assembler classes to do the work.

Another way to use the ApplicationBuilder is to subclass it, optionally use the configureFromJSON() method, and then programmatically enhance the structure before calling newApplication().

Pancake Assembly

There is one case that stands out as a common case, and forms a reasonable middle-ground. It is where each layer sits exactly on top of each other layer, like pancakes. Each layer will only use the layer directly below and only that layer. For this case we have a convenience setup. You create an Assembler[][][], where the outer-most array is each layer, the middle array is the modules in each layer, and the last array is a set of assemblers needed to put the things togather.

Let’s look at an example;

public static void main( String[] args )
        throws Exception
{
    qi4j = new Energy4Java();
    Assembler[][][] assemblers = new Assembler[][][]{
        { // View Layer
            { // Login Module
                new LoginAssembler()
            // :
            },
            { // Main Workbench Module
                new MenuAssembler(),
                new PerspectivesAssembler(),
                new ViewsAssembler()
            // :
            },
            { // Printing Module
                new ReportingAssembler(),
                new PdfAssembler()
            // :
            }
        },
        { // Application Layer
            { // Accounting Module
                new BookkeepingAssembler(),
                new CashFlowAssembler(),
                new BalanceSheetAssembler()
            // :
            },
            { // Inventory Module
                new PricingAssembler(),
                new ProductAssembler()
            // :
            }
        },
        { // Domain Layer
        // :
        },
        { // Infrastructure Layer
        // :
        }
    };
    ApplicationDescriptor model = newApplication( assemblers );
    Application runtime = model.newInstance( qi4j.spi() );
    runtime.activate();
}

private static ApplicationDescriptor newApplication( final Assembler[][][] assemblers )
        throws AssemblyException
{
    return qi4j.newApplicationModel( new ApplicationAssembler()
    {

        @Override
        public ApplicationAssembly assemble( ApplicationAssemblyFactory appFactory )
                throws AssemblyException
        {
            return appFactory.newApplicationAssembly( assemblers );
        }

    } );
}

Full Assembly

Full Assembly means that you have the opportunity to create any layer/module hierarchy that are within the rules of the Zest™ runtime. It requires more support in your code to be useful, and the example below is by no means a recommended way to organize large application assemblies.

In principle, you first start the Zest™ runtime, call newApplication with an ApplicationAssembler instance and call activate() on the returned application. The ApplicationAssembler instance will be called with an ApplicationAssemblyFactory, which is used to create an ApplicationAssembly describing the application structure.

private static Energy4Java qi4j;

private static Application application;

public static void main( String[] args )
        throws Exception
{
    // Create a Zest Runtime
    qi4j = new Energy4Java();
    application = qi4j.newApplication( new ApplicationAssembler()
    {

        @Override
        public ApplicationAssembly assemble( ApplicationAssemblyFactory appFactory )
                throws AssemblyException
        {
            ApplicationAssembly assembly = appFactory.newApplicationAssembly();
            buildAssembly( assembly );
            return assembly;
        }

    } );
    // activate the application
    application.activate();
}

static void buildAssembly( ApplicationAssembly app ) throws AssemblyException
{
    LayerAssembly webLayer = createWebLayer( app );
    LayerAssembly domainLayer = createDomainLayer( app );
    LayerAssembly persistenceLayer = createInfrastructureLayer( app );
    LayerAssembly authLayer = createAuth2Layer( app );
    LayerAssembly messagingLayer = createMessagingLayer( app );

    webLayer.uses( domainLayer );
    domainLayer.uses( authLayer );
    domainLayer.uses( persistenceLayer );
    domainLayer.uses( messagingLayer );
}

static LayerAssembly createWebLayer( ApplicationAssembly app ) throws AssemblyException
{
    LayerAssembly layer = app.layer( "web-layer" );
    createCustomerWebModule( layer );
    return layer;
}

static LayerAssembly createDomainLayer( ApplicationAssembly app ) throws AssemblyException
{
    LayerAssembly layer = app.layer( "domain-layer" );
    createCustomerDomainModule( layer );
    // :
    // :
    return layer;
}

static LayerAssembly createInfrastructureLayer( ApplicationAssembly app ) throws AssemblyException
{
    LayerAssembly layer = app.layer( "infrastructure-layer" );
    createPersistenceModule( layer );
    return layer;
}

static LayerAssembly createMessagingLayer( ApplicationAssembly app ) throws AssemblyException
{
    LayerAssembly layer = app.layer( "messaging-layer" );
    createWebServiceModule( layer );
    createMessagingPersistenceModule( layer );
    return layer;
}

static LayerAssembly createAuth2Layer( ApplicationAssembly application ) throws AssemblyException
{
    LayerAssembly layer = application.layer( "auth2-layer" );
    createAuthModule( layer );
    return layer;
}

static void createCustomerWebModule( LayerAssembly layer ) throws AssemblyException
{
    ModuleAssembly assembly = layer.module( "customer-web-module" );
    assembly.transients( CustomerViewComposite.class, CustomerEditComposite.class,
                         CustomerListViewComposite.class, CustomerSearchComposite.class );
}

static void createCustomerDomainModule( LayerAssembly layer ) throws AssemblyException
{
    ModuleAssembly assembly = layer.module( "customer-domain-module" );
    assembly.entities( CustomerEntity.class, CountryEntity.class );
    assembly.values( AddressValue.class );
}

static void createAuthModule( LayerAssembly layer ) throws AssemblyException
{
    ModuleAssembly assembly = layer.module( "auth-module" );
    new LdapAuthenticationAssembler().assemble( assembly );
    new ThrinkAuthorizationAssembler().assemble( assembly );
    new UserTrackingAuditAssembler().assemble( assembly );
}

static void createPersistenceModule( LayerAssembly layer ) throws AssemblyException
{
    ModuleAssembly assembly = layer.module( "persistence-module" );
    // Someone has created an assembler for the Neo EntityStore
    new NeoAssembler( "./neostore" ).assemble( assembly );
}