Schemas

How schemas are defined, registered, and selected at runtime in the Star Wars demo using Viaduct.

In Viaduct, a schema describes the GraphQL surface that a request can access. In the Star Wars demo, we register two schema IDs — one public and one with extra fields — and select between them at runtime based on scopes.

What a schema is

  • A set of SDL files (*.graphqls) under src/main/viaduct/schema.
  • A schema ID that names a concrete slice of the overall SDL (for example, public vs. public-with-extras).
  • A scope configuration that controls which requests can see each slice.

The Star Wars demo defines two schema IDs:

  • PUBLIC_SCHEMA — the default public surface.
  • PUBLIC_SCHEMA_WITH_EXTRAS — includes everything from PUBLIC_SCHEMA plus extra fields marked with @scope(to: ["extras"]).

Where schemas are registered

Schemas are registered in configuration, providing scope bindings and SDL discovery settings (package prefix and resource regex). Example (excerpt adapted from ViaductConfiguration.kt):

Tag not found: schema_registration in demoapps/starwars/src/main/kotlin/com/example/starwars/service/viaduct/ViaductConfiguration.kt

  • grtPackagePrefix: optional package prefix for GRT schema file discovery (test-only override, production uses defaults).
  • grtResourcesIncluded: optional regex pattern for which SDL files to include (test-only override, production uses defaults).
  • tenantPackagePrefix: where Viaduct scans for generated resolver classes.
  • scopes: which runtime scopes activate each schema ID.

Organizing SDL files

Place your GraphQL SDL files under the configured resources path so they are included by resourcesIncluded. Keep entities modular (for example, character.graphqls, film.graphqls, species.graphqls) and use directives like @scope, @idOf, and @oneOf where appropriate.

Example (fragment)

type Planet implements Node @scope(to: ["default"]) @resolver {
 """
 The ID of an object
 """
 id: ID!

 """
 The name of this planet.
 """
 name: String

 """
 The diameter of this planet in kilometers.
 """
 diameter: Int

 """
 The number of standard hours it takes for this planet to complete a single
 rotation on its axis.
 """
 rotationPeriod: Int

 """
 The number of standard days it takes for this planet to complete a single orbit
 of its local star.
 """
 orbitalPeriod: Int

 """
 A number denoting the gravity of this planet, where "1" is normal or 1 standard
 G. "2" is twice or 2 standard Gs. "0.5" is half or 0.5 standard Gs.
 """
 gravity: Float

 """
 The average population of sentient beings inhabiting this planet.
 """
 population: Float

 """
 The percentage of the planet surface that is naturally occurring water or bodies
 of water.
 """
 surfaceWater: Float

 """
 The ISO 8601 date format of the time that this resource was created.
 """
 created: String

 """
 The ISO 8601 date format of the time that this resource was edited.
 """
 edited: String

 """
 The residents of this planet.
 """
 terrains: [String]

 """
 The films that this planet has appeared in.
 """
 climates: [String]

 """
 The residents of this planet.
 """
 residents(limit: Int): [Character!] @resolver

 """
 The films that this planet has appeared in.
 """
 films(limit: Int): [Film!] @resolver
}

Fields like film are typically resolved by field/batch resolvers, not embedded in SDL logic.

Evolving the schema

To add a field that should exist only in the extras slice:

  1. Declare it in SDL with a scope:
extend type Species @scope(to: ["extras"]) {
 culturalNotes: String @resolver
 rarityLevel: String @resolver
 specialAbilities: [String] @resolver
 technologicalLevel: String @resolver
}

  1. Ensure the schema ID associated with extras is registered (PUBLIC_SCHEMA_WITH_EXTRAS).
  2. The controller will route requests that include the extras scope to that schema ID.

To add a new entity:

  • Define the type and relationships in SDL.
  • Implement node/field (and batch if needed) resolvers.
  • Emit typed IDs with ctx.globalIDFor(Type.Reflection, internalId).
  • Write integration tests that hit both schema IDs if visibility differs.

Testing and troubleshooting

  • Validate that the expected fields appear in introspection under each schema ID.
  • Run end-to-end queries for both public and extras slices.
  • Confirm that resolvers are wired and batched correctly when many parents are selected.
  • Log the chosen schemaId and active scopes for each request.

Do and don’t

  • Do register schema IDs with clear scope sets (public vs. public + extras).
  • Do keep SDL modular and use directives for visibility and type-safety.
  • Don’t rely on a single “mega schema” and conditional logic inside resolvers to hide fields.
  • Don’t mix raw IDs with Global IDs; declare @idOf where applicable.