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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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