class: center, middle
# GraphQL and Perl 6 Curt Tilmes *Curt.Tilmes@nasa.gov* *Milwaukee Perl Mongers* 2017-05-31 --- # 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/) --- # Hello World ``` use GraphQL; class Query { method hello(--> Str) { 'Hello World' } } my $schema = GraphQL::Schema.new(Query); say $schema.execute('{ hello }').to-json; ``` --- # Hello World ``` use GraphQL; class Query { method hello(--> Str) { 'Hello World' } } my $schema = GraphQL::Schema.new(Query); say $schema.execute('`{ hello }`').to-json; ``` -- ```response { "data": { "hello": "Hello World" } } ``` --- ![](graphql1.svg) --- # Hello World GraphQL Server ``` use GraphQL; `use GraphQL::Server;` class Query { method hello(--> 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` with no parameters * returns Facebook Graph*i*QL IDE ---
--- ![](graphql2.svg) * Pass three things in: Query Document, Operation Name, and Variables * Schema coordinates Validation and Execution * Introspection allows Execution of queries on the Schema itself * Execution calls your code for Resolution of Objects and Fields --- # 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', fieldlist => GraphQL::Field.new( name => 'hello', type => $GraphQLString, resolver => sub { 'Hello World' } ) ) ); ``` * GraphQL is strongly, staticly typed * GraphQL Types can have a 'name' and 'description' * GraphQL Fields can have a resolver --- # 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. -- | GraphQL Type | Perl Type | |--------------|---------------------| | String | Str | | Int | Int | | Float | Num | | Boolean | Bool | | ID | ID (subset of Cool) | --- # 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 * We're going to model (a very simplified form of) the Perl 6 Modules List * Three primary types: **Author**, **Tag**, and **Distribution** * A **Distribution** can have 1 **Author** and 0 or more **Tag**s * An **Author** can have 1 or more **Distribution**s * A **Tag** can have 1 or more **Distribution**s --- # Example - Redis Database * [Redis](https://redis.io/) is a very nice (very fast) NoSQL database * It can store many different data structures, including **Set** * We're going to access it with [Redis::Async](https://github.com/CurtTilmes/perl6-eredis) * Grab the JSON modules list from [https://modules.perl6.org/.json](https://modules.perl6.org/.json) --- # Example, Loading Redis - For each distribution - Add the author_id to the *authors* **Set** - Add the name to the *dists* **Set** - Store the distribution JSON Hash in *dist:(name)* - Store the author JSON Hash in *author:(author_id)* - Add the distribution name to the *author-dist:(author_id)* **Set** - For each tag - Add the tag to the *tags* **Set** - Add the tag JSON Hash to *tag:(tag)* - Add the distribution name to the *tag-dist:(tag)* **Set** --- # Example, Loading Redis ``` for jget('https://modules.perl6.org/.json')
.list -> $dist { $r.sadd("authors", $dist
); $r.sadd("dists", $dist
); $r.setnx("dist:$dist
", to-json($dist)); my $author-rec = { author_id => $dist
}; $r.setnx("author:$dist
", to-json($author-rec)); $r.sadd("author-dist:$dist
", $dist
); for $dist
.list -> $tag { $r.sadd("tags", $tag); my $tag-rec = { tag => $tag }; $r.setnx("tag:$tag", to-json($tag-rec)); $r.sadd("tag-dist:$tag", $dist
); } } ``` --- # Example, Simple Model to Access Data ``` class Model { method authors() { $r.smembers('authors') } method distributions() { $r.smembers('dists') } method tags() { $r.smembers('tags') } method author($author_id) { from-json ($r.get("author:$author_id") or return) } method distribution($name) { from-json ($r.get("dist:$name") or return) } method tag($tag) { from-json ($r.get("tag:$tag") or return) } method author-distributions($author_id) { $r.smembers("author-dist:$author_id") } method tag-distributions($tag) { $r.smembers("tag-dist:$tag") } } ``` --- # Example, Define Types ``` class Distribution { has Str $.name; has Str $.description; has Str $.author_id; has Str $.build_id; has Str $.date_updated; has Int $.issues; has Int $.stars; has Str $.url; has Str $.meta_url; has Str $.travis_status; has Str $.travis_url; } class Author { has Str $.author_id; } class Tag { has Str $.tag; } ``` --- # Example, Define Types ``` class Distribution { has Str $.name; has Str $.description; has Str $.author_id; has Str $.build_id; has Str $.date_updated; has Int $.issues; has Int $.stars; has Str $.url; has Str $.meta_url; has Str $.travis_status; has Str $.travis_url; `has $.tag-list; # Won't show up in GraphQL` } class Author { has Str $.author_id; } class Tag { has Str $.tag; } ``` --- # Example, **new** to load object from Model ``` class Distribution { method new(Str :$name) { my $d = Model.distribution($name) or return; $d
= $d
:delete; # Rename tags nextwith(|$d); # Flatten to pairs (key => value) } } class Author { method new(Str $author_id) { my $d = Model.author($author_id) or return; nextwith(|$d); # Flatten to pairs (key => value) } } class Tag { method new(Str $tag) { my $d = Model.tag($tag) or return; nextwith(|$d); # Flatten to pairs (key => value) } } ``` --- # Example, Add accessors for linked types ``` class Distribution { method author() returns Author { Author.new($!author_id); } method tags() returns Array[Tag] { Array[Tag].new($!tag-list.map({ Tag.new($_) })); } } class Author { method distributions() returns Array[Distribution] { Array[Distribution].new( Model.author-distributions($!author_id) .map({ Distribution.new($_) })); } } class Tag { method distributions() returns Array[Distribution] { Array[Distribution].new( Model.tag-distributions($!tag) .map({ Distribution.new($_) })); } } ``` --- # Example, Finally, a top-level **Query** type ``` class Query { method author(Str :$author_id!) returns Author { Author.new($author_id) } method distribution(Str :$name!) returns Distribution { Distribution.new($name) } method tag(Str :$tag!) returns Tag { Tag.new($tag) } method authors() returns Array[Author] { Array[Author].new( Model.authors.map({ Author.new($_) }) ); } method distributions() returns Array[Distribution] { Array[Distribution].new( Model.distributions.map({ Distribution.new($_) }) ); } method tags() returns Array[Tag] { Array[Tag].new( Model.tags.map({ Tag.new($_) }) ); } } ``` --- # Example, Start the server ``` use GraphQL; use GraphQL::Server; use GraphQL::Modules; # Where I defined all the types my $schema = GraphQL::Schema.new(Author, Distribution, Tag, Query); GraphQL-Server($schema); ``` --- # Simple Query ```query { author(author_id:"Curt Tilmes
") { author_id } } ``` -- ```response { "data": { "author": { "author_id": "Curt Tilmes
" } } } ``` --- # Nested graph query ```query { author(author_id:"Curt Tilmes
") { author_id distributions { name } } } ``` -- ```response { "data": { "author": { "author_id": "Curt Tilmes
", "distributions": [ { "name": "GraphQL" }, ... ] } } } ``` --- # Add more fields ```query { author(author_id:"Curt Tilmes
") { author_id distributions { name `stars url` } } } ``` ```response { "data": { "author": { "author_id": "Curt Tilmes
", "distributions": [ { "name": "GraphQL", `"stars": 6,` `"url": "https://github.com/CurtTilmes/Perl6-GraphQL"` }, ... ] } } } ``` --- # Follow Tag graph ```query { author(author_id:"Curt Tilmes
") { author_id distributions { name tags { tag } } } } ``` -- ```response { "data": { "author": { "author_id": "Curt Tilmes
", "distributions": [ { "name": "GraphQL", "tags": [ { "tag": "GRAPHQL" } ] }, ... ] } } } ``` --- # Reuse fieldlists with fragments ```query { author(author_id:"Curt Tilmes
") { author_id distributions { `...name_tags` } } } fragment `name_tags` on Distribution { name tags { tag } } ``` --- # Include variables .left-col[ ```query query ($tag: String) { tag(tag: $tag) { distributions { name } } } ``` ``` { "tag":"HTTP" } ``` * Variables in JSON ] -- .right-col[ ```response { "data": { "tag": { "distributions": [ { "name": "PostCocoon::Url" }, { "name": "WWW" }, { "name": "URI::FetchFile" }, { "name": "Bailador" }, { "name": "LibCurl" } ] } } }```] --- # Descriptions * Add descriptions to schema with POD ``` #| A specific distribution of a Perl6 module class Distribution { #| Name of the distribution has Str $.name; #| Description of the distribution has Str $.description; #| author id of the Author of the distribution has Str $.author_id; #| build id has Str $.build_id; #| date updated has Str $.date_updated; #| Number of open github issues has Int $.issues; #| Number of github stars has Int $.stars; ... #| Author object of the distribution method author() returns Author ... #| Tag objects associated with the distribution method tags() returns Array[Tag] ... } ``` --- # GraphQL * Easy to add types and fields and maintain backward compatibility of interface. -- * 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 each time. -- * 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 let me know what you like/don't like. -- * 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. * The Perl 6 version is described at [http://graphql.tilmes.org](http://graphql.tilmes.org) -- ##.center[Thank You!] .center[*Slides produced with [remark](https://remarkjs.com)*]