Section 4.4. INTER-OBJECT CROSSCUTTING


4.4. INTER-OBJECT CROSSCUTTING

From a software engineering perspective, the distinction between intra-object and inter-object crosscutting is more fundamental than just the expressiveness of the pointcut mechanism. Typically, software engineers consider classes or concerns as the unit of development and change. In addition, all specifications that are part of a single concern specification are assumed to be potentially interdependent and are likely to be developed and evolve mutually, typically by one or a few closely cooperating software engineers. In accordance with this view, software engineers should design filters as an integral part of the concerns.[10] This is a sharp contrast with inter-object crosscutting concerns, which are assumed to be developed independently, typically at a different time and potentially by different software engineers. As a result, the interfaces between the crosscutting concerns and the concerns that are affected by them are substantially more critical. The CF model reflects this distinction by introducing a different ("higher-level") mechanism for inter-object crosscutting. Its basic concept is to use groups of filters and related definitions, called filter modules, which are composed with concerns through the so-called superimposition[11] mechanism. The superimposition specifications describe the locations within the program where concern behavior is to be added in the form of filter modules.

[10] This may seem in contradiction with the idea that filters are a modular extension to the object-oriented model, but as we explain in the sidebar "Common Misconceptions about Composition Filters," the fact that the language model is an extension does not mean that applications should be designed by first designing objects and adding the interface part afterward.

[11] Our notion of superimposition bears resemblance to, but is truly distinct from, the technique of superimposition as proposed by Bougé and Francez [14] and Katz [24]. Conceptually, superimposition as proposed by Bosch [13] is very close, but it refers to instantiation-time composition and does not include any support for crosscutting.

4.4.1. Evolution Step 2: Adding Workflow Management

To explain the mechanism of superimposition, we introduce a second extension to our running example. In the design of the example so far, each clerk (i.e., human user) has to choose which task is to be executed next for a given document. Accordingly, the document is forwarded to the appropriate task object. To enforce a better-managed business process, we introduce a new concern, called WorkFlowngine.

Based on a workflow specification, the concern WorkFlowEngine is responsible for implementing the task selection process. In the current version, task selection is implemented within the method forward(). This method further calls on the method selectTask(), which returns a task selected among a set of alternatives. The method selectTask is implemented specifically for each TaskProcessor concern.

The concern WorkFlowEngine declares the attribute workFlowSpec, which represents the process to be enforced. This concern also implements the method choose(), which returns the task to be forwarded based on the workflow specification and the set of alternatives available.

The method forward() first calls selectTask() of WorkFlowEngine, followed by an invocation on choose(). Adding workflow management to the system in an object-oriented implementation would require redefinition of method forward() for all task classes. The method forward() cannot be implemented by a superclass since every task implements this method in a specific manner. An aspect-oriented implementation is a preferable solution.

We use this example to illustrate how crosscutting can be expressed using the CF model. As shown in Listing 4-2, this concern consists of four parts: (1) a (crosscutting) filter module named UseWorkFlowEngine that ensures that all relevant concerns in the application actually use the engine for determining the next tasks (lines 210); (2) a (shared) filter module named Engine that implements the behavior of the workflow engine itself (lines 1119); (3) a superimposition specification (lines 2028); and (4) the implementation of the necessary functionality (lines 2942).

Listing 4-2. Specification of WorkFlowEngine, illustrating a crosscutting CF concern
  (1)  concern WorkFlowEngine begin    //introduces global workflow control  (2)    filtermodule UseWorkFlowEngine begin   //declares crosscutting code  (3)      externals  (4)        wfEngine : WorkFlowEngine;      //*declare* a shared instance  (5)      methods                   //declare the intercepted- messages  (6)        Set selectTask(Document);  (7)        workflow(Message);  (8)      inputfilters  (9)        redirect: Meta = {[selectTask]wfEngine.workflow}; (10)    end filtermodule useWorkFlowEngine; (11)    filtermodule Engine begin      //defines interface of workflow engine (12)      methods (13)        workflow(Message); (14)        choose(Document, TaskProcessor, Set) : (15)        TaskProcessor; (16)        setWorkFlow(WorkFlow); (17)      inputfilters (18)        disp : Dispatch = { inner.* };      //accept all my methods (19)    end filtermodule engine; (20)    superimposition begin      //defines actual crosscutting composition (21)      selectors        //queries set of all instances in the system (22)        allTasks = {*=RequestHandler, (23)                    *=MedicalCheck, *=Payment, (24)                     *=RequestDispatcher, *=Approval }; (25)      filtermodules (26)        self <- Engine; (27)        allTasks <- UseWorkFlowEngine; (28)    end superimposition; (29)    implementation in Java; (30)      class WorkFlowEngine { (31)        WorkFlow workFlowRepr; (32)        void workflow(Message mess { (33)          Document doc = mess.getArg(1); (34)          TaskProcessor curTask = mess.target(); (35)          Set alternatives = mess.send(); (36)          mess.return( choose(doc, curTask, alternatives) ); (37)       }; (38)     TaskProcessor choose(Document, TaskProcessor, Set){ ... }; (39)     void setWorkFlow(WorkFlow wf) { ... }; (40)      } (41)    end implementation; (42)  end concern WorkFlowEngine; 

The filter module UseWorkFlowEngine defines a filter of type Meta, which intercepts the calls on the method selectTask() and sends them in reified[12] form to the external object wfEngine as the argument of a message workflow(). This filter module represents the crosscutting behavior that must be superimposed upon all the TaskProcessor concern instances. In this case, the crosscutting behavior consists mostly of connecting the various task instances to the central workflow engine.

[12] A reified form is a representation of the message as an object (an instance of the Message concern) from which information about the message, such as target, selector, arguments, and sender, can be retrieved and modified.

The filter module Engine and the implementation part together implement the workflow engine. In addition to some methods for accessing and manipulating the workflow representation (here only setWorkFlow() is shown), this filter module defines the method workflow(). This method selectTask() first determines the next task that should handle the document, then modifies the corresponding argument of the message object, and finally fires the message so that the message continues its original execution with an updated argument.

The superimposition clause specifies how the concerns crosscut each other. The superimposition clause starts with a selectors part that specifies a number of join point selectors, abstractions of all the locations that designate a specific crosscut. Selectors are defined as queries over the instance space, expressed using OCL (Object Constraint Language), which is part of the UML specification [31]. The concern WorkFlowEngine defines a single selector named allTasks. This selector repeatedly uses the expression *=<ConcernName> to specify all objects that are instances of the various classes that represent tasks. The selectors part can be followed by a number of sections that can specify respectively which objects, conditions, methods, and filter modules are superimposed on locations designated by selectors. In this example, the filter module Engine is superimposed upon self. This means that instances of WorkFlowEngine include an instance of the filter module Engine. In addition, the filter module useWorkFlowEngine[13] is superimposed on all the instances defined by the selector allTasks.

[13] Names on the right side can be prefixed with the concern name followed by a double semicolon. Otherwise, the prefix "self::" is assumed.

The sidebar, "Unification of Classes, Filter Modules, and Superimposition into Concerns," discusses the effects of combining filter modules, the superimposition clause, and the implementation clause within a single concern and the ramifications of omitting some of these parts.

Unification of Classes, Filter Modules, and Superimposition into Concerns

The composition filters model adopts a single abstraction as the major module concept, the concern abstraction. In this sidebar, we discuss some of the ramifications of this design decision.

A concern abstraction consists of three optional parts: (1) the filter module specifications; (2) the superimposition specification; and (3) the implementation of the behavior. This is illustrated in the Figure 4-6, especially by the concern on the left side of the picture.

Figure 4-6. The elements of concerns and their mapping to concern instances.


The figure shows on the left side a concern specification for concern A, consisting of two filter modules, a superimposition specification, and an implementation. On the right side of the figure, another concern specification, B, is depicted that consists only of a filter module and a superimposition specification. In the middle, an example concern instance, an A, is shown, which is an instance of concern A, including the implementation defined by A and one filter module superimposed by A, as well as an additional filter module that has been superimposed by B.

As a result of the unification into concerns, the model does not distinguish between "aspect" and "base" module abstractions, which has a couple of advantages: (1) It makes the model 'cleaner': no different syntax and/or semantic rules need to be defined for the various possible configurations. (2) This structure allows for modeling base-level functionality and state together with crosscutting behavior into a single module abstraction (rather than two separate modules for respectively the "aspect" and the "base" level abstractions).

This offers a degree of symmetry [22] between concerns that avoids design compromises and allows for more stable designs where the decisions about what to model as base and what to model as aspect abstractions can be avoided. However, depending on the particular implementation, the structure of the run-time model may be different; for example, when the superimposition specifications are resolved statically, there are no equivalent abstractions at runtime.

Not all three parts of the concern specification are obligatory. Table 4-1 outlines the various possible combinations of leaving out one or more parts and summarizes their intuitive meaning.

Table 4-1. The Parts of a Concern and Their Mapping to Concern Instances

Filter Module(s)

Super-Imposition

Implementation

Explanation

c.f. conventional class

only to self

c.f. conventional composition filters class

Crosscutting concern with implementation

'Pure' crosscutting concern, no implementation

c.f. abstract advice without crosscutting definition

Superimposition only (of reused filter specs)

CF class or aspect with only reused filter specs



4.4.2. Evolution Step 3: Adding Logging

One important concern of the workflow system is monitoring the process, detecting the bottlenecks, and rescheduling and/or reallocating resources when necessary. Determining which methods need to be monitored is difficult a priori (i.e., at compile time) since it depends on the purpose of monitoring. Therefore, all interactions among objects may potentially need logging. More precisely, logging the reception of messages by an instance is sufficient. For each instance, actual logging of the message receptions can be turned on or off at runtime.

The definition of concern Logging in Listing 4-3 starts with the filter module NotifyLogger, which defines the crosscutting behavior needed to collect the information to be logged from all over the application. Logging is implemented by sending all received messages as objects to the global object logger using a Meta filter. The Logging concern creates an internal Boolean object logOn for every instance, which is used to enable or disable the logging of messages. More details of the implementation are shown in Listing 4-3. Note that logging is also supported for the methods of the WorkFlowEngine concern, but we exclude the instances of Logging, especially to avoid recursive logging of the log() messages.

Listing 4-3. Specification of the Logging concern
  (1)  concern Logging begin                //introduces centralized logger  (2)    filtermodule NotifyLogger begin    //declares the crosscutting code  (3)      externals  (4)        logger : Logging;   //declare a shared instance of this concern  (5)      internals  (6)        logOn : boolean;      //created when the filtermodule is imposed  (7)      methods  (8)        loggingOn();                 //turn logging for this object on  (9)        logginOff();                //turn logging for this object off (10)        log(Message);         //declared here for typing purposes only (11)      conditions (12)        LoggingEnabled; (13)      inputfilters (14)        logMessages : Meta = (15)        { LoggingEnabled=>[*]logger.log }; (16)        dispLogMethods : Dispatch = (17)        { loggingOn, loggingOff }; (18)    end filtermodule NotifyLogger; (19)    filtermodule Logger begin       //defines interface of logger object (20)      methods (21)        log(Message);   //and methods for information retrieval from log (22)      inputfilters (23)        disp : Dispatch = (24)        { inner.* };                       //accept all my own methods (25)    end filtermodule Logger; (26)    superimposition begin (27)      selectors (28)        allConcerns = { *->reject(oclIsTypeOf(Logging)) }; (29)            //selects all concern instances except instances of Logging (30)      methods (31)        allConcerns <- {loggingOn(), (32)        loggingOff() };                                  //bind methods (33)      conditions (34)        allConcerns <- LoggingEnabled;                //bind condition (35)      filtermodules (36)        allConcerns <- NotifyLogger; //superimpose NotifyLogger on all (37)       self <- Logger;       //superimpose Logger filtermodule on self (38)    end superimposition; (39)    implementation in Java; (40)      class LoggerClass { (41)        boolean LoggingEnabled() { return logOn }; (42)        void loggingOn()  { logOn:=true; }; (43)        void loggingOff()  { logOn:=false; }; (44)        void log(Message mess) { ... }; // collect information & store (45)      } (46)    end implementation; (47)  end concern Logging; 

The shared part of the logging functionality is defined by filter module Logger. This declares the method log(), which takes an instance of Message as an argument and logs the information about the message. Typically, a range of methods for retrieving and/or displaying the logged information should be declared by Logger as well; we omit these for brevity. The filter module also contains a filter definition disp that ensures that these method(s) are made available on the interface of the Logging concern.

The superimposition part of this example is interesting since it involves a slightly more complicated binding process. This is illustrated by Figure 4-7. The set of join points where logging must be added is defined by allConcerns. This selector uses an OCL expression to select all instances in the system, except for the instances of concern Logging. The last part of the superimposition clause defines the super imposition of the Logger filter module to the Logging concern itself and the superimposition of the NotifyLogger to the join points defined by the allConcerns selector. The latter filter module declares an external global instance of Logging named logger, an internal logOn, which is created for each superimposed filter module, and several methods. The loggingOn() and loggingOff() methods must be available for execution in the context of the superimposed instance (because of the dispatch filter dispLogMethods), but they are declared by concern Logging. Therefore, the superimposition specification must explicitly bind the declared methods to the implementation within concern Logging, which is done in lines 31-32. Similarly, the condition LoggingEnabled is declared in the filter module NotifyLogger (line 12). When this condition is evaluated in the logMessages filter in lines 1415, normally the condition would be searched in the local context. Therefore, in line 34, the implementation of the LoggingEnabled condition from concern Logging is bound to all other concerns.

Figure 4-7. Illustration of superimposition and binding of filter module NotifyLogger.




Aspect-Oriented Software Development
Aspect-Oriented Software Development with Use Cases
ISBN: 0321268881
EAN: 2147483647
Year: 2003
Pages: 307

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net