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) discovered by Viaduct under a package prefix.
  • 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):

schemaRegistrationInfo = SchemaRegistrationInfo(
    scopes = listOf(
        DEFAULT_SCHEMA_ID.toSchemaScopeInfo(),
        EXTRAS_SCHEMA_ID.toSchemaScopeInfo(),
    ),
    packagePrefix = "com.example.starwars", // Scan the entire com.example.starwars package for graphqls resources
    resourcesIncluded = ".*\\.graphqls"
),

  • packagePrefix: where Viaduct scans for generated types/resolvers.
  • resourcesIncluded: which SDL files to include.
  • 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.