Domain Searching Using Visitorsby Paul Mukherjee
Modern applications typically require domain searching functionality--the ability to search for data within the context of the application domain. For instance, an application for tracking financial transactions might need to be able to search for all transactions over a certain cash value; a supply-chain management application might need to be able to search for all requisitions for a particular supplier. This capability could be for ad hoc searching or for the generation of reports. Since the underlying information is typically stored in a relational database, at some point the search needs to be converted from the language of the domain to the query language supported by the database (typically SQL). Though this is a variation on the well-known problem of object-relational (O-R) mapping, this particular aspect of the problem has generated less interest than the more fundamental problem of defining the relationship between domain objects and database tables.
As a consequence, I have seen many applications where the benefits of a carefully designed O-R mapping layer have been negated by ill-conceived search functionality that couples the domain objects tightly with the database. The objective of this article is to show how careful design can provide a flexible solution that is easy to maintain and adapt to different O-R mappings or native SQL. The solution I present has two main ingredients: a collection of classes that capture the information to be used for searching; and an implementation of the Visitor pattern that provides the implementation of the search, in the language of the underlying persistence service.
In this article, I use a running example to illustrate the ideas I am presenting. The example is a simple database that is used as part of a backup application to record which files are located in a specific backup volume.
This article is organized as follows: in the following section, some of the forces that influence the design are described. After that an overview of the design is given, followed by a description of the framework used in the design. The two sections after this show two different implementations of the design, and finally, the strengths and weaknesses of the design are considered.
The source code accompanying this article is available in the Resources section below. Note that for brevity I have inlined some variables and methods in this article, compared to the actual source code.
When considering the problem of domain searching, a number of different forces apply and should be considered as parameters constraining potential solutions.
The first constraint is that the solution should support loose coupling between domain objects and the database. A couple of years ago this would have required no further justification, but many of the exponents of lightweight approaches to application design have argued eloquently that loose coupling is a symptom of over-engineering, so some explanation is necessary.
The objective of loose coupling in this context is to ensure clean separation between the problem domain layer and the data management layer. This separation is critical if each layer is to have well-defined responsibilities. In fact, the emerging trend towards transparent persistence makes this separation all the more important.
Any solution should also be maintainable; modifying or extending domain search functionality should not lead to wholesale changes in the application design. Loose coupling is normally necessary for maintainability, but not sufficient.
Other forces could also apply depending on the specific application, such as performance, substitutability (should a number of different databases or persistence services be supported?), and so on. However, I consider loose coupling and maintainability to be common to all solutions except perhaps the worst kind of hack thrown together on short notice (the kind we all have worked on but never admit to!).
The domain searching design I am going to present assumes a standard layered approach with a presentation layer, a problem domain layer and a data management layer. The example also uses a client layer consisting of Java Server Pages (JSPs) served to a user in a browser. To keep the example simple, I have not used a web application framework such as Struts, though there would be no problem using this design within a Struts application.
The example therefore structures the design as shown in Figure 1. The important part of the design is the third step. This is also the only step that is dependent on the underlying persistence service.
Figure 1: Solution structure
In order to help understand the example, the structure of the database used is shown in Figure 2. A volume contains a number of files, and a number of keywords may also be associated with a volume to allow for keyword-based searching. The information persisted has deliberately been kept as simple as possible to ensure that the clarity of the design is preserved.
Figure 2: Database tables
In order to demonstrate the flexibility of the design, I will present two implementations, one using SQL and one using Hibernate. However, before these implementations can be described, the overall solution framework needs to be outlined.
When designing domain searching, it is important to have a clear specification of what is to be searched, and what is to be presented as the result of the search. This might seem self-evident, but in my experience it can lead to significant discussion, especially if the users of the application fall into different constituencies.
Assuming that such a specification is in place, a number of search criterion classes should be created, which will contain the user-provided search data. In the backup database example, users can search using volume names, file names, a file's last modification date, and keywords. The result will show the volume name, file name, file size, and last modification date for each file matching the submitted search criteria. It is permitted for none of the described search criteria to be used, in which case the entire contents of the database will be returned, subject to whatever constraint is specified by the presentation tier on page size.
This leads to the class diagram shown in Figure 3.
Figure 3: Search criterion classes
ISearchCriterion is an interface implemented by all
search criterion classes. The methods defined by this interface are
described in the table below. For this article, the most important
method is the
accept method, which provides the entry
point for visitors. This is a standard implementation of the
||The name of the criterion. This is used to identify the criterion uniquely within the program.|
||The display name of the criterion. This is the name used by clients to present the criterion to users.|
||This method is used to populate the criterion. The string value is that submitted by the user; it is parsed by this method and the object stores the result of the parse.|
||The entry point for visitor objects, for this criterion.|
For domain searching, the Visitor pattern provides the perfect
means to traverse the search criterion classes. The interface
ISearchVisitor defines methods for visiting each kind
of search criterion. This allows the submitted search criteria to
be traversed and a query built; the search can then be executed and
a result delivered. The visitor hierarchy for this example is shown
in Figure 4.
Figure 4: Visitor classes
The following sections describe the SQL and Hibernate visitors in more detail.
Pages: 1, 2