Monday, August 1, 2011

Aspect-Oriented Implementation of a "Keyword Search Functionality"

Having studied aspect oriented programming during my masters, I still do my best to keep up with this "aspect way of thinking". Here is a little experiment that tries to build an aspect oriented implementation of a keyword search functionality. The implementation makes it possible to perform a keyword search on the model classes that the author defines.

On a high-level, here is how the implementation looks like:


The diagram doesn't say much; but it emphasizes on one important detail. Your model is not really "aware" that there is a keyword search functionality sitting on top of it. In other words, your model is oblivious [1]. This is one of the things that AOP (aspect-oriented programming) offers; you can unplug the "search engine" component and plug it into a different project almost with no effort.

Let's go into more details of the search engine component. To perform a keyword search; the following steps are necessary:
i) There is a need to know which classes in your model are relevant for keyword search.
ii) There is a need to somehow '"track" these relevant classes. The engine needs to know when instances of these classes are created and when they are destroyed.
iii) There is a need to extract the keywords from these relevant classes. Let's say for a Customer class, we'll probably need to extract the name & lastname information if those are the two fields in the search criteria.
iv) With all these keywords in hand, there is a need for a search algorithm. The details of this algorithm is beyond the scope of this article as it is less relevant for an AOP experiment.

Let's see how AOP provides a solution for each step.

Step 1: Marking classes as Keyword Searchable
The author needs a way to mark certain classes in his/her model as 'KeywordSearchable'. So, let's create an interface for that purpose as follows:
package searchengine;

import java.util.List;

public interface KeywordSearchable {

 List<String> getKeywords();

}
The model is not aware of the keyword search functionality; so we cannot simply expect the model classes to implement this interface. The following aspect takes care of such configuration:

package searchengine;

import infrastructure.Measure;

public aspect KeywordSearchConfig {

 declare parents: <Your Model Class> implements KeywordSearchable;

}
The author can specify here the classes that s/he wants to have as keyword searchable.

So far, we have marked the classes as 'KeywordSearchable', however we haven't defined how we are going to track the creation & destruction of the instances of these classes. That takes us to step 2.

Step 2: Tracking the lifecycle of KeywordSearchable objects
We have our second (and last) aspect that takes care of the lifecycle management of the KeywordSearchable objects.

package searchengine;

public aspect KeywordSearchableAspect {
 
 public List<KeywordSearchable> searchableObjects = new ArrayList<KeywordSearchable>();
 
 pointcut searchableObjectGetsCreated(KeywordSearchable searchableObject): 
  target(searchableObject) && execution(public KeywordSearchable+.new(..));
 
 after(KeywordSearchable searchableObject) : searchableObjectGetsCreated(searchableObject) {
  searchableObjects.add(searchableObject);
 }
The pointcut 'searchableObjectGetsCreated' captures the creation of all objects that are type of KeywordSearchable. As you might remember, these are the instances of the classes that are marked as KeywordSearchable by the author of the code.
After the pointcut, there is an after advice; the code within the advice gets executed after the pointcut. That means, after an KeywordSearchable object gets created (the pointcut), the object gets added to a list (the after advice).

So far, we have marked the objects as keyword searchable and also we can keep track of them. The 'searchableObjects' list contains the list of keyword searchable objects.

Step 3: Extracting keywords from KeywordSearchable objects
Now, there is a need to extract the keywords out of these KeywordSearchable objects. In other words, the model classes that implement KeywordSearchable have to provide an implementation for the List getKeywords() method. Since the model classes don't have any idea that they are actually implementing this interface, the search engine will provide this functionality as well. Here is the implementation:

public aspect KeywordSearchableAspect {

 public List<String< KeywordSearchable.getKeywords() {
  
  String allKeywords = this.toString();
  return Arrays.asList(allKeywords.split("\\s+"));
 }
        // ... pointcut and the advice are still here ...
}
This implementation assumes that the toString() method will provide a good representation of the class. As a result, the individual words of the toString() method will be used as the keywords to search for this instance. For clarity, here is a model class that I had in my example:
package infrastructure;

public class Product {
 
 private final String brandName;
 private final String modelName;
 
 public Product(String brand, String model) {
  this.brandName = brand;
  this.modelName = model;
 }
 
 public String toString() {
  return brandName + " " + modelName; 
 }
}
For this model class, the assumption to extract keywords from toString() method will work fine. However, this is a pretty heavy assumption and it might not work for many toString() implementations. An alternative implementation for the getKeywords() method would use reflection to go through all the fields of the class and use their values as keywords. Use of toString() is a simplification.

Technically, this step shows how AOP injects an implemented method into a class declaration. Since the code is injected to an interface, all the classes implementing this interface get the code. It is a pretty powerful example of code re-use. This concept of injecting code into classes (or sharing code between classes) without including the heavy inheritance dependency is not new; Scala captures this concept in its Traits[2]. There is a also a C# implementation of this concept [3].

Step 4: Search algorithm
Once we have all the keywords from each instance of KeywordSearchable, the rest is to write an algorithm for it.

package searchengine;

public class KeywordSearcher {

 private final List<KeywordSearchable< searchableObjects;
 private Map<String,List<KeywordSearchable>> mapping = new HashMap<String,List<KeywordSearchable>>(); 
 
 public KeywordSearcher() {
  this.searchableObjects = KeywordSearchableAspect.aspectOf().searchableObjects;
  // do your mapping here...
 }
 public List<KeywordSearchable> searchWithQuery(String query) {
 // your algorithm goes here...
 }
}
KeywordSearcher class would contain the logic to perform the actual search. What is important here is the use the KeywordSearchableAspect. This aspect provides the current list of searchable objects. These searchable objects implement KeywordSearchable; so they could provide their keywords. The search algorithm has everything it needs; all the searchable objects and their keywords.

In conclusion, this article describes a purely experimental approach for implementing the keyword search functionality using aspect oriented programming. This is probably not the best nor most performant way implementing this functionality. However, it shows how a unique approach of how AOP can be used in a project.

[1]: Aspect-Oriented Programming is Quantification and Obliviousness (2000)
[2]: Traits in Scala
[3]: Roles in C#

No comments:

Post a Comment