DoytoQuery: A Different Kind of Mismatch

f0rb recently posted about a project of his, DoytoQuery. It's a framework that leverages Spring, providing a data access layer and a REST endpoint facility; the data access layer seems to be the primary focus, using "OQM" for "Object Query Mapping" to distinguish itself from the more common "ORM." From the surface, it doesn't look bad, but it has its own impedance mismatches that might undermine the approach.

The core idea is this: instead of writing query methods, query DSLs, or JPQL, you define a plain Java object whose field names encode query conditions. ageGe maps to age >= ?. nameLike maps to name LIKE '%arg%'. idIn maps to id IN (...). Every assigned field gets folded into the WHERE clause at runtime, and null fields are skipped. Pagination and sorting live in a base class, PageQuery, that every query object inherits.

That actually handles the common case well. Define your entity, define your query object with appropriately-named fields, wire up a service and controller, and you have a working CRUD endpoint with dynamic filtering. The README makes it look almost frictionless, and for simple scenarios, it probably is. The comparison repo - authored by the same person, which is worth noting - benchmarks it against Spring Data JPA, Spring JDBC, jOOQ, and MyBatis-Plus, and the code-size numbers are believable: DoytoQuery genuinely needs fewer lines for straightforward filtering queries because the framework is doing the work your repository methods or service wrappers would otherwise be responsible for.

Where the mask slips

The trouble arrives when your queries get complicated, because the naming convention has to stretch to cover it, and it stretches visibly.

OR conditions are handled via a nested query object. You add an or field of the same query type, and whatever you populate there gets joined with OR. In URL form it looks like or.salaryInUsdLt=30000&or.salaryInUsdGt=300000. In Java it's a SalaryQuery or field on SalaryQuery. It works, but you've now encoded query logic into object structure, and anybody reading that field without knowing the convention has no idea what they're looking at.

Subqueries are stranger. A field named salaryInUsdGt0 where the type is another query object generates salary_in_usd > (SELECT max(salary_in_usd) FROM ...). The 0 suffix is load-bearing. That's not a Java field name; it's a DSL token that happens to be syntactically valid as a Java identifier. The line between "object" and "query language" has dissolved, quietly, under the hood.

This is the mismatch that ORM critics have always pointed at, just rotated. ORM's impedance mismatch is between the relational model and the object model. DoytoQuery's is between SQL's query semantics and Java's identifier namespace. You've traded one leaky abstraction for another, and this one is arguably harder to debug because it's invisible - it only makes sense if you've read the convention table, and the documentation doesn't always tell you when you've left the simple cases behind.

The repo situation

The project has been maintained - v2.1.0 dropped in November 2024, and there are 1,480 commits on the main branch - but the ecosystem around it is thin. It appears to be primarily one developer. The English documentation is sparse, and at least one page of the docs appears to have been written for GoooQo, the Go port, rather than the Java version. There's a Chinese README that's more complete, which suggests the primary audience isn't the English-speaking Java community.

None of that is a fatal objection. Thin ecosystems can produce good tools. But for a practitioner evaluating adoption risk, the bus-factor question is real, and forty-five stars over seven years is a signal worth taking seriously. It's not crucial - a great project might just lack great marketing - but it's worth considering.

The bus factor is a reference to "what happens if someone gets hit by a bus?" Can the team or project survive the loss, temporary or otherwise, of a specific member? If so, the "bus factor" is minimal. If that person is where all the institutional knowledge resides and the project dies without them, the bus factor is critical.

What it's actually good for

The OQM concept, separate from this particular implementation, is worth understanding. The idea that a query object should be a first-class type - reusable, composable, passable across layers - is genuinely different from Spring Data's query-derivation-from-method-names, where your query shapes proliferate as repository method signatures. DoytoQuery's query objects can be built, shared, and passed around like any other value, which is a real ergonomic advantage at scale.

That ergonomic advantage comes at a cost: you're tying yourself to a datastore and the convention, a convention that's unique to DoytoQuery.

Spring Data can yield some amazing method names for relatively simple queries, and that's actually probably what you want if you use the same Repository object for different datastores; if your ThingRepository is used for Mongo and Postgres, the Spring Data query derivation from method names is your best bet, even though they might be eighty-plus characters wide. The question is whether you'd need that flexibility or not.

For teams that are willing to learn the occasionally-surprising convention and stay within it, the boilerplate reduction for standard CRUD-plus-filtering work can be real. Maybe. For complex queries, you're still writing it, you're just translating to DoytoQuery's query structure rather than writing it in SQL, or semantically as you would in Spring; the focus changes, it doesn't go away.

There is no "just get the right dataset, magically" invocation.

And thus the question is whether the convention pays for itself once your queries become non-trivial, or whether you find yourself fighting the naming system to express what you actually need. Based on what's visible in the repo and the comparison scenarios, the answer is probably "it depends on your domain."

If your filtering requirements are predictable and bounded, DoytoQuery is worth a prototype. If you're going to need complex joins, dynamic subqueries, and conditional aggregations, you're going to outrun the naming convention faster than the documentation warns you.

Worth knowing about

DoytoQuery isn't ready to displace anything in production for most teams. The documentation gap alone creates onboarding friction that the simplicity of the happy path doesn't compensate for. But the concept is honest enough - OQM is a distinct approach, not just ORM rebranded - and the field-naming mechanism is clever enough that understanding how it works can sharpen your thinking about what query layers are actually doing in the frameworks you already use.

DoytoQuery is interesting, but there are real questions about whether it's interesting in the right ways. The performance comparison makes it look good, of course, and that's not nothing, but whether the tradeoffs are worth it or not is ... debatable, because there's virtually no documentation set, expertise is going to be in-house and homegrown, and there are real questions whether the query syntax is actually mappable cleanly to Java paradigms.

It's the same set of questions that every data management library has to answer for itself, and readers are welcomed to offer their thoughts: is this a better mousetrap?

Comments (0)

Sign in to comment

No comments yet.