Composing Concerns with a Framework Approach
Constantinos A. Constantinides
1,2and Tzilla Elrad
21
Mathematical and Computer Sciences Department Loyola University Chicago
[email protected]
2
Concurrent Programming Research Group Computer Science Department Illinois Institute of Technology, Chicago
elrad@ iit.edu
Abstract
As concurrent software systems become larger, the inter- action of their components is becoming more complex.
This interaction may limit reuse, making it difficult to vali- date design and correctness and perhaps forcing reengineering of these systems in order to meet future re- quirements. In order to reduce this complexity and to build stable and adaptable concurrent software systems, we pre- sent an approach that emphasizes the separation of the interaction components from the functional components.
This approach targets the design and development of open concurrent software systems where the functional re- quirements, concurrency requirements, and structural re- quirements can be easily designed, implemented, and re- used.
1. Introduction
As the size of software systems increases, their design has reached a complexity that requires software engineers to revisit the principle of separation of concerns, which refers to the realization of system concepts as separate software units. This principle is essential to software development since its benefits include better analysis and understanding of systems, easy adaptability, maintainability and high de- gree of reusability. At the same time, separation of con- cerns can only be beneficial if the different concerns can be effectively composed to produce the overall system. In order to support separation of concerns the OOP paradigm seems to work well only if a problem can be described with relatively simple interfaces among objects. Unfortu- nately, this is not the case when we move from sequential programming to concurrent and distributed programming, as the component interaction violates simple object inter- faces. As a result, the benefits associated with OOP no longer hold. One of the reasons is the inherent structure of
today’s software systems that conceptually does not lead itself to be safely decomposed. Reengineering of these systems is needed in order to meet future changes in re- quirements. This component interaction is based on a number of properties such as synchronization, scheduling, and fault tolerance. These are properties that affect the per- formance and semantics of the system, and the term “code- tangling” (Kiczales et al., 1997) is referred to as the phe- nomenon where the implementations of such properties (called aspects) cut across groups of functional compo- nents. This code-tangling and the resulting high coupling of components destroys modularity, making the source code difficult to develop and difficult to understand. It also limits reuse, making the source code difficult to evolve. It further makes programs more error prone. In essence, it destroys the quality of the software. In (Bergmans and Ak- sit, 2000) the authors refer to these phenomena as “compo- sition anomalies”. This composition anomaly requires a shift in the methodologies used to separate concerns.
There are currently a number of different proposals that
address advanced separation of concerns. Although the
mechanism of weaving lies at the center of their differ-
ences, they all have the same goals: Better abstraction than
what has so far been achieved with current methodologies,
higher modularity and higher degree of reuse. Some of the
open issues regarding the design and implementation of
architectures lie at the level at which aspects and compo-
nents integrate. Should the integration be at the source
code or maybe at the object code? How do we better ex-
press aspectual properties of systems? Should we use an
aspect language or a framework approach? To what de-
gree should an aspect oriented architecture support an open
system? Should it enable dynamic adaptability and ex-
pandability? Should it support reusability? Should it fur-
ther enable formal verification of system properties? In
this paper we present the architecture of a framework that supports separation of concerns and addresses these issues.
2. Requirements for Concurrent Systems
In (Constantinides and Elrad, 2000c) we discussed a num- ber of desired requirements for concurrent systems in order to provide an advanced separation of concerns. E- commerce and online client-server applications, like trou- ble-ticketing systems, on-line reservation systems, time- card reporting systems, and online auctions, are becoming increasingly popular. To ensure high availability and reli- ability, these systems are often designed according to the design methodology of open software systems. Generally, the functional components of a concurrent system are sta- ble. On the other hand, the interaction components are volatile and reactive to the environment needs. During the analysis phase of these systems, it is important to identify and isolate the functional components from the interaction components. Interaction components are used to address requirements like load balancing, fault tolerance, through- put, security, audits, location transparency, concurrency, and coordination. Object-oriented techniques have indeed supported reuse of these components to the degree that it satisfied software developers but failed to satisfy the soft- ware system architects. Software architects are very much interested in the common collaboration for a cluster of components. Object-oriented design techniques have showed their effectiveness to capture functional require- ments into components. There is, however, little evidence that OO design techniques have fully resolved the software reusability problem. Recent research in concurrent object- oriented programming has showed that mixing the concurrency code along with the functional code may im- pede reuse and make it very difficult to extend or reuse the functional or concurrency components in isolation from each other. The structural properties (ilities), of the soft- ware systems are usually scattered across the functional components. In OOP, objects are the basic units of com- putation and the software system is composed of a set of objects and a set of interaction scenarios. These objects may play the role of a servant object, a client object, or perhaps both roles. These objects may reside on the same host or distributed across the network.
3. The Software Architecture of an Aspect- Oriented Framework
In the Aspect Moderator framework (Figure 1), a concur- rent object is represented as a cluster of co-operating classes that handle the creation of aspects as well as the
interaction between components and aspects. The subse- quent subsections will discuss the analysis and design phases of the framework.
Aspect Moderator EvaluateAspect
RegisterAspect ASPECT
FACTORY CreateAspect
Security Security Synchronization Synchronization Scheduling Scheduling
Proxy
Functional Component
SERVICE A
SERVICE B ASPECT
BANK
Figure 1. Architecture of the Aspect Moderator Framework 4. Analysis Phase
This section will discuss the analysis phase of the frame- work. As an example, we will use the trouble-ticketing system. This is an application where clients open (place) tickets on a server, and assign (retrieve) tickets from a server. This application is based on the producer consumer protocol with the use of a bounded buffer. The framework can be studied in two different stages: initialization, and method invocation.
4.1 Initialization Phase
During initialization, the proxy to the functional compo- nent (TicketServer) will request from a special object (As- pectFactory) the creation of two objects that capture the synchronization constraints that are associated with serv- ices (methods) open() and assign() (Figure 2). Upon crea- tion, objects OpenSynchronizationAspect and AssignSyn- chronizationAspect are registered (stored) in the Aspect- Moderator object that will need to reference them during method invocation in order to coordinate their interaction with the functional components.
4.2 Method Invocation
The proxy to the functional component is responsible to
evaluate each of aspects that are associated with each one
of the services that are defined on the functional compo-
nent. The result of this evaluation may cause the service to
be invoked, cause the caller to wait, or abort the activation.
Before executing each on the functional component, the proxy object calls the moderator object to evaluate the as- pect code that is associated with that method (Figure 3).
Methods in the functional component that are associated with aspect objects are referred to as participating meth- ods. Every participating method is guarded by pre- activation and post-activation phase.
:TicketProxy
createAspect (Open, Sync)
:OpenSyncAspect new
registerAspect () :Aspect
Moderator :AspectFactory
createAspect (Assign, Sync)
:AssignSync Aspect new
registerAspect ()
Figure 2. Initialization Phase
Upon a message reception that involves a participating method, the proxy will delegate the responsibility to the aspect moderator to evaluate the preactivation phase. The moderator will, in turn, evaluate all required aspects of the participating method by calling the precondition of all re- quired aspects. Upon successful return of the preactivation phase, the proxy will call the actual participating method.
Once execution is complete, the proxy will initiate the postactivation phase, by delegating responsibility to the moderator to evaluate the postactivation on the given method. During this phase, the aspect moderator will call the postaction of the required aspects.
5. Design Phase
This section will describe the design phase of the frame- work.
5.1 Initialization: Deploying the Factory Method Pat- tern
The Factory Method pattern (Gamma et al., 1995) can be used to create the required aspects for the participating methods of the functionality class. All aspect objects im- plement the AspectIF interface. The intent of the Factory
Method pattern is to define an interface for creating an aspect object, but let the requestor decide which class to instantiate.
:TicketProxy :Aspect Moderator
:OpenSync
Aspect :Ticket
ACTOR put (ticket)
preActivation (open, sync)
precondition()
[precondition == true]
open (ticket)
postaction() postActivation (open, sync)
Figure 3. Method Invocation Phase
Figure 4 shows a class diagram with the roles that classes and interfaces play in the Factory Method pattern. Using the organization shown, a TicketServerProxy object calls method createAspect() of an object that implements the AspectfactoryIF interface by passing some parameters that tell that method which class that implements the AspectIF interface to instantiate.
TicketServerProxy
ConcreteAspect
<<interface>>
AspectIF <<interface>>
AspectFactoryIF
Creates
Requests-creation
creator *
precondition():int postaction():void
1 requestor 1
*
createAspect (String methodID, String aspect, TicketServerProxy component):object
AspectFactory
createAspect (String methodID, String aspect, TicketServerProxy component):object precondition():int
postaction():void Uses
Figure 4. Creating Aspects Using the Factory Method
5.1.1 Participants of the Factory Method Pattern The participants of the Factory Method pattern are as fol- lows:
• TicketServerProxy: It acts as a creation requestor for aspect objects.
• AspectFactoryIF: This is an application-independent interface. It declares the Factory Method, which re- turns an object of type AspectIF by taking whatever arguments are needed to deduce the class to instanti- ate.
• AspectFactory: This is an application-specific class. It implements the Factory Method to return an instance of ConreteAspect.
• AspectIF: defines the interface of objects the Factory Method creates.
• ConcreteAspect: This is a concrete class instantiated by the objects that participate in the Factory Method pattern. It implements the AspectIF interface.
5.1.2 Code Example: Initialization Phase
The constructor of TicketServerProxy contains the code to request 1) the creation of the two aspect objects, and 2) their registration with the aspect moderator object (Figure 5). The call from the TicketServerProxy to the Aspect- Factory passes a number of arguments that define the type of aspect instantiation (Figure 6).
TicketServerProxy (AspectModerator moderator, AspectFactory factory) {
…
moderator.registerAspect(OPEN, SYNC,
factory.create(OPEN, SYNC, this));
moderator.registerAspect(ASSIGN, SYNC,
factory.create(ASSIGN, SYNC, this));
}
Figure 5. Constructor of TicketServerProxy Class
public class AspectFactory implements AspectFactoryIF {
…
public Object create (String methodID, String aspect, TicketServerProxy component) { if (methodID.equals("Open"))
if (aspect.equals("Sync"))
return new OpenSynchronizationAspect(component);
if (methodID.equals("Assign")) if (aspect.equals("Sync"))
return new AssignSynchronizationAspect(component);
return null;}
Figure 6. AspectFactory Class
The implementation of the OpenSynchronizationAspect is defined by precondition() and postaction(). Method pre- condition() is called during the pre-activation part of method invocation. During precondition() the constraints dictate that if the shared object (TicketServer) is not full, then the method returns true. The postaction() method will be called during the post-activation part of method invoca- tion phase and it will ensure the incrementing of the count- ers of the shared resource.
public class OpenSynchronizationAspect implements AspectIF { public TicketServerProxy component;
protected int ActiveOpen = 0;
…
public int precondition() {
if ((component.noitems < component.capacity) && (ActiveOpen == 0)) {
++ActiveAssign;
++component.noitems;
// retutrn resume }
else { // return blocked}
}
public void postaction() { --ActiveOpen;
component.assignPtr = (component.assignPtr + 1) % component.capacity;
}}
Figure 7. OpenSynchronizationAspect Class As aspect objects are first class abstractions (values), they can be stored in an array within the aspect moderator ob- ject. Further, they can be referenced (accessed) from the array. In examining the usage and importance of aspect registration, we introduce the concept of an aspect bank, which provides a hierarchical two-dimensional composi- tion of the system in terms of aspects and components.
Upon creation of the aspect objects, the proxy will call the aspect moderator object to store (register) these aspect objects for reference during the next (method invocation) phase. The call to create() method will return a reference to an instance of an aspect class. This reference is param- eterized to method registerAspect() of the moderator ob- ject. Method registerAspect() will simply create an entry in a two dimensional array within the moderator object to be used for future reference (Figure 9).
5.1.3 Method Invocation Phase
Figure 10 shows the guarding of a participating method
between methods preActivation() and postActivation() that
are defined by the AspectModerator class (Figure 11).
public void registerAspect (String methodID, String aspect, Object aspectObject) {
if (methodID.equals("Open") && aspect.equals("Sync")) { aspectArray[open][sync] = aspectObject;
}
// similarly for assign() }
Figure 9. Registration of Aspect Objects
public void open (Object the_value) {
if (moderator.preActivation(OPEN) == RESUME) { super.open(the_value);
moderator.postActivation(OPEN);
}
else // error }
// similarly for assign() }
Figure 10. Guarded Methods in TicketServerProxy
public int preActivation(String methodID) { int result = ERROR;
if (methodID.equals("Put")) { synchronized(PutWaitingQueue) {
synchronized(this) {
result = ((PutSynchronizationAspect)
aspectArray[put][sync]).precondition();
}
while (result == BLOCKED ) { try {
PutWaitingQueue.wait ();
synchronized(this) {
result = ((PutSynchronizationAspect)
aspectArray[put][sync]).precondition();
}
} catch (Exception exception) { return AspectModerator.ABORT;
} } //while } synchronized return result;
} // put
//similarly for assign return result;
}
public void postActivation(String methodID) { if (methodID.equals("Open")) {
synchronized(AssignWaitingQueue) {
synchronized(this) { ((OpenSynchronizationAspect) aspectArray[open][sync]).postcondition();
}
AssignWaitingQueue.notify();
} // synchronized queue } // open
// similarly for assign }
Figure 11. Illustration of Pre- and Post-activation
Method preActivation() will call precondition() in the method’s corresponding synchronization aspect. During precondition(), guards will validate the synchronization constraints of the invoked method, returning
RESUMEupon success. After successful return, the AspectModerator will return a
RESUMEto the FunctionalityProxy that will acti- vate the method in the sequential object. The completion of the method execution will initiate a call by the Ticket- ServerProxy to the AspectModerator’s postActivation phase. During postActivation(), there is a call to the post- action() of the method's synhronization aspect during which synchronization variables are updated. The proxy- moderator object pair coordinate functional and aspectual behavior by handling their interdependencies. The UML class diagram of the aspect moderator framework is shown in Figure 12.
Component
ConcreteAspect
<<interface>>
AspectIF
<<interface>>
AspectFactoryIF
Creates Requests-creation
creator *
precondition():int postcondition():void
1 requestor 1
*
createAspect (String methodID, String aspect, Application component):object ComponentProxy
<<interface>>
AspectModeratorIF preActivation(String methodID):int postActivation(String methodID):void registerAspect (String methodID, String aspect,
Object aspectObject):void
int preActivation(String methodID):int postActivation(String methodID):void registerAspect (String methodID, String aspect,
Object aspectObject):void AspectModerator
<<interface>>
AspectFactoryIF
createAspect (String methodID, String aspect, Application component):object
AspectFactory
Uses Uses
creator * Uses
precondition():int postcondition():void
Figure 12. Class Diagram of the Aspect Moderator 5.3 Provision of Adaptability
The key to a successful software architecture is to ensure that the software system is adaptable enough to meet future requirements.
Let a new requirement state that authentication should be
introduced to the system. The general architecture of the
aspect moderator ensures adaptability both in terms of
components and aspects. In the trouble ticketing system, an
ExtendedTicketServerProxy, will request the creation of
two authentication aspects from an extended factory com-
ponent (Figure 13).
public class ExtendedTicketProxy extends TicketProxy { ..
static ExtendedAspectModerator moderator;
static ExtendedAspectfactory factory;
ExtendedTicketServerProxy (ExtendedAspectModerator m, ExtendedAspectFactory af) { ...
moderator.registerAspect(OPEN, AUTHENTICATE, factory.create(OPEN, AUTHENTICATE, this));
moderator.registerAspect(ASSIGN, AUTHENTICATE, factory.create(ASSIGN, AUTHENTICATE, this));
}...}
Figure 13. ExtendedTicketServerProxy
As a consequence of the addition of a new aspect, a re- quest to a participating method will now have to be guarded by preactivation of authentication (done by the extended aspect moderator) followed by preactivation of synchronization. Only when both are true, then execution may proceed. The execution of the actual method is fol- lowed by the postactivation of synchronization followed by postactivation of authentication (Figure 14).
public void open (Object the_value) {
if (moderator.preActivation(OPEN) == RESUME) { super.open(the_value);
moderator.postActivation(OPEN);
}
else {System.out.println ("ABORT");}
}
public Object assign() {
Object the_return_value = null;
if (moderator.preActivation(ASSIGN) == RESUME) { the_return_value = super.assign();
moderator.postActivation(ASSIGN);
}
else {System.out.println ("ABORT");}
return the_return_value;
}
Figure 14. ExtendedTicketServerProxy
The implementation of the ExtendedAspectFactory is shown in Figure 15. Figure 16 illustrates the registration of the two authentication aspect objects in the extend- edAspectModerator object. Figures 17, and 18 illustrates methods preActivation() and postActivation() respectively in the ExtendedAspectModerator that handle the authenti- cation aspect.
6. Conclusion
In this paper we presented an approach for the design and development for open concurrent software systems. The Aspect Moderator framework supports a high level of
separation concerns, as it manages to retain separation in all stages of the development. From analysis we emphasize the decoupling between object concurrency constraints and object functionality. Functional and non-functional re- quirements can be easily designed, implemented, tested and reused. The Aspect Moderator framework comple- ments the object-oriented and component-oriented tech- nology in order to promote code reuse and to support adaptability of concurrent software systems.
public class ExtendedAspectFactory extends AspectFac- tory {
public static final String AUTHENTICATE= "Authenti- cate";
public ExtendedAspectFactory () {}
public Object create (String methodID, String aspect, ExtendedTicketServerProxy compo- nent) {
if (methodID.equals("Open")) if (aspect.equals("Authenticate"))
return new OpenAuthenticationAspect(component);
if (methodID.equals("Assign")) if (aspect.equals("Authenticate"))
return new AssignAuthenticationA- spect(component);
return null;
}}
Figure 15. Implementation of ExtendedAspectFactory
public void registerAspect (String methodID, String as- pect, Object aspectObject) {
if (methodID.equals("Open") && as- pect.equals("Authenticate")) {
newAspectArray[open][authenticate] = aspectObject;
}
if (methodID.equals("Assign") && as- pect.equals("Authenticate")) {
newAspectArray[assign][authenticate] = aspectObject;
}}
Figure 16. Registration of Authentication Aspects
public int preActivation(String methodID) { int result = this.ERROR;
if (methodID.equals("Open")) {
synchronized(OpenAuthenticationQueue) { synchronized(this) {
result = ((OpenAuthenticationAspect)
newAspectArray[open][authenticate]).precondition();
}
while (result == BLOCKED) { try {
OpenAuthenticationQueue.wait();
Synchronized(this) {
result = ((OpenAuthenticationAspect)
newAspectArray[open][authenticate]).precondition();
… }
// similarly for assign()
Figure 17. Pre-activation in ExtendedAspectModerator
public void postActivation(String methodID) { if (methodID.equals("Open")) {
synchronized(AssignAuthenticationQueue) { synchronized(this) {
((OpenAuthenticationAspect)
newAspectArray[open][authenticate]).postaction();
}
AssignAuthenticationQueue.notify();
} } // open
if (methodID.equals("Assign")) {
synchronized(OpenAuthenticationQueue) { synchronized(this) {
((AssignAuthenticationAspect)
newAspectArray[assign][authenticate]).postaction();
}
OpenAuthenticationQueue.notify();
} } // assign }