Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Precondition with schema directives

Yosuke Kurami
September 13, 2023

Precondition with schema directives

Yosuke Kurami

September 13, 2023
Tweet

More Decks by Yosuke Kurami

Other Decks in Programming

Transcript

  1. About me { "data": { “user": { "url": "https://github.com/Quramy", "bio":

    "Front-end web developer. TypeScript, Angular and Vim, weapon of choice.", "pinnedItems": { "nodes": [ { "url": "https://github.com/Quramy/tsuquyomi", "description": "A Vim plugin for TypeScript" }, { "url": "https://github.com/Quramy/ts-graphql-plugin", "description": "TypeScript Language Service Plugin for GraphQL developers" }, { "url": "https://github.com/Quramy/lerna-yarn-workspaces-example", "description": "How to build TypeScript mono-repo project with yarn and lerna" }, { "url": "https://github.com/Quramy/typescript-eslint-language-service", "description": "TypeScript language service plugin for ESLint" }, { "url": "https://github.com/Quramy/typed-css-modules", "description": "Creates .d.ts files from CSS Modules .css files" }, { "url": "https://github.com/Quramy/prisma-fabbrica", "description": "Prisma generator to define model factory" } ] } } } } query QuramyQuery { user(login: "Quramy") { url bio pinnedItems(first: 6) { nodes { ... on Repository { url description } } } } }
  2. 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.
  3. 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. https://en.wikipedia.org/wiki/Design_by_contract#Description
  4. 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.
  5. 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
  6. 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.
  7. 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
  8. 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 }
  9. 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
  10. - 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 = GraphQLAssertError.new "Assertion Error" end end end end end def result @assert_error end end end
  11. 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. 
 https://en.wikipedia.org/wiki/Aspect-oriented_programming - (Reusable) We can annotate @assertStgEnv directive if we add more fi elds which need the same contextual precondition.
  12. 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 https://github.com/graphql/graphql- spec/issues/300 .
  13. 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.