I am creating a custom query class, and i am unsure about the most elegant way to code it.
The goals are:
- Easy to use
- Extensibility
- Flexible so that complex queries can be formulated
Approaches
Currently i can think of two alternatives.
1. Builder pattern
Result r = new Query().is("tall").capableOf("basketball").name("michael").build();
The methods is()
, capableOf()
and name()
return a self-reference to the Query
object. build()
will return a Result
object.
2. Static Imports
Result r = new Query(is("tall"), capableOf("basketball"), name("michael"));
The methods is()
, capableOf()
and name()
are static imports and return Condition
objects. The Query constructor takes an arbitrary number of conditions and returns the result.
And/Or/Not queries
More complex queries like the following are complicated to formulate:
tall basketball player named [michael OR dennis]
UNION
silver spoon which is bent and shiny
Builder pattern:
Result r = new Query().is("tall").capableOf("basketball").or(new Query().name("michael"), new Query().name("dennis")).
union(
new Query().color("silver").a("spoon").is("bent").is("shiny")
).
build();
This is difficult to write and read. Also, i do not like the multiple use of new
.
Static imports:
Result r = new Query(is("tall"), capableOf("basketball"), or(name("michael"), name("dennis"))).
union(color("silver"), a("spoon"), is("bent"), is("shiny"));
Looks better to me, but i do not really like the use of static imports. They are difficult in terms of ide integration, auto-completion and documentation.
Sum up
I am looking for an effective solution, therefore i am open to suggestions of any kind. I am not limited to the two alternatives i presented, if there are other possibilities i'd be happy if you tell me. Please inform me if you need further information.
You are about to implement a domain specific language (DSL) in Java. Some would refer to your DSL as being an "internal" DSL, because you want to use standard Java constructs for it as opposed to "external" DSLs, which are much more powerful (SQL, XML, any type of protocol), but have to be constructed primitively using string concatenation.
Our company maintains jOOQ, which models SQL as "internal" DSL in Java (this was also mentioned in one of the comments). My recommendation for you is that you follow these steps:
My personal recommendation for you is this:
is
,has
,capableOf
, etc are predicate factory methods. Static methods are your best choice in Java, because you will probably want to be able to pass predicates to various other DSL methods of your API. I don't see any problem with IDE integration, auto-completion, or documentation, as long as you put them all in the same factory class. Specifically Eclipse has nice features for that. You can putcom.example.Factory.*
to your "favourites", which leads to all methods being available everywhere from the auto-completion dropdown (which is again a good access-point for Javadocs). Alternatively, your user can just static-import all methods from the Factory wherever they need it, which has the same result.and
,or
,not
should be methods on the predicate type (not
may also be a central static method). This leads to an infix notation for boolean combinations, which is considered more intutitve by many developers, than what JPA/CriteriaQuery did:For unions, you have several options. Some examples (which you can also combine):
Last but not least, if you want to avoid the
new
keyword, which is part of the Java language, not of your DSL, do also construct queries (the entry points of your DSL) from a Factory:With these examples, you can construct queries as such (your example):
Java version:
For further inspiration, have a look at jOOQ, or also at jRTF, which also does an excellent job at modelling RTF ("external" DSL) in Java as an "internal" DSL