class: center, middle
# GraphQL and Perl 6 Curt Tilmes *Curt.Tilmes@nasa.gov* *The Perl Conference 2017 in DC* 2017-06-21 --- # Introduction * **GraphQL** invented by Facebook to improve mobile app performance * Now widely used within Facebook, and growing in popularity -- * Open Source reference implementation and specification at [graphql.org](http://graphql.org) -- * Implementations: Javascript, Ruby, PHP, Python, Java, C/C++, Go, Scala, .NET, Elixir, Haskell, SQL, Lua, Elm, Clojure, Swift, OCaml --- # Introduction * **GraphQL** invented by Facebook to improve mobile app performance * Now widely used within Facebook, and growing in popularity * Open Source reference implementation and specification at [graphql.org](http://graphql.org) * Implementations: Javascript, Ruby, PHP, Python, Java, C/C++, Go, Scala, .NET, Elixir, Haskell, SQL, Lua, Elm, Clojure, Swift, OCaml... **AND Perl 6!** -- * Many are migrating from RESTful APIs to GraphQL. * Often multiple REST queries can be merged into a single GraphQL query with a single round trip client to server. -- * Check out [https://githubengineering.com/the-github-graphql-api/](https://githubengineering.com/the-github-graphql-api/) --- # Why Perl 6? * Grammars * Introspection * Concurrency and Asynchrony -- * But really: - Looking for a fun project to learn Perl 6! -- - This is my first Perl 6 program (be gentle..) --- .left-col[From GraphQL Specification [facebook.github.io/graphql/](http://facebook.github.io/graphql/)
] -- .right-col[Perl6 GraphQL Grammar GraphQL::Grammar.pm ```grammar rule Selection {
|
|
} rule Field {
?
?
?
? } rule Alias {
':' } rule Arguments { '('
+ ')' } rule Argument {
':'
} rule FragmentSpread { '...'
? } rule InlineFragment { '...'
?
?
} rule FragmentDefinition { 'fragment'
?
} ``` ] --- # Hello World ``` use GraphQL; class Query { method hello() returns Str { 'Hello World' } } my $schema = GraphQL::Schema.new(Query); say $schema.execute('{ hello }').to-json; ``` --- # Hello World ``` use GraphQL; class Query { method hello() returns Str { 'Hello World' } } my $schema = GraphQL::Schema.new(Query); say $schema.execute(`'{ hello }'`).to-json; ``` -- ```response { "data": { "hello": "Hello World" } } ``` --- # Hello World GraphQL Server ``` use GraphQL; `use GraphQL::Server;` class Query { method hello() returns Str { 'Hello World' } } my $schema = GraphQL::Schema.new(Query); `GraphQL-Server($schema);` ``` * Wraps `$schema.execute()` in a simple Bailador server -- * HTTP POST GraphQL Query to `/graphql` * JSON response comes back -- * HTTP GET `/graphql` * returns Facebook Graph*i*QL IDE ---
--- # Perl Schema definition * Three styles: 1. Manual 2. GraphQL Schema Language (GSL) 3. Perl 6 Class introspection -- * All result in the same schema * GSL is just parsed to produce the manual objects * Perl objects are introspected with Perl 6's Metamodel -- * Can be inter-mixed as desired --- # Manual Schema Creation ``` my $schema = GraphQL::Schema.new( GraphQL::Object.new( name => 'Query', description => 'Query is the main GraphQL object', fieldlist => GraphQL::Field.new( name => 'hello', type => $GraphQLString, description => 'Use the hello query to get a greeting', resolver => sub { 'Hello World' } ) ) ); ``` * GraphQL is strongly, staticly typed * GraphQL Types can have a 'name' and 'description' * GraphQL Fields can have a resolver, `Callable` code --- # Some of the GraphQL classes GraphQL Type | Perl Class ------------ | ---------- String | GraphQL::String Int | GraphQL::Int Float | GraphQL::Float Boolean | GraphQL::Boolean ID | GraphQL::ID Scalar | GraphQL::Scalar List | GraphQL::List Non-Null | GraphQL::Non-Null Object/Type | GraphQL::Object Field | GraphQL::Field Interface | GraphQL::Interface Enum | GraphQL::Enum Union | GraphQL::Union Input | GraphQL::Input --- # GraphQL Schema Language (GSL) ``` my $schema = GraphQL::Schema.new(`'type Query { hello: String }'`, resolvers => { Query => { hello => sub { 'Hello World' } } } ); ``` * Compatible with many other language's schema definitions * Pass a two level hash with resolving functions. Object/Field. ---
--- # Perl ``` class Query { method hello() returns Str { 'Hello World' } } my $schema = GraphQL::Schema.new(Query); ``` -- * Some restrictions on how you construct your objects. * All named/typed arguments, including typed return. * Invalid methods (not typed, include '-', etc.) are just ignored. -- | GraphQL Type | Perl Type | |--------------|---------------------| | String | Str | | Int | Int | | Float | Num | | Boolean | Bool | | ID | ID (subset of Cool) | --- # Document your GraphQL Schema using POD ``` #| Query is the main GraphQL object class Query { #| Use the hello query to get a greeting method hello() returns Str { 'Hello World' } } ``` * Each class or method can have POD attached to it. * (You can get these from within Perl with `.WHY`) * Automatically added to your GraphQL type's 'description' field. * 'description' available externally with schema introspection, such as with **Graph*i*QL** --- # Constructing a new GraphQL Server * Model your **data** - Perhaps already in an RDBMS? - How will you access it? SQL? NoSQL? Something else? -- * Define your **schema** (one of the three methods) - Each type of Object, with their fields and types - Each type of Query, their calling signature --- # Example Database ``` class Person { has ID $.id; has Str:D $.name is rw is required; has Str $.birthday is rw; } my @database = Person.new(id => 0, name => 'Gilligan', birthday => 'Friday'), Person.new(id => 1, name => 'Skipper', birthday => 'Monday'), Person.new(id => 2, name => 'Professor', birthday => 'Tuesday'), Person.new(id => 3, name => 'Ginger', birthday => 'Wednesday'), ; ``` --- # Simple Model to access the 'database' ``` class Model { method get-person($id) { @database[$id]; } method list-people($start, $count) { @database[$start ..^ $start+$count] } } ``` --- # Example Schema (GSL): ``` type Person { id: ID name: String! birthday: String } type Query { person(id: ID!): Person listpeople(start: ID!, count: Int!): [Person] } schema { query: Query } ``` -- ``` my $resolvers = { Query => { person => sub (:$id) { $model.get-person($id) }, listusers => sub (:$start, :$count) { $model.list-people($start, $count) } } }; ``` --- # Example Schema (Perl) ``` class Query { method person(ID :$id!) returns Person { $model.get-person($id) } method listpeople(ID :$start!, Int :$count!) returns Array[Person] { $model.list-people($start, $count) } } ``` -- ``` "Type check failed for return value; expected Array[Person] but got List ($(Person.new(id => 0,...)" ``` --- # Example Schema (Perl) ``` class Query { method person(ID :$id!) returns Person { $model.get-person($id) } method listpeople(ID :$start!, Int :$count!) returns Array[Person] { `Array[Person].new`($model.list-people($start, $count)) } } ``` -- ``` my $schema = GraphQL::Schema.new(Person, Query); ``` --- # Simple Query .left-col[ ```query { person(id: 0) { id name birthday } } ```] -- .right-col[ ```response { "data": { "person": { "id": "0", "name": "Gilligan", "birthday": "Friday" } } } ```] --- # Multiple queries, distinguish with aliases .left-col[ ```query { `a`: person(id: 0) { id name birthday } `b`: person(id: 1) { id name birthday } } ```] -- .right-col[ ```response { "data": { "a": { "id": "0", "name": "Gilligan", "birthday": "Friday" }, "b": { "id": "1", "name": "Skipper", "birthday": "Monday" } } } ```] --- # Reuse fieldlists with fragments .left-col[ ```query { a: person(id: 0) { ...somefields } b: person(id: 1) { ...somefields } } fragment somefields on Person { id name birthday } ```] -- .right-col[ ```response { "data": { "a": { "id": "0", "name": "Gilligan", "birthday": "Friday" }, "b": { "id": "1", "name": "Skipper", "birthday": "Monday" } } } ```] --- .left-col[ ```query { listpeople(start: 1, count: 3) { ...somefields } } fragment somefields on Person { id name birthday } ```] -- .right-col[ ```response { "data": { "listpeople": [ { "id": "1", "name": "Skipper", "birthday": "Monday" }, { "id": "2", "name": "Professor", "birthday": "Tuesday" }, { "id": "3", "name": "Ginger", "birthday": "Wednesday" } ] } } ```] --- # Example - Enum ``` `enum State
;` class Person { has ID:D $.id is required; has Str:D $.name is rw is required; has Str $.birthday is rw; `has State $.state is rw;` } my $schema = GraphQL::Schema.new(`State,` Person, Query); ``` --- .left-col[ ```query { listpeople(start: 1, count: 3) { name birthday state } } ```] .right-col[ ```response { "data": { "listpeople": [ { "name": "Skipper", "birthday": "Monday", "state": "UGLY" }, { "name": "Professor", "birthday": "Tuesday", "state": "GOOD" }, { "name": "Ginger", "birthday": "Wednesday", "state": "BAD" } ] } } ```] --- # Input Object ``` class PersonInfo is GraphQL::InputObject { has Str $.name; has Str $.birthday; has State $.state; } ``` --- # Model for Updates ``` class Model { method add-person(PersonInfo $newperson) { my $id = @database.elems; @database.push(Person.new(:$id, name => $newperson.name, birthday => $newperson.birthday, state => $newperson.state)); return $id; } method update-person($id, PersonInfo $input) { for
-> $field { @database[$id]."$field"() = $_ with $input."$field"() } @database[$id] } } ``` --- # Mutation ``` class Mutation { method add(PersonInfo :$new!) returns ID { $model.add-person($new) } method update(ID :$id!, PersonInfo :$input!) returns Person { $model.update-person($id, $input) } } ``` -- ``` my $schema = GraphQL::Schema.new(State, User, Query, `PersonInfo, Mutation`); ``` --- ```query mutation { add(new: { name: "Thurston" }) } ``` -- ```response { "data": { "add": 5 } } ``` -- .left-col[ ```query { person(id: "5") { name birthday state } } ```] -- .right-col[ ```response { "data": { "person": { "name": "Thurston", "birthday": null, "state": null } } } ```] --- # Include variables .left-col[ ```query mutation (`$bday: String`) { update(id: "5", input: { birthday: `$bday`, state: GOOD }) { name birthday state } } ``` ``` { "bday" : "May 1" } ``` * Variables in JSON ] -- .right-col[ ```response { "data": { "update": { "name": "Thurston", "birthday": "May 1", "state": "GOOD" } } } ```] --- # Variable data structure ```query mutation (`$newperson`: PersonInfo) { add(new: `$newperson`) } ``` Variables: ``` { "newperson": { "name": "Lovey", "birthday": "Oct 17", "state": "GOOD" } } ``` * Note, since this is JSON, quote enums! -- ```response { "data": { "add": "6" } } ``` --- # GraphQL * Individual fields and enum values can be marked deprecated, but still work -- * A query can be validated against a schema prior to execution. -- * A parsed, validated query could be cached. Some applications just identify pre-validated queries by identifier rather than allowing arbitrary queries. -- * Reuse queries with variables for things that change rather than making a new query. -- * Publish/Subscribe coming soon for spec, not yet in the Perl version -- * Client libraries and tools are rapidly developing, including client side validation and caching. --- # Conclusion * Perl 6 implementation still in development, please try it out and report issues, bugs, questions, or advice on the interface, etc. Pull Requests especially welcome! -- * Loads more information available at [graphql.org](http://graphql.org), including many youtube videos of talks. * Almost all public information about GraphQL fully applies to the Perl 6 version. -- ##.center[Thank You!]