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

Core Bootstrap

code

docs

tests

Qi4j 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 Qi4j, 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.

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.0


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

Facilities

Assembly Specifications
Class Scanner

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 easiest 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 to define certain aspects. An example is the popular JdbmEntityStore, which Assembler can be used like;

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

Singleton Assembler

Every Qi4j 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 Qi4j 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.

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 Qi4j 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 Qi4j 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 Qi4j 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 );
}