← All writing

ADL vs GraphQL

The Read Side and the Hard Side

TL;DR: GraphQL gave clients something genuinely new: a typed schema plus the freedom to shape their own queries. But the SDL is a contract, not an implementation: behind every field sits a hand-written resolver, and behind every mutation sits arbitrary imperative code the schema says nothing about. Whether a write is legal (for this user, in this record’s state, given every other record) is exactly the question GraphQL was never designed to answer, and it’s the question ADL is mostly about. The closer comparison is Hasura-class engines, which really do interpret schemas into running APIs. But they derive the application from the database and stop at query and CRUD. ADL derives the database, the API, and the write-side semantics from one domain model. Where GraphQL keeps a real advantage, client-shaped reads, we’ll say so plainly.


What GraphQL actually solved

Credit first, because GraphQL earned it. Before 2015, API consumers lived on whatever endpoints the server team had shipped: over-fetching here, three round trips there, a bespoke aggregation endpoint begged into the next sprint. GraphQL inverted the power dynamic. The server publishes a typed graph; the client composes exactly the query it needs: arbitrary selection sets, nested traversal, one round trip. Add introspection and the tooling that grew from it, and you get a developer experience for reads that REST never matched.

For its actual problem, many diverse clients aggregating data across a large organization’s services, GraphQL remains an excellent answer. Nothing below argues otherwise.

The contract/implementation split, again

But look at where GraphQL sits on this series’ axis. The SDL, the schema definition language, is a description. type Post { title: String!, author: User! } declares shape and nothing else. How author gets loaded is a resolver: hand-written code. Whether it’s efficient (the N+1 problem and the dataloader folklore around it) is hand-managed. Whether the caller may see it is hand-enforced. GraphQL famously has no native authorization model; every team assembles one from directives, middleware, or per-resolver checks, and the schema cannot tell you which fields are actually protected. The contract and the behavior are two artifacts. Sound familiar?

It’s most acute on the write side. A GraphQL mutation is, semantically, a named RPC with typed arguments: publishPost(id: ID!): Post. The schema tells you it exists and what it returns. It does not and cannot tell you: who may call it; whether it’s valid from the post’s current state; what fields it requires; what invariants it must respect across other records; what side effects it triggers. All of that is the resolver body: imperative code, unique to every codebase, invisible to the contract, drifting from any document that describes it.

Here’s the uncomfortable summary: GraphQL applied its innovation to reads and left writes exactly where REST had them — behind hand-written handlers. And writes are where the risk lives. Nobody’s security incident ever started with an over-fetched read field. They start with a write that was legal when it shouldn’t have been.

The fair comparison: schema-driven engines

The strongest version of the GraphQL position isn’t the SDL. It’s Hasura, PostGraphile, and their kin, which genuinely interpret a schema into a running API. Point Hasura at Postgres and you get an instant GraphQL surface with row-level permissions. No resolver drift, because no resolvers. That’s an executable specification, and it deserves to be compared as one.

The difference is the direction of derivation and where it stops. Hasura-class engines derive the application from the database: the tables are the source of truth, and the engine projects a query/CRUD surface over them, with an authorization layer configured on top. What the database can’t express, the engine can’t derive. And the database can’t express state machines, guarded transitions, cross-record business policy with human-readable violations, computed aggregations with declared recalculation triggers, lifecycle side effects, or history semantics. Those return to hand-written code: actions, event triggers, sidecar services. The perimeter of the declarative model is the perimeter of SQL’s expressiveness, and everything beyond it is imperative again.

ADL inverts the direction: the domain model is the source of truth, and both the database and the API derive from it, along with the parts of the application SQL was never going to hold. A state machine on an order isn’t an action handler; it’s a declaration the runtime enforces, including who may execute each transition and what fields it requires. “Enrollment may not exceed the study’s cap” isn’t a trigger someone wrote; it’s a rule with a message and a regulation citation, evaluated before the write commits, logged as compliance evidence when it fires. The write side — the hard side — is inside the specification, not around it.

Where GraphQL is simply better

Two concessions, one of them significant.

The significant one: client-shaped reads. A Kalos API is REST with a disciplined query surface: filtering with typed operators, sorting, pagination, ?include= for declared relationships, ?fields= for sparse responses. That covers the large majority of real client needs, but it is not GraphQL’s arbitrary selection-set composition, and pretending otherwise would be dishonest. If your defining problem is heterogeneous clients composing deep, novel traversals across a wide graph (the problem GraphQL was actually built for), GraphQL wins that layer. Nothing stops you putting a GraphQL aggregation gateway in front of Kalos’s OpenAPI surface; the emitted spec gives a gateway everything it needs. That’s a composition, not a contradiction.

The smaller one: federation across teams. GraphQL’s federation story (many teams contributing subgraphs to one supergraph) addresses an organizational-scale problem ADL doesn’t attempt. An ADL domain is a coherent application, not a company-wide data plane.

The question that sorts the two

Ask one question of your project: is the hard part reading the data flexibly, or governing how it changes?

If it’s the first (aggregation, client diversity, read-shape freedom), GraphQL’s side of the ledger is real. If it’s the second (who may do what, in which states, under which policies, with what evidence trail), then the schema language was never going to help you, because those semantics were never in it. They were always in the resolvers. ADL’s claim is that they belong in the specification, where a human can review them and a runtime can enforce them.

The one-line version

GraphQL lets clients ask precisely for what they want. ADL lets the system say precisely what is allowed — and the second problem was always the harder one.

Next in the series: ADL vs JSON Schema, the difference between a well-formed document and a legal write.