Chapter 9. Keeping Extensions Separate with Pointcuts

Extension use cases are a special kind of use case that allows you to keep additional functionality separate from an existing use case. The realization of extension use cases requires additional behavior to be defined into existing operations. This is often intrusive with traditional techniques. Through aspects, use-case slices keep these operation extensions separate from the existing operations. They are subsequently executed at designated points specified by pointcuts during compilation or execution. Pointcuts are parameterizable, which allows you to apply the same extensions on multiple points at once. This parameterization capability can also be used in conjunction with templating to address a wider range of problems. In addition, with abstract pointcuts, you can have prebuilt extensions that can be attached to desired points later.

Realizing Extension Use Cases

Let us do a quick recap of what we have discussed earlier on extension use cases. Each use case has a set of extension points by which you can extend its behavior. An extension use case will introduce additional behaviors (which we call extension flows) into existing use cases at designated extension points. The designation of extension points is achieved through extension pointcuts. This keeps the separation of the extension and the existing (also known as the base) use case separate during requirements time.

When we discussed the composition of a use-case slice into the design model in Chapter 8, “Keeping Peer Use-Case Realizations Separate with Aspects,” we dealt only with complete operations. You had only to overlay complete operations onto existing classes. However, when you realize extension use cases, you need to add behaviors (or what we call operation extensions and what AOP calls advices) into existing operations at precise points designated by pointcuts. This means that to keep the separation of extension use-case realizations from the base use-case realization, you need a more fine-grained composition mechanism than that discussed in Chapter 8.

The discussion in this chapter uses a simple example of a logging extension. More complex examples are available in the next part of the book. The Logging extension use case is an infrastructure mechanism that extends both the Reserve Room and Check In Customer use cases, as shown in Figure 9-1.

Logging use case.

Figure 9-1. Logging use case.

The objective of the logging extension is to count the number of requests on enquiries about any room types and whether or not they are successful. This information allows us to subsequently analyze the relative popularity of various room types in our hotel.

Keeping Modularity of Extension Use-Case Realizations

Let us begin by considering how we will extend the Reserve Room use-case realization with the Logging extension. In the Logging extension, we have two roles. The two roles and the classes that play these roles are depicted in Figure 9-2.

Roles in Logging extension.

Figure 9-2. Roles in Logging extension.

The first role is that of a logger. The class that plays this role saves the number of room requests and their outcomes into some persistent data storage. Most language environments provide data storage capability. In this example, let’s assume that we will create a LogStream class, which merely encapsulates that capability with a log() operation that stores information into a stream (e.g., text file).

With the LogStream class, we now have the ability to store information into a file, but we also need to extract the required information in the first place. In the Reserve Room use-case realization, the ReserveRoomHandler is the class responsible for making room requests. The ReserveRoomHandler class plays the role of a target, and we need operation extensions to be composed onto the target to extract the required information.

We now identify the specific execution point at which the operation extension will be composed onto the target. Figure 9-3 shows the sequence diagram describing the interactions within the Reserve Room use-case realization. When a Client invokes makeReservation() on a ReserveRoomHandler instance, the latter invokes retrieve() on a Room instance.

Extending existing operation «aspect».

Figure 9-3. Extending existing operation «aspect».

On top of this base interaction, we overlay the logging operation extension. This is framed within a box in Figure 9-3. This box also depicts where the additional behavior is executed. It occurs within the makeReservation() operation after the call Room.retrieve() is completed. In essence, the operation extension invokes the log() operation on the LogStream instance to store the required information—whether or not the call to retrieve() on a Room instance is successful.

Operation Extensions

An operation extension comprises the behavior specific to a use-case realization within an operation. In our example, the operation extension is responsible for simply invoking the log() operation on the LogStream instance. An operation extension maps to an advice in AOP.

Structural and Behavioral Context

You must note two things when identifying the place to execute the operation extension: the structural context and the behavioral context. The structural context describes where in the design element structure you will be overlaying the operation extension—that is, which package, which class, and which operation you will be executing within. In our example, we are executing the operation extension within the makeReservation() operation within the ReserveRoomHandler class.

The behavioral context identifies the point in the execution flow where the operation extension extends the existing operation. This point corresponds to what we term in use-case modeling as extension point, and corresponds to the concept of join points in AOP. In our example, we are only interested in calls to retrieve() on a Room class. You might be interested in which class or operation is calling makeReservation() itself, or even in the caller of that calling class. In general, the execution context will refer to the state of the call stack.

In addition, it is also useful to give the operation extension a name that describes what it does.

Operation Extension Declaration

Recall that the name of a class extension describes which existing class in the design element structure we want to overlay. Likewise, the declaration of an operation extension expresses where in the existing operation we will be extending and what the operation extension is doing. Accordingly, the operation extension declaration will have these three segments. We express the declaration in the following form:

structural context  + behavioral context + operation extension

In Figure 9-3, the declaration of the logging operation extension is specified as follows:

makeReservation()  {after call(Room.retrieve()) logData }

The first segment, makeReservation(), describes the existing operation within which we will be executing. We do not need to express it as something like ReserveRoomHandler.makeReservation(), because the operation extension will be placed within a class extension in an aspect, and this identifies the class to which the operation extension belongs.

The second segment, call(Room.retrieve()), identifies the extension point when a call is made to the operation retrieve() in the Room class. This expression is what AOP terms a pointcut. The after keyword is a modifier to define where that operation extension will be executed relative to the pointcut. The semantics of the operation extension declaration are that we will be executing the operation extension within the execution of the makeReservation() after makeReservation() completes a call to the retrieve() operation in a Room instance.

Finally, the third segment, logData, indicates what the operation extension actually does.

Figure 9-4 depicts the complete Logging extension use-case slice, with the Logging collaboration, the LogStream class, and the Logging aspect. Notice that the LogStream class resides outside the Logging aspect. This is because, the LogStream class is a new class that we created for the logging functionality.

Contents of logging use-case slice showing operation extensions.

Figure 9-4. Contents of logging use-case slice showing operation extensions.

The UML aspect contains class extensions, each containing a subset of features (attributes, operations’ and relationships) specific to the use case it realizes. This gives a good representation of the overall effects a use-case realization has on an existing class. As shown in Figure 9-4, an operation extension resides within a class extension. This is useful to identify the structural context of the operation extension—that is, which class and which operation you are extending. This provides a good indication of the overall effect a use-case realization has on an existing operation.

Pointcuts

Let’s take a closer look at the expression call(Room.retrieve()). It is a direct reference to a specific extension point in the existing makeReservation() operation where a call to the retrieve() operation in the Room class is made. Such direct referencing makes it sensitive to changes. So, for instance, if the retrieve() operation is renamed read(), the referenced extension point will no longer be valid. Therefore, such a direct reference to an extension point is not good.

A better option is to give the extension point a meaningful name that signifies what the existing operation is doing when you need to execute the operation extension. You can then define the actual extension point separately. In our example, we want to track the outcome of calls to the Room class, so a name like roomCall is more meaningful, and you can link this name with the definition of the extension point. This naming of an extension point is known as a pointcut. The operation extension declaration is now:

makeReservation(){after (〈roomCall〉) logData}

The 〈〉 indicates that roomCall in the operation extension declaration is a parameter that can be bounded to different values. This can be achieved using a pointcut expression in the aspect. Alternately, the parameter may apply to the entire use-case slice, in which case the use-case slice becomes a template.

Figure 9-5 shows the revised logging use-case slice after using a point-cut to refer to the join point (i.e., execution point) at call(Room.retrieve()). A pointcut named roomCall is defined as follows:

Logging use-case slice with pointcut compartment.

Figure 9-5. Logging use-case slice with pointcut compartment.

roomCall = call(Room.retrieve())

Note that there are no 〈 〉 brackets in the pointcut definition. This is because using the brackets would have indicated that the name of the pointcut is parameterized, but this is not the case.

The implementation of the Logging aspect in Figure 9-5 is shown in Listing 9-1.

Example 9-1. Logging Aspect in AspectJ: Logging.java

1.  package infra.logging;
2.  import app.customer.ReserveRoomHandler ;
3.  import domain.room.Room ;
4.
5.  public aspect Logging {
6.  pointcut roomCall() :
7.     withincode(void ReserveRoomHandler.makeReservation())
8.             &&  call(void Room.retrieve()) ;
9.
10. after () : roomCall() {
11. // code
12. }
13. }

The Logging aspect resides in the logging package within the infrastructure layer (see line 1). Since the aspect references the ReserveRoomHandler, it must import it, and since the pointcut roomCall contains a reference to the Room class, the Room class must be imported. These import statements are in lines 2 and 3. Note that in Chapter 8, we located the ReserveRoomHandler class in the customer package in the application layer and the Room class in the room package in the domain layer, so the Logging aspect has import statements to reference these packages.

The pointcut roomCall is defined in lines 6, 7, and 8. The operation extension is implemented as an advice in lines 10, 11, and 12. The keyword after indicates that the operation extension extends the existing operation after the join points specified by the pointcut roomCall.

The pointcut roomCall defined in lines 6, 7, and 8 is a conjunction of two smaller pointcuts separated by a logical and operator (denoted by && in Listing 9-1). Let us look at them individually:

  • The withincode(void ReserveRoomHandler.makeReservation()) segment refers to all possible join points in the makeReservation() operation.

  • The call(void Room.retrieve()) segment refers to all calls to the retrieve() operation in the Room class.

The conjunction of these two pointcuts matches all join points within the makeReservation() operation that are calls to Room.retrieve().

Note that we do not explicitly describe the segment withincode (void ReserveRoomHandler.makeReservation()) in Figure 9-5. This is because it has already been expressed by the fact that the structural context of operation extension itself. So, you see that the representation in Figure 9-5 emphasizes the context in which operation extensions execute rather than merely listing them as advices in AspectJ.

Parameterizing Pointcuts

We have just seen how we can extend a single use-case realization. In general, you might want to execute an operation extension at multiple extension points in multiple use-case realizations or even into multiple extension points in the same use-case realization. One of the key attractions of applying AOSD lies in its ease of parameterizing the definition of pointcuts with wildcards and regular expressions, which we discuss in this section.

Note that the parameters we discuss in this section are used to locate and bind the aspect onto existing classes and operations in order to extend them. There is yet another kind of parameter—a template parameter, such as those used for C++ templates. This template capability is also seen in Java 1.5. Templates are frequently used as a means to generate classes and elements. We discuss template parameters in Section 9-5.

Identifying Parameters

Recall the interaction that occurs when a client class invokes a ReserveRoomHandler instance to perform the makeReservation() operation (shown in Figure 9-3). The makeReservation() in turn invokes the retrieve() operation on a Room instance. This is sufficient when we want to extend just the one specific makeReservation() operation. If we want to extend multiple operations at once, we must introduce some parameterization. Let us replace ReserveRoomHandler, makeReservation(), and retrieve() with the parameters 〈RoomAccessor〉, 〈roomAccessOperation〉, and 〈roomCall〉 respectively. The sequence diagram shown in Figure 9-3 will now be parameterized as shown in Figure 9-6.

Using sequence diagrams with parameters.

Figure 9-6. Using sequence diagrams with parameters.

Recall that the objective of the Logging use-case extension is to count the successes and failures of requests made on any Room instances. These requests are not limited to the retrieve() operation, but any operation in the Room class itself. We collectively call these Room operations 〈roomCall〉. A number of classes may make calls to 〈roomCall〉. In our example, we had one class that made these calls: ReserveRoomHandler. Assume instead that multiple classes may invoke Room operations. We call these classes collectively 〈RoomAccessor〉. In these 〈RoomAccessor〉 classes, there will be various operations that will make a 〈roomCall〉. We collectively call these operations 〈roomAccessOperation〉.

Defining Parameters

After parameterizing the Logging extension in Figure 9-5, we have the result shown in Figure 9-7. In this case, we have defined the parameters as pointcuts. In Figure 9-7, there are three pointcuts, one for each of the three parameters RoomAccessor, roomAccessOperation, and roomCall.

Parameterized logging use-case slice.

Figure 9-7. Parameterized logging use-case slice.

Figure 9-7 shows a hierarchical structural composition. The Logging aspect contains the 〈RoomAccessor〉 class extension, which contains the 〈roomAccessOperation〉 operation, which makes a 〈roomCall〉. Each sets the structural context for the next and gradually narrows down the specific point where the operation extension will be executed.

The pointcut

RoomAccessor = ReserveRoomHandler or CheckInHandler

restricts the classes to be extended to the ReserveRoomHandler and the CheckInHandler classes.

The pointcut

roomAccessOperation = *(..)

refers to any operation. However, since the parameter is in the context of the RoomAccessor class, it means any operation in the RoomAccessor class.

The pointcut

roomCall  = call (Room.*(..))

refers to any calls to any operation in the Room class. Together, the three pointcuts refer to any execution point in any operation of ReserveRoomHandler or the CheckInHandler classes that make calls to any Room operation.

Parameterizing Pointcuts in AOP

Let us see how the Logging aspect in Figure 9-7 gets implemented in AspectJ. Since you make no changes to the behavior of the operation extension, it will be the same as what you had previously in Listing 9-1. However, since you are executing the operation extension into multiple points, the pointcuts will be changed, as shown in Listing 9-2.

Example 9-2. Regular Expressions in AspectJ: Logging.java

1.      pointcut RoomAccessor():
2.              within(ReserveRoomHandler)
3.              || within(CheckInHandler) ;
4.      pointcut roomAccessOperation() :
5.              RoomAccessor()
6.              && withincode(* *(..))
7.      pointcut roomCall() :
8.              roomAccessOperation()
9.              && call(* Room.*(..)) ;

The pointcuts in Listing 9-2 are a direct mapping from what is in Figure 9-7. However, aspects in AspectJ have no concept of class extensions and are, therefore, unable to make use of the hierarchical context of Figure 9-7. To get around this problem, the logical and operator (denoted by &&) is used.

For example, lines 10, 11, and 12 show that the pointcut roomAccessOperation has two segments joined by the && operator. The first segment sets the context for the second. The pointcut roomAccessOperation therefore refers to any operation in the ReserveRoomHandler or CheckInHandler classes.

The same goes for the roomCall pointcut in Lines 13, 14, and 15. It contains two segments. The first segment uses the roomAccessOperation pointcut to define the context for the next.

As you can see in Listing 9-2, the use of the && operator makes the pointcut expression slightly lengthy. You can definitely shrink the pointcuts. For example, since the parameter RoomAccessOperation refers to all operations, it does not restrict anything and can therefore be left out. Thus, the roomCall pointcut can be expressed more compactly as follows:

pointcut roomCall()
    : (within(ReserveRoomHandler)||within(CheckInHandler))
    && call(* Room.*(..))

However, as you try to make the pointcut expressions more compact, it becomes harder to understand which points your advices are extending.

It is important that you express pointcuts in a manner that highlights the structural and behavioral context by which the advice will be executed. This greatly improves the readability of pointcut expressions.

Generalizing Extension Use-Case Realizations

Now let’s consider how to generalize extensions. We continue with the Logging extension example. Suppose the required behavior for the Logging extension is the same for various existing use cases and can therefore be applied repeatedly on these different existing use cases. However, the extension points are different for these use cases. You can factor out the generic behavior into a Generic Logging aspect in which the pointcuts are deliberately undefined (i.e., they are declared abstract).

When you want to apply the logging extension behavior on an existing use case, let’s say the Reserve Room use case, you specialize the Generic Logging use case into a Concrete Logging extension with the pointcuts defined. This is depicted in Figure 9-8.

Generalizing Logging extension.

Figure 9-8. Generalizing Logging extension.

If you realize the Generic and the Concrete Logging use cases, you find corresponding aspects, as shown in Figure 9-9. For brevity, we do not depict their respective use-case slices in Figure 9-9. We are interested only in the aspects here.

Realizing generalizations.

Figure 9-9. Realizing generalizations.

Since the Generic Logging use case is where the behavior is defined, you have class extensions there. However, since you are speaking in generic terms, you deliberately do not define all the pointcuts yet. Instead, you postpone the specification of the pointcuts to the child ConcreteLogging aspect.

Recall that the objective of the Logging extension is to count the number of successful or failed requests to the Room instance. So, for any given class, we are interested in any of its operations that invoke the Room instance. Thus, you have two pointcuts, 〈roomAccessOperation〉 defined as *(*) (i.e., any operation) and 〈roomCall〉 defined as call (Room.*(*)).

The RoomAccessor pointcut is deliberately left undefined in the GenericLogging aspect. The definition is postponed to the child ConcreteLogging aspect. In Figure 9-9, the ConcreteLogging aspect defines the RoomAccessor pointcut to be ReserveRoomHandler. In this way, you overlay the generic logging onto a specific use-case slice, in this case the Reserve Room use-case slice.

The AspectJ mapping for the GenericLogging aspect in Figure 9-9 is shown in Listing 9-3, and the mapping for the ConcreteLogging aspect is shown in Listing 9-4.

Example 9-3. Abstract Aspects in AspectJ: GenericLogging.java

1.  package infra.logging;
2.  import domain.room.Room ;
3.
4.  public abstract aspect GenericLogging {
5.      abstract pointcut RoomAccessor() ;
6.      pointcut roomAccessOperation() :
7.              RoomAccessor()
8.              && withincode(* *(..)) ;
9.      pointcut roomCall() :
10.             roomAccessOperation()
11.             && call(* Room.*(..)) ;
12. after () : roomCall () {
13. // code
14. }
15. }

The GenericLogging aspect in Listing 9-3 resides in the logging package. Since these pointcuts have a reference to the Room class (see line 9), we need to import the Room class (line 2).

The GenericLogging aspect has three pointcuts: RoomAccessor(), roomAccessOperation(), and roomCall() (see the UML aspect depicted in Figure 9-9). The GenericLogging aspect is abstract because the RoomAccessor() pointcut is abstract.

The advice in lines 12, 13, and 14 adds the required behavior to do the logging after the roomCall pointcut.

Example 9-4. Concrete Aspects in AspectJ: ConcreteLogging.java

1.  package infra.logging;
2.  import app.customer.ReserveRoomHandler ;
3.
4.  public aspect ConcreteLogging extends GenericLogging {
5.      pointcut RoomAccessor():
6.              within(ReserveRoomHandler) ;
7.  }

The ConcreteLogging aspect inherits from the GenericLogging aspect through the Java extends keyword. Note that Java extends is not a use-case extend but rather a class inheritance (i.e., generalization). The ConcreteLogging aspect defines the RoomAccessor() pointcut to refer to the ReserveRoomHandler class.

Templating Use-Case Slices

We earlier discussed the use of applying aspects on multiple operations at once by parameterizing pointcut expressions. There is yet another way to apply aspects on multiple operations and classes—through the use of templates.

In UML, a template element (also known as a parameterized element) is annotated by a dashed box on its top right-hand corner. This dashed box contains the parameters for that element. Figure 9-10 shows an example of a parameterized List class with Item as a parameter. In essence, it is a list of items.

Parameterized list.

Figure 9-10. Parameterized list.

You can create different lists—ReservationList, RoomList, and so on—by substituting the Item parameter with Reservation, Room, and so on.

From our discussion, you see that there are three ways for you to define parameters in a use-case slice:

  • Identify the parameter as a pointcut and define the pointcut expression.

  • Identify the parameter as a pointcut and postpone the definition of the pointcut expression to a child aspect.

  • Identify the parameter as a template parameter.

In general, you use a combination of the three techniques. This is illustrated in the parameterized logging use-case slice on Figure 9-11.

Logging use-case slice template.

Figure 9-11. Logging use-case slice template.

In Figure 9-11, you find several parameters, each delimited by 〈〉:roomAccessOperation, roomCall, RoomAccessor, Logger, and Resource. The roomAccessOperation and roomCall parameter are defined through pointcuts in Figure 9-11. The RoomAccessor parameter is defined through a pointcut. But in this case, we are postponing the definition of the pointcut expression to its child aspect.

The roomCall pointcut uses a Resource use-case slice parameter in its expression, call (〈Resource〉.*(..)). This Resource is a string parameter that will be substituted when the template is applied. The Logger is also another parameter for the use-case slice. In our example, we want to log requests to the room resource, so the Resource parameter is substituted by the Room string. We can also substitute the Logger parameter with the LogStream class when we apply the logging use-case slice.

We have just shown you the different techniques for defining parameters in use-case slices. When working with use-case slices, you should begin by identifying the parameters first. Thereafter, you decide which technique is most applicable. You will frequently use a combination of techniques. We provide further examples in Part IV of this book.

Summary and Highlights

In this chapter, we demonstrated how you can keep the realizations of extension use cases separate from that of a base use case. This requires the composition of operation extensions defined in extension use-case slices to be composed into operations defined in the base use-case slice. When defining operation extensions, it is important to clarify its structural context—within which class and which operation you will be executing the operation extension—and also to clarify its behavioral context—when in the execution of the operation the operation extension will execute.

When developing with AOP and AspectJ, your pointcut expressions may become rather lengthy. This is extremely dangerous because you might lose track of which operations you are extending. The solution to the problem is to break the pointcut expressions into segments to explicitly highlight the structural and behavioral context of the pointcut. This makes your pointcut expressions more understandable.

AOP offers a powerful mechanism to apply aspects (specifically advices) on multiple classes (specifically operations) at once by permitting regular expressions to be used to define pointcuts. This is extremely convenient because you find that crosscutting behaviors are generally repetitive. With regular expressions in pointcuts, this repetition is minimized. In addition, through the use of abstract pointcuts, you can even construct prebuilt extensions that can be hooked onto existing operations by defining the pointcuts. This makes extensions highly reusable.

However, an aspect in AOP has a limitation in that it can only add to existing classes. This is overcome by templating capabilities, which can be modeled in UML today as template elements. In fact you can parameterize any element in a use-case slice, including aspects. This makes your use-case slices even more reusable.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.41.229