Slide 1

Slide 1 text

Precondition with schema directives 2023.09.13 GraphQL Tokyo#21

Slide 2

Slide 2 text

About me { "data": { “user": { "url": "", "bio": "Front-end web developer. TypeScript, Angular and Vim, weapon of choice.", "pinnedItems": { "nodes": [ { "url": "", "description": "A Vim plugin for TypeScript" }, { "url": "", "description": "TypeScript Language Service Plugin for GraphQL developers" }, { "url": "", "description": "How to build TypeScript mono-repo project with yarn and lerna" }, { "url": "", "description": "TypeScript language service plugin for ESLint" }, { "url": "", "description": "Creates .d.ts files from CSS Modules .css files" }, { "url": "", "description": "Prisma generator to define model factory" } ] } } } } query QuramyQuery { user(login: "Quramy") { url bio pinnedItems(first: 6) { nodes { ... on Repository { url description } } } } }

Slide 3

Slide 3 text

Today’s theme: Contextual precondition of GraphQL Schema

Slide 4

Slide 4 text

GraphQL Schema as “Contract” - Server (Suppllier): - GraphQL server should resolve query results if operations are valid. - Client (Consumer): - GraphQL client should send operations de fi ned in schema.

Slide 5

Slide 5 text

Design by Contract The central idea of DbC is a metaphor on how elements of a software system collaborate with each other on the basis of mutual obligations and bene fi ts. The metaphor comes from business life, where a "client" and a "supplier" agree on a "contract" that de fi nes, for example, that: - The supplier must provide a certain product (obligation) and is entitled to expect that the client has paid its fee (bene fi t). - The client must pay the fee (obligation) and is entitled to get the product (bene fi t). - Both parties must satisfy certain obligations, such as laws and regulations, applying to all contracts.

Slide 6

Slide 6 text


Slide 7

Slide 7 text

What’s precondition ? - GraphQL speci fi cation guarantees the followings: - Field names in operation are de fi ned in schema - The values of the fi led variables are valid types. - If the client violates these preconditions, GraphQL engine throws validation exception.

Slide 8

Slide 8 text

Field resolver’s arguments - Resolver takes 4 arguments: 
 object resolved by parent, fi eld arguments, context, and metadata - Not but fi eld name and fi eld arguments, also context affects the query result so much. - How to give precondition for the execution context 🤔? export const QueryTypeResolver = { hogeFuga: async(_, fieldArgs, context, _metadata) => { const { code } = fieldArgs const results = await context.prisma.hogeFuga.findMany({ where: { code } }) return results } } satisfies QueryResolver

Slide 9

Slide 9 text


Slide 10

Slide 10 text

Preconditioning with context - Examples of fi elds preconditioned for execution context: 1. “Mutation fi eld executable only for authenticated user attached speci fi c authorization” 
 ( context[:current_user].has_authorities? ) 2. “Query fi eld executable only for staging environment in order to debug” 
 ( env.fetch “RUNTIME_ENVIRONMENT” == “STAGING”) - If these preconditions are established, the postconditions get more sharp and offensive.

Slide 11

Slide 11 text

Precondition make postcondition more offensive - Defensive schema: 
 Output type de fi ned as optional value (coalescing to null) - Offensive schema: 
 Output type de fi ned as strict value type Query { """ Resolve null unless staging env """ hogeFuga: String } type Query { """ (Precondition) Throw assertion error unless staging env. """ hogeFuga: String! } More strict😄 It can be null

Slide 12

Slide 12 text

Expose precondition for context as directive - Next problem: “How to expose contextual precondition to schema?” - Custom schema directives - With GraphQL SDL: # schema.graphql directive @assertStgEnv on FIELD_DEFINITION type Query { hogeFuga: String! @assertStgEnv }

Slide 13

Slide 13 text

Expose precondition for context as directive - With graphql-ruby # app/graphql/directives/assert_stg_env.rb module Directives class AssertStgEnv < GraphQL::Schema::Directive locations FIELD_DEFINITION end end # app/graphql/types/query_type.rb module Types class QueryType < Types::BaseObject field :hoge_fuga, String, null: false, directives: { Directives::AssertStgEnv => {}} end end

Slide 14

Slide 14 text

- We can use AST Analyzer to implement to check precondition corresponding to schema directives: module Analyzers class GraphQLAssertError < GraphQL::AnalysisError end class AssertionAnalyzer < GraphQL::Analysis::AST::Analyzer def initialize(subject) super @assert_error = nil end def on_enter_field(node, _parent, visitor) if @assert_error.nil? field_definition = visitor.field_definition field_definition&.directives.each do |directive| if directive.is_a? Directives::AssertStgEnv unless env.fetch(“RUNTIME_ENV”, nil) == "STAGING" @assert_error = "Assertion Error" end end end end end def result @assert_error end end end

Slide 15

Slide 15 text

Aspect Oriented Programming - (Separation of concern) The @assertStgEnv directive separates the assertion logic from the fi eld(:hoge_fuga) resolver implementation. - In other words, we can recognize @assertStgEnv assertion logic as an Aspect. - (Reusable) We can annotate @assertStgEnv directive if we add more fi elds which need the same contextual precondition.

Slide 16

Slide 16 text

Caveat - For now, GraphQL schema directives can not be introspected. - Introspection query re fl ects only @deperecated directive. - So, custom schema directives are only “structured documents” for client applications. - If you want more details, see spec/issues/300 .

Slide 17

Slide 17 text

Conclusion - GraphQL schema is “contract” between server and client . - Preconditions make application components more sharp and offensive. - We can de fi ne schema directives to explain preconditions for execution context. - Custom scheme directive can be recognized as an aspect.

Slide 18

Slide 18 text

Thank you!