Defining Layers
OiL's architectural layers are defined as two Lua modules.
The first one is an architectural module, whose name is prefixed by oil.arch
.
Generally, this module defines an abstract architecture that describe a set of components and their connections.
The other module is a builder module, whose name is prefixed by oil.builder
.
This module defines how to create and initialize the components of an architectural module.
Generally, the builder module defines how the components of the abstract architecture provided by the architectural module are implemented.
A layer may be defined only by an architectural module that simply defines new connections.
Moreover, a layer may be defined by a single builder module that replaces component implementations.
Architectural Modules
An architectural module must provide function assemble(components)
to connect the components defined in the layer.
Parameter components
is a table that maps the name of each component to the instance that must be assembled.
Ideally, the assemble
function should connect only the components provided and should not fail if some components are missing.
This is useful to allow the use of the module to build partial assemblies.
Although OiL does not impose any restriction on the way the components are implemented, the standard implementation is entirely developed using the support provided by LOOP's component models.
In particular, standard architectural modules additionally provide component templates for the components defined in the layer.
These templates are useful in creation of component factories as will be illustrated in the following sections.
The code below shows the definition of a fictional architectural module.
See files lua/oil/arch/*.lua
from the OiL's standard distribution for other examples of architectural modules using LOOP.
local component = require "oil.component" -- component model local port = require "oil.port" -- port model local arch = require "oil.arch" -- helper module module "oil.arch.basic.client" -- layer name is 'base.client' ClientBroker = component.Template{ broker = port.Facet, proxies = port.Receptacle, references = port.Receptacle, } ObjectProxies = component.Template{ proxies = port.Facet, invoker = port.Receptacle, } OperationInvoker = component.Template{ invoker = port.Facet, requester = port.Receptacle, } function assemble(components) -- avoids the 'components.' prefix and errors -- when some component instances are missing. arch.start(components) OperationInvoker.requester = OperationRequester.requests ObjectProxies.invoker = OperationInvoker.invoker ClientBroker.proxies = ObjectProxies.proxies ClientBroker.references = ObjectReferrer.references arch.finish(components) end
Builder Modules
The modules cited above just define a skeleton where component implementations are filled to complete the assembly defined by the layer.
The definition of the actual component implementations is defined by builder modules.
A builder module must provide function create([components])
that returns a table with instances of the components defined in the layer properly initialized.
Optional parameter components
is a table with component instances from other layers.
Ideally, function create
should not replace the instances from table components
by other instances.
Standard modules use the support provided by LOOP to define component factories using the templates defined by standard architectural modules.
The component implementations provided for factories are LOOP classes.
Such classes are packaged as Lua modules.
Below, there is the definition of a builder module that defines a set of factories of components for the architecture exemplified above and uses function oil.builder.create
to create component instances.
See files lua/oil/builder/*.lua
from OiL's standard distribution for examples of other builder modules using LOOP.
local arch = require "oil.arch.basic.client" -- arch module local builder = require "oil.builder" -- helper module module "oil.builder.base.client" -- layer name is 'base.client' ClientBroker = arch.ClientBroker{ require "oil.kernel.base.Client" -- packaged LOOP class } ObjectProxies = arch.ObjectProxies{ require "oil.kernel.base.Proxies" -- packaged LOOP class } OperationInvoker = arch.OperationInvoker{ require "oil.kernel.base.Invoker" -- packaged LOOP class } function create(components) -- create one instance from each factory provided -- unless the instance already exists in 'components'. return builder.create(_M, components) end
Assembler Procedure
Component assemblies are created by function oil.builder.build(flavor [, components])
.
The parameter flavor
is the flavor that defines the layers to be used in the creation.
For each layer, the function oil.builder.build
checks for the existence of a builder with that name in namespace oil.builder.*
.
If the builder module is found then its create
function is called to create component instances in table components
.
After all builder modules found are executed, a similar procedure is performed for architectural modules.
In other words, for each architecture module found, its function assemble
is called to establish the proper connections between components created.
The described assemble construction method allows us to define new architectural modules that define new connections between components as well as builder modules that might provide alternative component implementations for those provided by OiL's standard distribution. This model for extension of the OiL architecture and implementation allied to the possibility to reuse the standard classes provided by means of inheritance or composition, and all the flexibility provided by the Lua language makes OiL easily customizable for different needs and applications.