Skip to content

Proposal/scope question: Argo binary wire-format transport for GraphQL responses (persisted queries + compression-friendly encoding) #1508

Description

@giridhargg

Summary

I've built and published a working implementation of Argo
a formally specified, production-proven (WhatsApp, Beeper) binary serialization format for
GraphQL responses — as a set of Spring Boot starters on top of spring-graphql. Before I put
more work into it, I'd like maintainer input on whether this is something you'd want as a
first-party module, or whether it's better as an independent library you'd link to from the docs
as a community extension. Either answer is useful to me; I mainly want to avoid guessing wrong
about scope.

What Argo is, briefly

Argo derives a compact "wire schema" per persisted query and encodes responses against it — not
a naive hand-rolled binary format. It gets unions/interfaces/fragments/@skip/@include right
by construction, keeps null/absent/error as three distinct wire states (JSON conflates the first
two), and routes scalar values into per-type columnar blocks specifically so it still beats
compressed JSON, not just raw JSON. Spec + rationale: the linked site, and Mike Solomon's
GraphQLConf 2023 talk ("Argo: Designing a Compact and Compressible Binary Serialization Format
for GraphQL").

What exists today

Three modules, published to Maven Central under io.github.giridhargg (repo: https://github.com/giridhargg/spring_argo_gql):

  • gql-argo-core — wire-format primitives (WireType, ArgoEncoder, ArgoDecoder,
    ArgoSchemaSerializer), independent of Spring.
  • gql-argo-server-starter — installs as a WebGraphQlInterceptor that fires after your
    existing resolvers; requests with Accept: application/argo get binary responses, everything
    else is untouched. Includes persisted-query registration/schema endpoints and extension points
    (ArgoPersistedQueryStore, ArgoSchemaCustomizer, ArgoEncodingCustomizer).
  • gql-argo-client-starter — client-side counterpart; works with RestClient, WebClient
    (blocking and reactive), and RestTemplate.

No changes to application resolver code are required — it's purely a transport-layer addition.

Evidence, not just a pitch

I audited the implementation against the GraphQL spec and Argo's own wire-format checklist
feature-by-feature (results in FEATURE_COVERAGE.md in the repo) rather than assuming coverage
from docs. Solid: all value kinds, nullability (including field-omitted vs. field-present-null as
genuinely distinct wire states), lists (including million-element, non-stack-growing encoding),
interfaces/unions preserving concrete type, aliases, fragments, directives, errors with full
message/locations/path/extensions, partial success, subscriptions (real, WebSocket-based),
introspection, Relay conventions. Explicitly out of scope / not implemented: binary request
encoding (Argo's spec itself only covers responses), @defer/@stream, batch requests, file
uploads, federation.

Benchmarked, not asserted — two separate measurements:

Wire size (WireSizeComparisonTest, raw byte lengths, JSON vs. Argo, uncompressed and
gzipped):

Shape JSON+gzip Argo+gzip Argo+gzip vs. JSON+gzip
List of 10,000 83,924 B 71,584 B 14.7% smaller
Nested (2,000 orders × 20 items) 17,119 B 7,153 B 58.2% smaller
Deeply nested (4 levels) 4,201 B 2,616 B 37.7% smaller

(Full 7-shape table, including the honest counter-case where gzip's own overhead dominates for
tiny single-record payloads, is in the repo.)

Encode/decode CPU cost (JMH, gql-argo-benchmarks, vs. Jackson): decoding is a consistent
15–43% faster than Jackson deserialize from a single record through 2,000 nested orders, since it
skips string parsing/escaping entirely. Encoding is a genuine tradeoff, not a clean win — slower
than Jackson for flat homogeneous lists (columnar-block bookkeeping overhead), faster for
broad-but-shallow nested shapes (23–34%), roughly even for deeply nested ones. I've documented
this honestly rather than cherry-picking the favorable numbers — full writeup with methodology
caveats in the repo's BLOG.md.

The scope question

spring-graphql's core is deliberately narrow, and I know new wire formats + a persisted-query
registry + two new starter modules is a substantial addition, not a small patch. So concretely,
I'd like your read on:

  1. Is a first-party binary transport something in scope for this project at all, or is that
    better left to satellite libraries by design?
  2. If in scope in principle: would you want it as-is (roughly, adopting these three modules), or
    would the shape need to change significantly to fit your conventions/maintenance model?
  3. If out of scope: would you be open to linking gql-argo from the docs (e.g. an "extensions/
    community" section) so people evaluating binary transports can find it?

Happy to do the work either way — adapt the modules to fit your conventions, split things up
differently, or just keep maintaining it independently with a docs link. I'd rather get a scope
answer before writing a PR than have a large one show up unsolicited.

Repo:https://github.com/giridhargg/spring_argo_gql
Maven Central: io.github.giridhargg:gql-argo-core / gql-argo-server-starter /
gql-argo-client-starter

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions