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

Precondition with schema directives

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.