Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GraphQL & Java: The less obvious

GraphQL & Java: The less obvious

A presentation on the less obvious aspects of introducing GraphQL into a Java-based stack, and an approach to dynamic schema generation. Originally presented at Codemotion Amsterdam 2017.

Avatar for Bojan Tomić

Bojan Tomić

May 17, 2017
Tweet

More Decks by Bojan Tomić

Other Decks in Technology

Transcript

  1. What? • Server-­side  runtime  for  executing  queries • Type  system

    • Storage  /  language  independent  (think  REST) • Open  source  spec  (tnx Facebook!) (think  WSDL/SOAP) • Unrelated  to  graph  DBs  and  their  query  languages (Cypher,  Gremlin  etc) • Data  description  +  query  language
  2. Why? Describe  data Predictable  results No  under/over-­fetch type Query {

    hero (episode: Int): Character } { hero (episode: 4) { name homeWorld { name type } } } { "data" : { "hero" : { "name" : "Luke Skywalker" "homeWorld" : { "name" : "Tatooine" "type" : "desert" } } } type Character { name: String friends: [Character] homeWorld: Planet } type Planet { name: String type: String }
  3. graphql-­java  :  Defining  the  schema GraphQLObjectType person= newObject() .name("Person") .field(newFieldDefinition()

    .name("addresses") .type(new GraphQLList(address)) .dataFetcher(env -> dataBase.query(…))) .field(newFieldDefinition() .name("name") .type(Scalars.GraphQLString)) .build(); GraphQLObjectType address = newObject() .name("Address") .field(newFieldDefinition() .name("streetName") .type(Scalars.GraphQLString) .build()) .field(newFieldDefinition() .name("houseNumber") .type(Scalars.GraphQLInt) .build()) .build();
  4. graphql-­java  :  Defining  the  schema GraphQLObjectType queryType = newObject() .name("ROOT_QUERY")

    .field(newFieldDefinition() .name("peopleByName") .argument(newArgument() .name("name") .type(Scalars.GraphQLString)) .type(new GraphQLList(person)) .dataFetcher(env -> personService.findByName(...))) .build(); class Person { private String name; private List<Address> addresses; //getters & setters } class Address { ... } class PersonService { public List<Person> findByName(String name) { … } }
  5. The  solution  :  Dynamic  schema  generation o Allow   following

      best  practices o Be  adaptable  /  customizable o Eliminate   type  duplication   (DRY)
  6. GraphQL  SPQR public  class PersonService { public List<Person>  findByName(String  name)

     { return dataBase.queryByName(name);;   } ... } @GraphQLQuery(name  =  "peopleByName")
  7. GraphQL  SPQR  :  Setup public  class Person  { private String

     firstName;; private String  lastName;; @GraphQLQuery(name  =  "firstName") public String  getFirstName()  { return firstName;; } @GraphQLQuery(name  =  "lastName") public String  getLastName()  { return lastName;; } … } PersonService personService =  new PersonService();; GraphQLSchema schema  =  new GraphQLSchemaGenerator() .withOperationsFromSingleton(personService) .generate();; GraphQL graphQL =  new GraphQL(schema);; String  query  =  " { peopleByName(firstName:  "John")  { firstName lastName }}";; ExecutionResult result  =  graphQL.execute(query);;
  8. GraphQL SPQR  :  Queries public  class PersonService { @GraphQLQuery(name  =

     "twitterProfile") public TwitterProfile getTwitterProfile ( Person  person)  { return twitterApi.getProfile(person);; } @GraphQLQuery(name  =  "peopleByName") public List<Person>  findByName(  ...  )  { ... }         } { peopleByName (firstName :  "John")  { firstName lastName } } @GraphQLContext twitterProfile { handle numberOfTweets } { twitterProfile ( person:  { firstName :  "John" lastName :  "Doe" }) handle numberOfTweets } }
  9. The  problem:  Malicious queries o Arbitrarily  complex  query o Arbitrary

     size  of  the  result o Analyze  AST  complexity o Limit  depth o Limit  execution  time o Whitelist queries
  10. The  problem:  N  +  1 { peopleByName (firstName :  "John")

     { firstName lastName twitterProfile { handle numberOfTweets } } } public  class PersonService { @GraphQLQuery(name  =  "twitterProfile") public TwitterProfile getTwitterProfile ( Person person)  { return twitterApi.getProfile(person);; } @Batched List< List< > >
  11. The  problem:  Optimize  fetching { peopleByName (name:  "John")  { firstName

    lastName } } @GraphQLQuery(name  =  "peopleByName") public List<Person>  getPeopleByName(String  name)  { SELECT  *  FROM  Users  WHERE  name  =  ‘John’;; return …;; } L
  12. The  problem:  Optimize  fetching { peopleByName (name:  "John")  { firstName

    lastName } } @GraphQLQuery(name  =  "peopleByName") public List<Person>  getPeopleByName(String  name, @GraphQLEnvironment List<String>  subFields)  { SELECT  firstName,  lastName FROM  Users   WHERE  name  =  ‘John’;; return …;; }
  13. The  problem:  Authorization o Inject  security  context  into  the  

    resolver @RequestMapping(value="/graphql") public Object  graphql(@RequestBody Map<String,  Object>  request)  { String  query =  request.get("query").toString();; User  user =  SecurityContextHolder.getContext().getAuthentication();; } public String  getProfile(...,  @GraphQLRootContext User  user)  { if (current.getId()  ==  ...)  { return ...   } } ExecutionResult executionResult =  graphQL.execute(query,  user);;
  14. The  problem:  Authorization @PreAuthorize("hasRole('ROLE_ADMIN')")         @GraphQLMutation(name  =

     "updateUser") public String  updateUser(...)  { ... } o AOP  style