Schemas
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 set of scope IDs, declared in
src/main/viaduct/scopes.yaml, that controls which fields each slice contains.
The Star Wars demo declares two schema IDs:
version: 1
schemaScopes:
- default
- extras
scopedSchemas:
- id: publicSchema
scopes: [default]
- id: publicSchemaWithExtras
scopes: [default, extras]
publicSchema— the default public surface.publicSchemaWithExtras— includes everything frompublicSchemaplus extra fields marked with@scope(to: ["extras"]).
The build checks every @scope name against schemaScopes and builds both scoped schemas. See Scope Schemas for the file's syntax.
Where schemas are registered¶
Schemas are registered when creating a Viaduct instance. The demo reads the scoped schemas the build declared with SchemaScopeInfo.fromResources() and registers all of them (excerpt from ViaductConfiguration.kt):
withScopedSchemasregisters exactly the listed views. Each declared scoped schema becomes aSchemaScopeInfo.Scoped; addSchemaScopeInfo.Baseto also serve the unfiltered base view. Schemas without@scopeneed neitherscopes.yamlnorwithScopedSchemas.
Organizing SDL files¶
Place each tenant's GraphQL SDL files under its src/main/viaduct/schema directory. 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(isBatching: true) {
"""
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
filmare 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:
- 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
}
- Ensure a scoped schema that includes
extrasis declared inscopes.yaml(publicSchemaWithExtras). - Serve that schema ID on a route in the controller, as the demo does with
/extras/graphql.
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)in Kotlin orcontext.globalIDFor(Type.ofClass(Character.class), internalId)in Java. - 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 route and
schemaIdeach request runs against.
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.
For multi-tenant schema management at the service layer, see the Multi-tenancy service engineer docs. For the full
@scopedirective reference, see the Scopes developer reference.