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;
@Override public void assemble( ModuleAssembly module ) throws AssemblyException { module.objects( MyObject.class ).visibleIn( Visibility.layer ); }
@Override public void assemble( ModuleAssembly module ) throws AssemblyException { module.transients( MyTransient.class ).visibleIn( Visibility.layer ); }
@Override public void assemble( ModuleAssembly module ) throws AssemblyException { module.values( MyValue.class ).visibleIn( Visibility.layer ); }
@Override public void assemble( ModuleAssembly module ) throws AssemblyException { module.entities( MyEntity.class ).visibleIn( Visibility.layer ); }
@Override public void assemble( ModuleAssembly module ) throws AssemblyException { module.services( MyService.class ).visibleIn( Visibility.layer ); }
@Override public void assemble( ModuleAssembly module ) throws AssemblyException { module.services( MyService.class ).taggedWith( "foo", "bar" ); }
@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 ); }
@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" ); }
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 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 ); }
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.
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 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 ); }