Many of our current services have general purpose lookup APIs in a format similar to the following (this example is from the GroupService):
The contract for this service is that the given keys in the map represent some property on the object being looked up (in fact, this map directly to property names on the BusinessObject which is mapped to the database). It effectively takes these values and passes them to the KNS LookupService, which interacts with OJB to perform the lookup. This means that it's possible to set the values to include wildcards as well as boolean logic (like || and &&).
There are a few problems with this approach currently:
- It is generally not well specified in the javadoc what the valid key values are for the search criteria map. Furthermore, these values are really tied to the underlying implementation details of the BusinessObject and how it's mapped to the database because they need to be the property names that are mapped in OJB.
- If the underlying OJB mappings were to change at all (i.e. a property gets renamed on an implementation class) then this would break the lookup api. This results in fragility at the API layer which would violate our requirements for compatibility.
- It is not possible to construct searches that perform "ORs" across different properties. This entries in the Map effectively form a large "AND" of all criteria.
- There is no support for returning a maximum set of results or for paging of results. This is important in cases where the result set could be very large.
- The above format is not easily extensible such that it would allow the introduction of such concepts mentioned previously.
We could address some of these problems by keeping the current contracts the same, but doing the following:
- Document the expected and valid input properties as part of the javadoc for the lookup method. Code the implementation such that it enforces this contract. Would likely mean a service-specific layer on top of the KNS LookupService.
- Add additional parameters to the lookup methods to take maxResults, resultsPerPage, startPage, and endPage
- Declare that doing cross-property "OR"-based searches is not a requirement so we will not support it.
- keeps our apis familiar for those who have already been using them.
- easy to translate from the standard KNS lookup forms
- we can't build support for cross-property "OR" searches.
- the number of parameters passed to the "lookup" method will increase in order to support paging and bounded searches (assuming that is a requirement)
- Anything outside of the standard KNS lookup paradigm has to implement a custom search function, resulting in a loss of consistency in behavior
We could also address this by doing the following:
- Introduce a general-purpose criteria-based API. Which would be similar to criteria apis provided by ORM frameworks (OJB, Hibernate, JPA, etc.)
- Document the expected and valid input properties as part of the javadoc for the lookup method. Code the implementation such that it enforces this contract. This might mean a layer that sits on top of the KNS LookupService and translates the incoming criteria to the appropriate form.
- Gives clients the most amount of flexibility for looking up data.
- Allows for ability to specify fetch size and paging options.
- Adds additional burden on implementers of the service to be able to provide an implementation that conforms to a general-purpose criteria-based query API.
- Though should be straightforward to translate for ORM-backed implementations.
Well-Defined Query Methods
This option introduces well-defined query methods that allow for executing various searches. For example, the service would have a large number of "query" methods instead of a single general purpose method.
- Results in well-defined contracts for search operations which are easily implementable.
- Results in the least amount of flexibility for search capabilities for clients. Each different type of search that is performed will need it's own search method.
- Effectively, would need to combine the Map<String, String> approach with other custom search methods.
Likely the most complicated of the options would be to do define a custom query language for searches. This could be similar to something like JPQL, though it would need to be agnostic of the ORM layer.
- Provides the highest level of flexibility in performing data searches.
- Effectively requires service implementers to write an interpreter for the query language in order to be able to properly implement the contract.
- Results in a very flexible service contract which will be difficult to implement and keep compatible across releases.
A Hybrid Approach
The recommended solution is a hybrid approach which combines the following strategies:
- Well-defined query methods for specific search operations which take few parameters. Examples:
- findGroupsForPrincipal(String principalId)
- findMembersOfGroup(String groupNamespaceCode, String groupName)
- findAllActionItems(String principalId)
- A general-purpose, but simple, criteria-based API that uses an expressive criteria model
This will allow for optimized methods for certain heavily used operations, as well as the ability to implement general-purpose search functions on top of our APIs.
This practice is effectively already in place in Kuali Rice, however the general purpose search function uses the Map<String, String> search criteria model.
A General-Purpose Criteria API
For the criteria api, we are proposing a criteria model which is simple but provides the desired amount of flexibility. Current requirements should entail:
- We will not be creating a central "search" service in order to do this. Instead, the Rice core will define a criteria data model that can be applied and implemented as distinct operations across multiple services in the various Kuali Rice modules.
- In otherwords, each service will have it's own Criteria-based "lookup" operation.
- However, each service will use a similar contract for this operation
- The operation may be named differently on each service, though by convention it should always start with "lookup"
- The criteria api should support the following data types:
- For values which don't fall into the categories above, they will be converted to a string
- The criteria model should be able to provide all of the kinds of capabilities defined here, which includes:
- like (using "*" as the wildcard)
- can only be used with string datatypes
- greater than
- less than
- greater than or equal to
- less than or equal to
- is null
- The query api should also support the "order by" directive
- The query api should support the ability to specify a maximum number of rows to return with the query
- If a particular service has more complex search requirements then those that will be provided via the general purpose criteria-based API, then they should implement a custom search operation which satisfies the service requirements.
Query API Design
An initial version of the API design is available for review as part of the following crucible review: