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.

Copyright (C) 2004-2008 Tecgraf, PUC-Rio

This project is currently being maintained by Tecgraf at PUC-Rio.