Generated Code

What code does Viaduct generate for you?

GraphQL Representational Type (GRT)

The API generates two kinds of classes: GraphQL Representational Types (GRTs), and resolver base classes, described in the Resolvers section.

For each GraphQL type, Viaduct generates a number of Kotlin classes to represent its values. These classes are found in the viaduct.api.grts package. We generate two classes for each GraphQL type: a class representing a value of a given type, and a builder-class allowing you to construct a value of a given type. Consider this simple schema:

type User implements Node {
  id: ID!
  firstName: String
  lastName: String
  displayName: String
}

The signature of the GRT for this type would look approximately like this:

package viaduct.api.grts

class User private constructor(...): NodeObject {
  suspend fun getId(alias: String? = null): GlobalID<User>
  suspend fun getFirstName(alias: String? = null): String?
  suspend fun getLastName(alias: String? = null): String?
  suspend fun getDisplayName(alias: String? = null): String?

  class Builder(ctx: ExecutionContext): DynamicValueOutputBuilder<User> {
    fun id(id: GlobalID<User>): Builder
    fun firstName(firstName: String?): Builder
    fun lastName(lastName: String?): Builder
    fun displayName(displayName: String?): Builder
    override fun build(): User
  }
}

NodeObject is a tagging interface (i.e., an interface with no methods) for GRTs representing GraphQL object types. DynamicValueOutputBuilder is an interface for builders of such types (it is parameterized on T and defines a build function that returns a T).

The values from a fragment on User (for example) are accessed through the GRT for User. As a result, the Viaduct GRTs for object types distinguish fields that are “not set,” because they haven’t been requested for in the fragment, from fields that are in the fragment and thus are “set.” If you attempt to access a field that has not been set, a UnsetSelectionException exception will be thrown, even if that field is nullable. Also, when you build an object-type value, you do not have to set all fields, even if those fields are non-nullable.

The GRTs for interface types are Kotlin interfaces with suspending getters (but no builders), while the GRTs for union types are simply Kotlin “tagging” interfaces (i.e., Kotlin interfaces with no members).

For GraphQL input-object types, the pattern for GRTs is similar to that for output-object types illustrated by User. However, instead of suspending getter functions, the GRTs for input-object types use Kotlin properties for accessing fields. “Partial” input-object types are not possible: every field of an input-object GRT instance is defined (thus, UnsetSelectionException is never thrown when accessing their fields). To achieve this invariant, builders for input-object types are stricter than those for object types: if you call build on an InputType.Builder instance without having set all required fields of that type, then build will raise a runtime error. A field is “required” if it’s defined with the `!` (non-null) wrapper and it has no default value.

Viaduct is what is known as a “schema-first” GraphQL system: developers write GraphQL schema directly, and the Viaduct system generates “GraphQL representational types (GRTs)” to allow developers to read and write the types expressed by the schema.

Any single tenant module consumes only a small fraction of the central schema, so building representational types for the entire schema for every tenant is wasteful. Instead, Viaduct uses “compilation schemas”, a per-tenant-module, private view of the central schema consisting of only the schema elements used by a tenant module. This makes Viaduct builds fast and scalable by ensuring that tenant modules are built in parallel and are only rebuilt when needed.

The tenant module compilation schema is used to generate the GRTs described above. The compilation schema is a subset of the total schema visible to a tenant module, a subset generated by looking at the import statements in the tenant module’s source code. Tenant compilation schemas are always valid, self-contained GraphQL schemas.


Code Structure

Tenant API Code Structure