github.com/codykaup/genqlient@v0.6.2/docs/DESIGN.md (about)

     1  # Design decisions
     2  
     3  This file contains a log of miscellaneous design decisions in genqlient.  They aren't all necessarily up to date with the exact implementation details, but are preserved here as context for why genqlient does X thing Y way.
     4  
     5  ## Types
     6  
     7  The main work of this library is in the return types it generates: these are the main difference from other libraries like [shurcooL/graphql](https://github.com/shurcooL/graphql/).  Choosing how to generate these types is thus one of the main design decisions.
     8  
     9  Luckily, there is plenty of prior art for generating types for GraphQL schemas, including:
    10  - Apollo's [codegen](https://github.com/apollographql/apollo-tooling#apollo-clientcodegen-output) tool generates Swift, Scala, Flow, and TypeScript types
    11  - [GraphQL Code Generator](https://www.graphql-code-generator.com/) generates types for TypeScript, C#, Java, and Flow
    12  - Khan Academy has an in-house tool used to generate Flow types for our mobile apps (Khan folks, it's at `react-native/tools/graphql-flow` in the mobile repo)
    13  - In Go, [gqlgen](https://github.com/99designs/gqlgen) generates similar types for the server side of things
    14  - In Go, [shurcooL/graphql](https://github.com/shurcooL/graphql/) doesn't generate types, but uses types in a similar fashion
    15  
    16  But of course all of these have somewhat different concerns, so we need to make our own choices.
    17  
    18  ### Optionality and pointers
    19  
    20  GraphQL has optional and non-optional types: `String` may be null, but `String!` is always set.  Go does not: there's just `string`, and it always has the zero value `""`, but cannot be `nil`.  Some libraries allow the use of pointers for optionality -- `*string` is optional, `string` is required -- but this perhaps [goes against the intended Go style](https://github.com/golang/go/issues/38298#issuecomment-634837296).
    21  
    22  We could refuse to use pointers, which makes it hard for clients that do want to tell `""` from `nil` (we could allow a configurable default); or we could allow their use on an opt-in basis (which also requires a configuration knob); or we could use them whenever a type is optional (which in many schemas is quite often); or we could use another representation of optionality, like `MyField string; HasMyField bool` (or keep the non-optional field and add a method for presence, or whatever).
    23  
    24  This is an important problem because many GraphQL APIs, including Khan Academy's, have a lot of optional fields.  Some are in practice required, or are omitted only if you don't have permissions!  So we don't want to add too much overhead for fields just because the schema considers them optional.
    25  
    26  A related issue is whether large structs (or all structs) should use pointers.  This can be a performance benefit for large structs, as non-pointers would need to be copied on return.  We don't strictly need that except at the toplevel response type (where there is no notion of optionality anyway), but using it might encourage people to avoid passing pointers.
    27  
    28  In other libraries:
    29  - Libraries in many other languages, including JS, don't have this issue; and GraphQL Code Generator in C# doesn't really generate server types.
    30  - gqlgen uses pointers for both optionality and all struct types; this has caused confusion for us.  For them the second problem is harder, as all struct types are return values.
    31  - shurcooL/graphql allows but doesn't require pointers; in practice we tend to use them infrequently on an opt-in basis.
    32  
    33  Here we can also look at non-GraphQL libraries:
    34  - encoding/json, and the other stdlib encoding packages, allow pointers, but typically encourage other approaches like `omitempty` (as discussed in the above link).
    35  - Google Cloud Datastore basically follows encoding/json; at Khan Academy we mostly don't use pointers
    36  - protobuf uses pointers for all structs; proto2 uses pointers for all fields as well (although provides a non-pointer accessor function) whereas proto3 does not use pointers (and application is not supposed to be able to distinguish between zero and unset in any language).
    37  
    38  **Decision:** I do not want to use pointers by default, because I really think it's just terrible Go style.  I think we can actually get away with just not supporting this at all for v0, and then add config options for pointers (which you can use for distinguishing zero and null, or to pass around intermediate values) and possibly also for presence fields, or anything else you want.
    39  
    40  ### Named vs. unnamed types
    41  
    42  Go has a strong distinction between named types (a.k.a. ["defined types"](https://golang.org/ref/spec#Type_definitions), for example, `type MyType struct { ... }`) and unnamed types (defined inline, as for example `struct { ... }`).  We have a choice in how much we want to use named vs. unnamed types.
    43  
    44  Specifically, here are two ways we could generate the type for a simple query `query myQuery { user { id } }`:
    45  
    46  ```go
    47  // unnamed types
    48  type MyQueryResponse struct {
    49      User struct {
    50          ID string
    51      }
    52  }
    53  
    54  // named types
    55  type MyQueryResponse struct {
    56      User User
    57  }
    58  
    59  type User struct {
    60      ID string
    61  }
    62  ```
    63  
    64  (In principle, we could go even further, and make MyQueryResponse a type alias or define it inline in the response type of the function `MyQuery`, but it's not clear this has much value.)
    65  
    66  Each has its advantages.  Named types are the easiest to refer to -- if you want to write a function
    67  ```
    68  func MakeMyQueryAndReturnUser(...) (???, error) {
    69      resp, err := MyQuery(...)
    70      return resp.User, err
    71  }
    72  ```
    73  it's much easier to type in the named types world -- `???` is just `User`, whereas with unnamed types it's `struct { ID string }` or some nonsense like that.  (Some type systems have a way to say "the type of T's field F", but Go's doesn't.)  This is especially relevant in tests, which may want to construct a value of type `MyQueryResponse`.  They are also necessary to some approaches to dealing with interfaces and fragments (see the next section).
    74  
    75  But named types also cause a big problem, which is the names.  We can't actually necessarily just name the type above `User` -- there may be multiple `User` types in a single package, or even in a single query.  For example, the query `{ user { id children { id } } }` needs to look something like:
    76  
    77  ```go
    78  type MyQueryResponse struct {
    79      User User1
    80  }
    81  
    82  type User1 struct {
    83      ID string
    84      Children []User2
    85  }
    86  
    87  type User2 struct {
    88      ID string
    89  }
    90  ```
    91  
    92  Moreover, these types names should be as stable as possible: changing one part of a query (or another query) shouldn't change the type-names.  And ideally, we might deduplicate: in a query like `{ self { id } myChildren { id } }` we'd be able to use a single type for both `self` and `myChildren`.  Although maybe, if you want that, you have to use a fragment; it's clearly in conflict with having stable names.  Or we can actually make this configurable!
    93  
    94  In other tools:
    95  - Apollo mostly uses named types (except alternatives of an interface/fragment are inline in TypeScript, but not in Flow), and in the above example names them, respectively, `MyQuery_user_User` and `MyQuery_user_User_children_User`, or maybe in some versions `MyQuery_user` and `MyQuery_user_children` in Flow/TypeScript.  In Java and Scala in some cases they use nested types, e.g. `MyQuery.User.Children`.
    96  - GraphQL Code Generator generates mostly unnamed types in Flow/TypeScript, with named types for named fragments only (except really it generates server-style types and then generates a mess of unnamed `$Pick`s from those); and basically just generates server-style types for other languages
    97  - Khan's mobile autogen uses entirely unnamed types.
    98  - gqlgen doesn't have this problem, because on the server each GraphQL type maps to a unique Go type; and to the extent that different queries need different fields this is handled at the level of which fields are serialized or which resolvers are called.
    99  - shurcooL/graphql allows either way.
   100  
   101  A side advantage of an approach that prefixes the query name to all the type names, for us, is that it means that the caller can decide in a very simple way whether to make the name exported, and avoids conflicts between queries.
   102  
   103  **Decision:** In general, it seems like even in languages with better ergonomics for unnamed types, Apollo's approach is somewhat reasonable.  And unnamed types will get really hairy for large queries in Go.  On some level, even if the naming scheme is bad, it won't be as bad as unnamed types -- if you don't need to refer to the intermediate type, you don't care, and if you do, it's better than a giant inline struct.  (But it's hard to change later without a flag as existing code may depend on those types.)
   104  
   105  We'll do something similar to Apollo's naming scheme.  Specifically:
   106  - The toplevel name will be `MyQueryResponse`, using the query-name.
   107  - Further names will be `MyQueryFieldTypeFieldType`.  We will not attempt to be super smart about avoiding conflicts.
   108  - Fragments will have some naming scheme TBD but starting at the fragment.
   109  - Input objects will have a name starting at the type, since they always have the same fields, and often have naming schemes like "MyFieldInput" already.
   110  
   111  See `generate/names.go` for the precise algorithm.
   112  
   113  All of this may be configurable later.
   114  
   115  ### How to represent interfaces
   116  
   117  Consider the following query (suppose that `a` returns interface type `I`, which may be implemented by either `T` or `U`):
   118  
   119  ```graphql
   120  query { a { __typename b ...F } }
   121  fragment F on T { c d }
   122  ```
   123  
   124  Depending on whether the concrete type returned from `a` is `T`, we can get one of two result structures back:
   125  
   126  ```json
   127  {"a": {"__typename": "T", "b": "...", "c": "..." , "d": "..."}}
   128  {"a": {"__typename": "U", "b": "..."}}
   129  ```
   130  
   131  The question is: how do we translate that to Go types?
   132  
   133  **Go interfaces:** one natural option is to generate a Go type for every concrete GraphQL type the object might have, and simply inline or embed all the fragments.  So here we would have
   134  ```go
   135  type T struct{ B, C, D string }
   136  type U struct{ B string }
   137  type I interface{ isI() }
   138  
   139  func (t T) isI() {}
   140  func (u U) isI() {}
   141  
   142  type Response struct{ A I }
   143  ```
   144  
   145  (This requires that these types, at least, be named, in the sense of the previous section.)  In this approach, the two objects above would look like
   146  
   147  ```go
   148  Response{A: T{B: "...", C: "...", D: "..."}}
   149  Response{A: U{B: "..."}}
   150  ```
   151  
   152  We can also define a getter-method `GetB() string` on `T` and `U`, and include it in `I`, so that if you only need `B` you don't need to type-switch.  Or, if that's not enough, we can also define a common embedded struct corresponding to the interface, so you can extract and pass that around if you want:
   153  
   154  ```go
   155  type IEmbed struct { B string }
   156  type T struct { IEmbed; C, D string }
   157  type U struct { IEmbed }
   158  type I interface { isI(); GetIEmbed() IEmbed }
   159  ```
   160  
   161  Note that this option gives a few different ways to represent fragments specifically, discussed in the next section.
   162  
   163  **Fragment fields:** another natural option, which looks more like the way `shurcooL/graphql` does things, is to generate a type for each fragment, and only fill in the relevant ones:
   164  ```go
   165  type Response struct {
   166      A struct {
   167          B string
   168          F struct { // optional
   169              C, D string
   170          }
   171      }
   172  }
   173  ```
   174  
   175  (This works with named or unnamed types as discussed in the previous section.)  In this approach, the two objects above would look like
   176  
   177  ```go
   178  Response{A: {B: "...", F: {C: "...", D: "..."}}}
   179  Response{A: {B: "..."}}
   180  ```
   181  
   182  (Note the types are omitted since they're not named.)
   183  
   184  Each approach also naturally implies a different way to handle a query that uses interface types without fragments.  In particular, consider the query
   185  
   186  ```graphql
   187  query { a { b } }
   188  ```
   189  
   190  using the same schema as above.  In the former approach, we still define three types (plus two receivers); and `resp.A` is still of an interface type; it might be either `T` or `U`.  In the latter approach, this looks just like any other query: `resp.A` is a `struct{ B string }`.  This has implications for how we use this data: the latter approach lets us just do `resp.A.B`, whereas the former requires we do a type-switch, or add a `GetB()` method to `I`, and do `resp.A.GetB()`.
   191  
   192  
   193  **Pros of Go interfaces:**
   194  
   195  - it's the most natural translation of how GraphQL does things, and provides the clearest guarantees about what is set when, what is mutually exclusive, etc.
   196  - you always know what type you got back
   197  - you always know which fields are there -- you don't have to encode at the application level an assumption that if fragment A was defined, fragment B also will be, because all types that match A also match B
   198  
   199  **Pros of fragment fields:**
   200  
   201  - the types are simpler, and allow the option of unnamed types
   202  - if you query an interface, but don't care about the types, just the shared fields, you don't even have to think about any of this stuff
   203  - for callers accessing shared fields (of interfaces or of fragments spread into several places) we avoid having to make them do a type switch or use getter methods
   204  
   205  Note that both approaches require that we add `__typename` to every selection set which has fragments (unless the types match exactly).  This seems fine since Apollo client also does so for all selection sets.  We also need to define `UnmarshalJSON` on every type with fragment-spreads; in the former case Go doesn't know how to unmarshal into an interface type, while in the latter the Go type structure is too different from that of the JSON.  (Note that `shurcooL/graphql` actually has its own JSON-decoder to solve this problem.)
   206  
   207  **Flatten everything:** a third non-approach is to simplify define all the fields on the same struct, with some optional:
   208  
   209  ```go
   210  type Response struct {
   211      A struct {
   212          B string
   213          C *string
   214          D *string
   215      }
   216  }
   217  ```
   218  
   219  Apart from being semantically messy, this doesn't have a good way to handle the case where there are types with conflicting fields, e.g.
   220  
   221  ```graphql
   222  interface I {}
   223  type T implements I { f: Int }
   224  type U implements I { f: String }
   225  
   226  query {
   227      a {
   228          ... on T { f }
   229          ... on U { f }
   230      }
   231  }
   232  ```
   233  
   234  What type is `resp.A.F`?  It has to be both `string` and `int`.
   235  
   236  **In other libraries:**
   237  - Apollo does basically the interface approach, except with TypeScript/Flow's better support for sum types.
   238  - GraphQL Code Generator does basically the interface approach (except with sum types instead of interfaces), except with unnamed types.  It definitely generates some ugly types, even with TypeScript/Flow's better support for sum types!
   239  - Khan's mobile autogen basically does the interface approach (again with unnamed types, and sum types).
   240  - gqlgen doesn't have this problem; on the server, fragments and interfaces are handled entirely in the framework and need not even be visible in user-land.
   241  - shurcooL/graphql uses fragment fields.
   242  - protobuf has a similar problem, and uses basically the interface approach in Go (even though in other languages it uses something more like flattening everything).
   243  
   244  **Decision:** In general, it seems like the GraphQL Way, and perhaps also the Go Way is to use Go interfaces; I've always found the way shurcooL/graphql handles this to be a bit strange.  So I think it has to be at least the default.  In principle we could allow both though, since fragment fields are legitimately convenient for some cases, especially if you are querying shared fields of interfaces or using fragments as a code-sharing mechanism, since using Go interfaces handles those only through getter methods (although see below).
   245  
   246  ### How to support fragments
   247  
   248  The previous section leaves some parts of how we'll handle fragments ambiguous.  Even within the way it lays things out, we generally have two options for how to represent a fragment.  Consider a query
   249  
   250  ```graphql
   251  query MyQuery { a { b ...F } }
   252  fragment F on T { c d }
   253  ```
   254  
   255  We'll have some struct (potentially several structs, one per implementation) representing the type of the value of `a`.  We can handle the fragment one of two ways: we can either flatten it into that type, such that the code is equivalent to writing `query Q { a { b c d } }` (except that `c` and `d` might be included for only some implementations, if `a` returns an interface), or we can represent it as a separate type `F` and embed it in the relevant structs.  (Or it could be a named field of said structs, but this seems to have little benefit, and it's nice to be able to access fields without knowing what fragment they're on.)  When the spread itself is abstract, there are a few sub-cases of the embed option.
   256  
   257  To get more concrete, there are [four cases](https://spec.graphql.org/June2018/#sec-Fragment-spread-is-possible) depending on the types in question.  (In all cases below, the methods, and other potential implementations of the interfaces, are elided.  Note also that I'm not sure I got all the type-names right exactly as genqlient would, but they should match approximately.)
   258  
   259  **Object spread in object scope:** The simplest spread is when we have an object-typed fragment spread into an object-typed selection.  (The two object types must be the same.)  This is typically used as a code-sharing mechanism.
   260  
   261  ```graphql
   262  type Query { a: A }
   263  type A { b: String, c: String, d: String }
   264  
   265  query MyQuery { a { b ...F } }
   266  fragment F on A { c d }
   267  ```
   268  
   269  ```go
   270  // flattened:
   271  type MyQueryA struct { B, C, D string }
   272  
   273  // embedded
   274  type MyQueryA struct { B string; F }
   275  type F struct { C, D string }
   276  ```
   277  
   278  **Abstract spread in object scope:** We can also spread an interface-typed fragment into an object-typed selection, again as a code-sharing mechanism.  (The object must implement the interface.)
   279  
   280  ```graphql
   281  type Query { a: A }
   282  type A implements I { b: String, c: String, d: String }
   283  interface I { c: String, d: String }
   284  
   285  query MyQuery { a { b ...F } }
   286  fragment F on I { c d }
   287  ```
   288  
   289  ```go
   290  // flattened:
   291  type MyQueryA struct { B, C, D string }
   292  
   293  // embedded:
   294  type MyQueryA struct { B string; FA }
   295  type F interface { isF(); GetC() string; GetD() string } // for code-sharing purposes
   296  type FA struct { C, D string } // implements F
   297  ```
   298  
   299  **Object spread in abstract scope:** This is the most common spread, perhaps, where you spread an object-typed fragment into an interface-typed selection in order to request some fields defined on a particular implementation of the interface.  (Again the object must implement the interface.)
   300  
   301  ```graphql
   302  type Query { a: I }
   303  type A implements I { b: String, c: String, d: String }
   304  type T implements I { b: String, u: String, v: String }
   305  interface I { b: String }
   306  
   307  query MyQuery { a { b ...F ...G } }
   308  fragment F on A { c d }
   309  fragment G on A { u v }
   310  ```
   311  
   312  ```go
   313  // flattened:
   314  type MyQueryAI interface { isMyQueryAI(); GetB() string }
   315  type MyQueryAIA struct { B, C, D string } // implements MyQueryAI
   316  type MyQueryAIT struct { B, U, V string } // implements MyQueryAI
   317  
   318  // embedded:
   319  type MyQueryAI interface { isMyQueryAI(); GetB() string }
   320  type MyQueryAIA struct { B string; F } // implements MyQueryAI
   321  type MyQueryAIT struct { B string; G } // implements MyQueryAI
   322  type F struct { C, D string }
   323  type G struct { U, V string }
   324  ```
   325  
   326  **Abstract spread in abstract scope:** This is a sort of combination of the last two, where you spread one interface's fragment into another interface, and can be used for code-sharing and/or to conditionally request fields.  Perhaps surprisingly, this is legal any time the two interfaces share an implementation, and neither need implement the other; this means there are arguably four cases of spreading a fragment of type `I` into a scope of type `J`: `I = J` (similar to object-in-object), `I implements J` (similar to abstract-in-object), `J implements I` (similar to object-in-abstract), and none of the above (which is quite rare).
   327  
   328  ```graphql
   329  type Query { a: I }
   330  type A implements I & J { b: String, c: String, d: String }
   331  type T implements I { b: String }
   332  type U implements J { c: String, d: String, v: String }
   333  interface I { b: String }
   334  interface J { c: String, d: String }
   335  
   336  query MyQuery { a { b ...F } }
   337  fragment F on J { c d }
   338  ```
   339  
   340  ```go
   341  // flattened:
   342  type MyQueryAI interface { isMyQueryAI(); GetB() string }
   343  type MyQueryAIA struct { B, C, D string } // implements MyQueryAI (and MyQueryAJ if generated)
   344  type MyQueryAIT struct { B }              // implements MyQueryAI
   345  
   346  // embedded:
   347  type MyQueryAI interface { isMyQueryAI(); GetB() string }
   348  type MyQueryAIA struct { B string; FA } // implements MyQueryAI
   349  type MyQueryAIT struct { B string } // implements MyQueryAI
   350  type F interface { isF(); GetC() string; GetD() string }
   351  type FA struct { C, D string } // implements F
   352  type FU struct { C, D string } // implements F (never used; might be omitted)
   353  // if I == J or I implements J, perhaps additionally:
   354  type MyQueryAI interface { isMyQueryAI(); GetB() string; F }
   355  ```
   356  
   357  Note in this case a third non-approach is
   358  
   359  ```go
   360  // does not work:
   361  type MyQueryAI interface { isMyQueryAI(); GetB() string }
   362  type MyQueryAIA struct { B string; F } // implements MyQueryAI
   363  type F struct { C, D string }
   364  ```
   365  
   366  This doesn't work because the fragment F might itself embed other fragments of object type.
   367  
   368  **Inline and named fragments:** Another complication is that in each of these cases, the fragment might be inline (`... on T { ... }`) or defined (`...F` where `fragment F on T { ... }`).  The latter is useful for sharing code, whereas the former may be more preferable when we just want request fields from a particular type.  Note that inline fragments have no natural name of their own; in the embedding approach we'd need to generate one.  (This is extra awkward because there's no prohibition on having several inline fragments of the same type in the same selection, e.g. `{ ... on T { id }, ... on T { name } }`, so even adding the type-name doesn't produce a unique name.)
   369  
   370  **Pros of flattening:**
   371  
   372  - Makes for the simplest resulting types, by far; the fragment adds no further complexity to the types.
   373  - Especially a simplification when embedding abstract-typed fragments, since you don't have to go through an extra layer of interface when you already have a concrete type.
   374  - More efficient when multiple fragments spread into the same selection contain the same field: we need only store it once whereas embedding must copy it once for each fragment.  Also easier to use in the same case, since if you have both `val.FragmentOne.MyField` and `val.FragmentTwo.MyField`, you can't access either via `val.MyField`.  (Empirically in the Khan Academy codebase this appears to be quite rare.)
   375  - If you need to manually construct values of genqlient-generated types, flattening will be a lot easier, but I don't really recommend doing that.
   376  
   377  **Pros of embedding:**
   378  
   379  - Results in cleaner type-names, since each (named) fragment can act as its own "root" for naming purposes.  (In principle we could do this even when flattening, although it might read somewhat awkwardly.)
   380  - Much more usable for deduplication; if you spread the fragment in several places in your query you can write a function which accepts an `F`, and pass it any of the data.  (Again we might be able to get this benefit, awkwardly, when flattening by generating an interface corresponding to each fragment, assuming we are also rooting the type-names at the fragment.)
   381  - It's easier to tell what fields go where; the casework for abstract-in-abstract embeds gets somewhat complex.
   382  - Arguably the most philosophically faithful representation of the GraphQL types in Go.
   383  
   384  Note in principle we could apply some of those benefits 
   385  
   386  **Decision:** There are pros and cons both ways here; in general it seems embedding is the most natural where your goal is deduplication, whereas flattening is best for inline fragments; for named fragments used only once there's maybe a slight benefit to flattening but it's not a big difference either way.  If we have to go with one or the other, probably flattening is better.  But I think the best thing, unless it turns out to be too much work to implement, is probably just to flatten inline fragments and embed named ones.  (In principle we could also have a flag flatten named fragments, if we find a need.)
   387  
   388  ## Configuration and runtime
   389  
   390  ### General configuration
   391  
   392  We will need some configuration for the library.  Global configuration we can easily put in a file, but per-query configuration makes the most sense inline.  We will eventually want to configure things like:
   393  - optionality, perhaps (see above)
   394  - collapsing (e.g. a query `{ a { b { c } } }` could, if configured, just return the `c`, so you don't have to unpack)
   395  
   396  One option is to put configuration in GraphQL directives.  The problem is that the server won't understand them!  So we'd have to remove them from the query; this means we're not sending the exact query we got to the server, which could mess with safelisting, hashing, etc.
   397  
   398  Instead, we will likely have to do that configuration in comments, which we already extract to use in docstrings.  We'll have to figure out how to support both.
   399  
   400  Or, we can figure out how to avoid configuration entirely.  This seems short-sighted but may be possible if we can solve for optionality another way and decide collapsing is a non-issue.
   401  
   402  **Decision:** We'll configure with comments on the field of the form `# @genqlient(...)`, syntax TBD but similar to a GraphQL directive.
   403  
   404  ### Query function signatures (context/client)
   405  
   406  Many users will want to give us a context.  Some may want to customize their HTTP client in arbitrary ways.  Everyone needs a place to thread in the URL.  A common way to handle the latter issues is with a client object.
   407  
   408  Given that, here are some sample signatures for a function that accepts a single ID variable:
   409  
   410  ```go
   411  // None of the above
   412  func GetUser(id string) (*GetUserResponse, error)
   413  
   414  // Uses a standard context
   415  func GetUser(ctx context.Context, id string) (*GetUserResponse, error)
   416  
   417  // Uses a custom context (used at Khan; this may become more common post-generics)
   418  func GetUser(ctx mypkg.Context, id string) (*GetUserResponse, error)
   419  
   420  // Uses a client object
   421  func GetUser(client graphql.Client, id string) (*GetUserResponse, error)
   422  
   423  // Uses both
   424  func GetUser(ctx context.Context, client graphql.Client, id string) (*GetUserResponse, error)
   425  ```
   426  
   427  Additionally, users may want to get the client from the context, using a custom method like Khan's KAContext or just ordinary `context.Value`.
   428  
   429  This can all be configurable globally -- say you can decide whether to use context and client, and optionally provide the type of your context and/or a function that gets client from it, or something.  We'll want to pick a good default before we have external users, so as not to break them, but it's easy enough to change the Khan-specific parts via codemod later.
   430  
   431  **Decision:** It seems easy enough to allow all of this to be configured: you can specify no context, a specific context type, or the default of context.Context; and then if you want you can specify a way to get the client from context or a global.  We'll need both hooks at Khan, and it's not much harder to add them in a generalizable way.
   432  
   433  ### Query extraction (for safelisting)
   434  
   435  One thing we want to be able to do is to make it clear exactly what query-document (down to comments and whitespace) we will be sending in the query for the purposes of safelisting and querying based on hash.  In the case where you have one query per file, that's easy, just use the whole file.  But you may want to share fragments between queries, in which case this is trouble: you either need a way to include fragments from another file (and a defined concatenation order), or you need to have several queries per file, and either have genqlient extract the right parts (in a defined/reproducible way), or have it send up the full file and the operation name to use (in which case we should still encourage you to not do that unless you're hashing, so that you aren't sending up too much data).  We could also allow configuration between the last two options (so if you don't care about hashing/safelisting you can auto-extract).  A variant is to have genqlient write out a list of what it thinks the final queries will be (it has them to include in the generated code), in which case you don't have to care how smart that is.
   436  
   437  We can decide this later once we support fragments and safelisting/hashing; it's fine if that's not available at first.