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