|
| 1 | +# Field Resolvers |
| 2 | + |
| 3 | +GraphQL schemas define types with named fields, and each of those fields may |
| 4 | +take arguments which alter the behavior of that field. You can think of |
| 5 | +`fields` much like methods on an object instance in OOP (Object Oriented |
| 6 | +Programming). Each field is implemented using a `resolver`, which may |
| 7 | +recursively invoke additional `resolvers` for fields of the resulting objects, |
| 8 | +e.g.: |
| 9 | +```graphql |
| 10 | +query { |
| 11 | + foo(id: "bar") { |
| 12 | + baz |
| 13 | + } |
| 14 | +} |
| 15 | +``` |
| 16 | + |
| 17 | +This query would invoke the `resolver` for the `foo field` on the top-level |
| 18 | +`query` object, passing it the string `"bar"` as the `id` argument. Then it |
| 19 | +would invoke the `resolver` for the `baz` field on the result of the `foo |
| 20 | +field resolver`. |
| 21 | + |
| 22 | +## Top-level Resolvers |
| 23 | + |
| 24 | +The `schema` type in GraphQL defines the types for top-level operation types. |
| 25 | +By convention, these are often named after the operation type, although you |
| 26 | +could give them different names: |
| 27 | +```graphql |
| 28 | +schema { |
| 29 | + query: Query |
| 30 | + mutation: Mutation |
| 31 | + subscription: Subscription |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +Executing a query or mutation starts by calling `Request::resolve` from [GraphQLService.h](../include/graphqlservice/GraphQLService.h): |
| 36 | +```cpp |
| 37 | +std::future<response::Value> resolve(const std::shared_ptr<RequestState>& state, const peg::ast_node& root, const std::string& operationName, response::Value&& variables) const; |
| 38 | +``` |
| 39 | +By default, the `std::future` results are resolved on-demand but synchronously, |
| 40 | +using `std::launch::deferred` with the `std::async` function. You can also use |
| 41 | +an override of `Request::resolve` which lets you substitute the |
| 42 | +`std::launch::async` option to begin executing the query on multiple threads |
| 43 | +in parallel: |
| 44 | +```cpp |
| 45 | +std::future<response::Value> resolve(std::launch launch, const std::shared_ptr<RequestState>& state, const peg::ast_node& root, const std::string& operationName, response::Value&& variables) const; |
| 46 | +``` |
| 47 | + |
| 48 | +### `graphql::service::Request` and `graphql::<schema>::Operations` |
| 49 | + |
| 50 | +Anywhere in the documentation where it mentions `graphql::service::Request` |
| 51 | +methods, the concrete type will actually be `graphql::<schema>::Operations`. |
| 52 | +This `class` is defined by `schemagen` and inherits from |
| 53 | +`graphql::service::Request`. It links the top-level objects for the custom |
| 54 | +schema to the `resolve` methods on its base class. See |
| 55 | +`graphql::today::Operations` in [TodaySchema.h](../samples/separate/TodaySchema.h) |
| 56 | +for an example. |
| 57 | + |
| 58 | +## Generated Service Schema |
| 59 | + |
| 60 | +The `schemagen` tool generates C++ types in the `graphql::<schema>::object` |
| 61 | +namespace with `resolveField` methods for each `field` which parse the |
| 62 | +arguments from the `query` and automatically dispatch the call to a `getField` |
| 63 | +virtual method to retrieve the `field` result. On `object` types, it will also |
| 64 | +recursively call the `resolvers` for each of the `fields` in the nested |
| 65 | +`SelectionSet`. See for example the generated |
| 66 | +`graphql::today::object::Appointment` object from the `today` sample in |
| 67 | +[AppointmentObject.h](../samples/separate/AppointmentObject.h). |
| 68 | +```cpp |
| 69 | +std::future<response::Value> resolveId(service::ResolverParams&& params); |
| 70 | +``` |
| 71 | +In this example, the `resolveId` method invokes `getId`: |
| 72 | +```cpp |
| 73 | +virtual service::FieldResult<response::IdType> getId(service::FieldParams&& params) const override; |
| 74 | +``` |
| 75 | + |
| 76 | +There are also a couple of interesting quirks in this example: |
| 77 | +1. The `Appointment object` implements and inherits from the `Node interface`, |
| 78 | +which already declared `getId` as a pure-virtual method. That's what the |
| 79 | +`override` keyword refers to. |
| 80 | +2. This schema was generated with default stub implementations (without the |
| 81 | +`schemagen --no-stubs` parameter) which speed up initial development with NYI |
| 82 | +(Not Yet Implemented) stubs. With that parameter, there would be no |
| 83 | +declaration of `Appointment::getId` since it would inherit a pure-virtual |
| 84 | +declaration and the implementer would need to define an override on the |
| 85 | +concrete implementation of `graphql::today::object::Appointment`. The NYI stub |
| 86 | +will throw a `std::runtime_error`, which the `resolver` converts into an entry |
| 87 | +in the `response errors` collection: |
| 88 | +```cpp |
| 89 | +throw std::runtime_error(R"ex(Appointment::getId is not implemented)ex"); |
| 90 | +``` |
0 commit comments