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. Precondition with
    schema directives
    2023.09.13 GraphQL Tokyo#21

    View Slide

  2. 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


    }


    }


    }


    }


    }

    View Slide

  3. Today’s theme:


    Contextual precondition
    of GraphQL Schema

    View Slide

  4. 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.

    View Slide

  5. 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

    View Slide

  6. GraphQL and DbC
    0QFSBUJPOT
    fi
    FMEWBSJBCMFT

    +40/EBUB
    GPSPVUQVUUZQF
    &YFDVUBCMFTDIFNB
    3FTPMWFS

    View Slide

  7. 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.

    View Slide

  8. 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

    View Slide

  9. GraphQL and DbC
    0QFSBUJPOT
    fi
    FMEWBSJBCMFT

    +40/EBUB
    GPSPVUQVUUZQF
    &YFDVUBCMFTDIFNB
    3FTPMWFS

    $POUFYUVBM
    QSFDPOEJUJPOT
    &YFDVUJPODPOUFYU

    View Slide

  10. 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.

    View Slide

  11. 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

    View Slide

  12. 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


    }

    View Slide

  13. 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

    View Slide

  14. - 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

    View Slide

  15. 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.

    View Slide

  16. 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 .

    View Slide

  17. 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.

    View Slide

  18. Thank you!

    View Slide