The Holy Grail of Business App Development
Tuesday, September 13, 2005
The Holy Grail of business application development is to be able to develop application code once and reuse it for many different vertical (or customer-specific) applications (some would say that "making lots of money" is the Holy Grail of business application development ;-) however, it is really just a by-product). There are different driving forces here:
- The developer wants to be able to create a generic business application that can be customized for different businesses by adding on "layers" or "contexts" of functionality that are specific to the requirements of the business or vertical industry that needs them (e.g. - one could develop a sales management system that had different "layers" of functionality for different industry verticals so that there might be "base" sales order functionality as well as functionality specific to the High-tech Electronics industry and other functionality that was specific to the Consumer Packaged Goods industry). This allows the developer to target a wider potential customer base with his product while minimizing the amount of code duplication that needs to be done for different customers/verticals. At the same time, the developer wants his application code to be clean and "targeted".
- The customer wants to be able to have a product that is custom-suited to his requirements but also wants to be able to take advantage of standard upgrades to the base code without having to retrofit them to his custom code.
- The application marketer wants to be able to sell to multiple markets but to have a product that looks and feels like it has been custom-written for each specific market.
- First of all, here is a diagramatic representation of the
"layers" that we will implement (taken from the
ContextL Overview):

Basically, we want to create a Person object as the base object in the "Root" layer. At the Employment layer, we will add Employer information (in this simple example, simply an employer name). At the Info layer, we will add additional information (in this simple example, simply a city name) to an object. In the example, we will add this additional Info layer information to both the Person (city where the Person lives) and Employer (city where the Employer is located). When we are looking at the Root context, we will only have the base Person detail available. When we are looking at the Employment context, we will have both the Person and Employer detail available. And, when we are looking at the Info context, all of the detail will be available. We will also create layer-specific methods that provide specializations of generic functions. - First of all, we need to start up ContextL:
CL-USER> (asdf:oos 'asdf:load-op :contextl) NIL
CL-USER> (in-package :contextl-user) #<Package "CONTEXTL-USER"> - Then, we define a Person class:
CX-USER> (define-layered-class person () ((name :initarg :name :layered-accessor person-name))) #<LAYERED-CLASS PERSON>
- Since we will be creating specialized display methods (as an
example of "layered" methods) for the
different class "contexts", we create a generic function and the
display method for the Person class:
CX-USER> (define-layered-function display-object (object)) #<STANDARD-GENERIC-FUNCTION LAYERED-FUNCTION-DEFINERS::|CONTEXTL-USER::DISPLAY-OBJECT| #x64A027E>
CX-USER> (define-layered-method display-object ((object person)) (print (list 'person :name (person-name object)))) #<STANDARD-METHOD LAYERED-FUNCTION-DEFINERS::|CONTEXTL-USER::DISPLAY-OBJECT| (T PERSON)> - I'll use myself as some sample data. Instantiate the object and
display it:
CX-USER> (defparameter *bill* (make-instance 'person :name 'william.clementson)) *BILL*
CX-USER> (display-object *bill*) (PERSON :NAME WILLIAM.CLEMENTSON) - Now, create the Employment layer:
CX-USER> (deflayer employment-layer) #<STANDARD-CLASS EMPLOYMENT-LAYER>
- There's an Employer class in the Employment layer along with a
method for displaying the Employer object:
CX-USER> (define-layered-class employer :in-layer employment-layer () ((name :initarg :name :layered-accessor employer-name))) #<LAYERED-CLASS EMPLOYER>
CX-USER> (define-layered-method display-object :in-layer employment-layer ((object employer)) (print (list 'employer :name (employer-name object)))) #<STANDARD-METHOD LAYERED-FUNCTION-DEFINERS::|CONTEXTL-USER::DISPLAY-OBJECT| (EMPLOYMENT-LAYER EMPLOYER)> - Let's create an instance of Employer and display it:
CX-USER> (defparameter *tech.coop* (make-instance 'employer :name 'www.tech.coop)) *TECH.COOP*
CX-USER> (with-active-layers (employment-layer) (display-object *tech.coop*)) (EMPLOYER :NAME WWW.TECH.COOP) - Now, we'll specify that the Person class can have some attributes
defined in the Employment layer (namely, an Employer) and we'll define a specialized method for displaying the Employment layer
details for the Person class as well as assign a value for the employer:
CX-USER> (define-layered-class person :in-layer employment-layer () ((employer :initarg :employer :layered-accessor person-employer))) #<LAYERED-CLASS PERSON>
CX-USER> (define-layered-method display-object :in-layer employment-layer :around ((object person)) (append (call-next-method) (print (list :employer (display-object (person-employer object)))))) #<STANDARD-METHOD LAYERED-FUNCTION-DEFINERS::|CONTEXTL-USER::DISPLAY-OBJECT| :AROUND (EMPLOYMENT-LAYER PERSON)>
CX-USER> (with-active-layers (employment-layer) (setf (person-employer *bill*) *tech.coop*)) #<EMPLOYER #x64D9F4E> - Let's have a look at what the Person class looks like if we access
it from the Root layer and from the Employment layer:
CX-USER> (display-object *bill*) (PERSON :NAME WILLIAM.CLEMENTSON)
CX-USER> (with-active-layers (employment-layer) (display-object *bill*)) (PERSON :NAME WILLIAM.CLEMENTSON) (EMPLOYER :NAME WWW.TECH.COOP) - Now, we'll define the equivalent for the Info layer and specify
that there is Info layer information for both the Person class and
the Employer class:
CX-USER> (deflayer info-layer) #<STANDARD-CLASS INFO-LAYER>
CX-USER> (define-layered-class info-mixin :in-layer info-layer () ((city :initarg :city :layered-accessor city))) #<LAYERED-CLASS INFO-MIXIN>
CX-USER> (define-layered-method display-object :in-layer info-layer :around ((object info-mixin)) (append (call-next-method) (print (list :city (city object))))) #<STANDARD-METHOD LAYERED-FUNCTION-DEFINERS::|CONTEXTL-USER::DISPLAY-OBJECT| :AROUND (INFO-LAYER INFO-MIXIN)>
CX-USER> (define-layered-class person :in-layer info-layer (info-mixin) ()) #<LAYERED-CLASS PERSON>
CX-USER> (define-layered-class employer :in-layer info-layer (info-mixin) ()) #<LAYERED-CLASS EMPLOYER> - Now that we've got everything defined, let's try creating our object
definitions in a top-down manner and subsequently displaying the
information at the individual layers (in order to illustrate both the
different object attributes that are applicable at the different
layers and how different methods are called depending on the
"context" at any given time):
CX-USER> (defparameter *tech.coop* (make-instance 'employer :name 'www.tech.coop :city 'vancouver)) *TECH.COOP*
CX-USER> (defparameter *bill* (make-instance 'person :name 'bill.clementson :employer *tech.coop* :city 'west.vancouver)) *BILL*
CX-USER> (display-object *bill*) (PERSON :NAME BILL.CLEMENTSON)
CX-USER> (with-active-layers (employment-layer) (display-object *bill*)) (PERSON :NAME BILL.CLEMENTSON) (EMPLOYER :NAME WWW.TECH.COOP)
CX-USER> (with-active-layers (employment-layer info-layer) (print (display-object *bill*))) (PERSON :NAME BILL.CLEMENTSON) (:CITY WEST.VANCOUVER) (EMPLOYER :NAME WWW.TECH.COOP) (:CITY VANCOUVER) (PERSON :NAME BILL.CLEMENTSON :CITY WEST.VANCOUVER :EMPLOYER (EMPLOYER :NAME WWW.TECH.COOP :CITY VANCOUVER)) - If we want to access class information at a specific layer but
hide some of the information that is available at a given layer, we
can do that too:
CX-USER> (with-active-layers (info-layer employment-layer) (with-inactive-layers (info-layer) (display-object *bill*))) (PERSON :NAME BILL.CLEMENTSON) (EMPLOYER :NAME WWW.TECH.COOP)

