Moje slajdy z prezentacji na GraphQL Wroclaw #3 https://www.meetup.com/GraphQL-Wroclaw/events/261828347/
Większość prezentacji dotyczących GraphQL opowiada o JavaSkryptowych narzędziach, natomiast mało kto mówi o tym jak korzystać z GraphQL w Javie. Prezentacja będzie o tym, jak sprawić aby nasz backendowy, Javowy serwer korzystał z GraphQL. Będzie o możliwych podejściach w implementacji, dobrych praktykach i 4-letnim doświadczeniu z GraphQL na produkcji.
4. GraphQL Java implementations
How to implement GraphQL in Java?
@MarcinStachniuk
/graphql-java/graphql-java
Code First
GraphQL in Java
5. GraphQL Simple API
GET /customers/2?fields=id,name,email,orders(id,status)
type Customer {
id: ID!
name: String!
email: String!
company: Company
orders: [Order]
}
type Order {
id: ID!
status: Status
}
enum Status {
NEW, CANCELED, DONE
}
{
"data": {
"customer": {
"id": "2",
"name": "name",
"orders": [
{
"id": "55",
"status": "NEW"
},
{
"id": "66",
"status": "DONE"
}
] } } }
{
customer(id: "2") {
id
name
orders {
id
status
}
}
}
RE
ST
@MarcinStachniukGraphQL in Java
6. Code First approach
private GraphQLFieldDefinition customerDefinition() {
return GraphQLFieldDefinition.newFieldDefinition()
.name("customer")
.argument(GraphQLArgument.newArgument()
.name("id")
.type(new GraphQLNonNull(GraphQLString)))
.type(new GraphQLNonNull(GraphQLObjectType.newObject()
.name("Customer")
.field(GraphQLFieldDefinition.newFieldDefinition()
.name("id")
.description("fields with ! are not null")
.type(new GraphQLNonNull(GraphQLID))
.build())
….
.build()))
.dataFetcher(customerFetcher)
.build();
}
@MarcinStachniukGraphQL in Java
7. How to implement DataFetcher for queries
GET /customers/2?fields=id,name,email,orders(id,status)
@Component
public class CustomerFetcher extends PropertyDataFetcher<Customer> {
@Autowired
private CustomerService customerService;
@Override
public Customer get(DataFetchingEnvironment environment) {
String id = environment.getArgument("id");
return customerService.getCustomerById(id);
}
}
RE
ST
{
customer(id: "2") {
id
name
orders {
id
status
}
}
}
@MarcinStachniukGraphQL in Java
8. How to implement DataFetcher for queries
GET /customers/2?fields=id,name,email,orders(id,status)
public class Customer {
private String id;
private String name;
private String email; // getters are not required
}
RE
ST
{
customer(id: "2") {
id
name
orders {
id
status
}
}
}
@MarcinStachniuk
public class OrderDataFetcher extends PropertyDataFetcher<List<Order>> {
@Override
public List<Order> get(DataFetchingEnvironment environment) {
Customer source = environment.getSource();
String customerId = source.getId();
return orderService.getOrdersByCustomerId(customerId);
}
}
GraphQL in Java
9. GraphQL mutations
input CreateCustomerInput {
name: String
email: String
clientMutationId: String!
}
type CreateCustomerPayload {
customer: Customer
clientMutationId: String!
}
type Mutation {
createCustomer(input: CreateCustomerInput):
CreateCustomerPayload!
}
{
"data": {
"createCustomer": {
"customer": {
"id": "40",
},
"clientMutationId":
"123"
}
}
}
POST /customers PUT /customers/123 DELETE /customers/123 PATCH /customers/123
mutation {
createCustomer(input: {
name: "MyName"
email: "me@me.com"
clientMutationId: "123"
}) {
customer {
id
}
clientMutationId
}
}
RE
ST
@MarcinStachniukGraphQL in Java
10. How to implement DataFetcher for mutations
POST /customers PUT /customers/123 DELETE /customers/123 PATCH /customers/123
@Component
public class CreateCustomersFetcher extends
PropertyDataFetcher<CreateCustomersPayload> {
@Override
public CreateCustomerPayload get(DataFetchingEnvironment env) {
Map<String, Object> input = env.getArgument("input");
String name = (String) input.get("name");
String email = (String) input.get("email");
String clientMutationId = (String) input.get("clientMutationId");
Customer customer = customerService.create(name, email);
return new CreateCustomerPayload(customer, clientMutationId);
}
RE
ST
mutation {
createCustomer(input: {
name: "MyName"
email: "me@me.com"
clientMutationId: "123"
}) {
customer {
id
}
clientMutationId
}
}
@MarcinStachniukGraphQL in Java
11. GraphQL Interface
GET /users?fields=id,name,superAdmin,permissions
type Query {
users: [User]
}
interface User {
id: ID!
name: String!
email: String!
}
type Admin implements User {
superAdmin: Boolean! // + id...
}
type Moderator implements User {
permissions: [String] // + id...
}
{
"data": {
"users": [
{
"id": "777",
"name": "Admin",
"superAdmin": true
},
{
"id": "888",
"name": "Moderator",
"permissions": [
"Delete Customer",
"Delete comment"
]}]}}
query getUsers {
users {
... on Admin {
id
name
superAdmin
}
... on Moderator {
id
name
permissions
}
}
}
RE
ST
@MarcinStachniukGraphQL in Java
12. In case Interfaces and Unions - Type Resolver is needed
public class UserTypeResolver implements TypeResolver {
@Override
public GraphQLObjectType getType(TypeResolutionEnvironment env) {
Object javaObject = env.getObject();
if (javaObject instanceof Admin) {
return env.getSchema().getObjectType("Admin");
} else if (javaObject instanceof Moderator) {
return env.getSchema().getObjectType("Moderator");
} else {
throw new RuntimeException("Unknown type " + javaObject.getClass().getName());
}
}
}
public interface User {...}
public class Admin implements User {...}
public class Moderator implements User {...}
@MarcinStachniukGraphQL in Java
14. Code First approach - How to build
Introspection
query
Introspection
response
Replace Relay
definitions
@MarcinStachniuk
Typescript relay
plugin
GraphQL in Java
15. GraphQL Java implementations
How to implement GraphQL in Java?
@MarcinStachniuk
/graphql-java/graphql-java
Schema First
GraphQL in Java
16. Schema First approach
type Customer {
# fields with ! are required
id: ID!
name: String!
email: String!
company: Company
orders: [Order]
}
*.graphqls
SchemaParser schemaParser = new SchemaParser();
File file = // ...
TypeDefinitionRegistry registry = schemaParser.parse(file);
SchemaGenerator schemaGenerator = new SchemaGenerator();
RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type("Query", builder ->
builder.dataFetcher("customer", customerFetcher))
// ...
.build();
return schemaGenerator.makeExecutableSchema(registry, runtimeWiring);
@MarcinStachniukGraphQL in Java
17. Schema First approach in comparison to Code First
private GraphQLFieldDefinition customerDefinition() {
return GraphQLFieldDefinition.newFieldDefinition()
.name("customer")
.argument(GraphQLArgument.newArgument()
.name("id")
.type(new GraphQLNonNull(GraphQLString)))
.type(new GraphQLNonNull(GraphQLObjectType.newObject()
.name("Customer")
.field(GraphQLFieldDefinition.newFieldDefinition()
.name("id")
.description("fields with ! are not null")
.type(new GraphQLNonNull(GraphQLID))
.build())
….
.build()))
.dataFetcher(customerFetcher)
.build();
}
Schema First approach
type Query {
customer(id: String!): Customer!
}
type Customer {
#fields with ! are not null
id: ID!
name: String!
email: String!
company: Company
orders: [Order]
}
@MarcinStachniukGraphQL in Java
20. SPQR approach
public class UserService {
@GraphQLQuery(name = "user")
public User getById(@GraphQLArgument(name = "id") Integer id) {
//...
}
}
@MarcinStachniuk
type Query {
user(id: Int): User
}
GraphQL in Java
By default argument names will be arg0, arg1,...
You can use @GraphQLArgument or
Compile code with -parameters option
21. SPQR approach
public class User {
//...
@GraphQLQuery(name = "name", description = "A person's name")
public String getName() {
return name;
}
}
@MarcinStachniuk
type User {
# A person's name
name: String
}
GraphQL in Java
22. SPQR approach - Smart conventions
@GraphQLType(name = "Item")
public class SpqrItem {
private String id;
private int amount;
// ...
@GraphQLId
@GraphQLNonNull
@GraphQLQuery(name = "id", description = "Item id")
public String getId() {
return id;
}
public int getAmount() {
return amount;
}
}
@MarcinStachniuk
// 'ID' type in schema, add 'implements Node'
// add ! (non null) in schema
// optional
// add ! (non null) because it’s int
GraphQL in Java
23. Classic Code First Approach
Pros:
● Stable library
● Some Javadoc and
documentation
● It was the only way at the
beginning to define a schema
@MarcinStachniuk
Cons:
● Hard to maintain
● Missing big picture
● Backend part needs to be
implemented first
● One DataFetcher for one
operation
● No possibility to mix with
Schema First
● No easy way to migrate to
Schema First or SPQR
GraphQL in Java
24. Schema First Approach
Pros:
● Easy to maintain and understand
● Stable library
● Some Javadoc and
documentation
● DSL defined in specification
● Reduce boilerplate to write
● A possibility to split schema into
more files
● Parallel development (frontend &
backend)
@MarcinStachniuk
Cons:
● Possible mismatch between
types in schema file and Java
code
● Binding between operations
defined in schema and
DataFetcher's is needed
● One DataFetcher for one
operation
GraphQL in Java
25. SPQR Approach
Pros:
● The author reply to Github issues
quickly
● Reduce boilerplate to write
● No mismatch between Schema
and Java code
● No binding between schema and
DataFetchers is needed
● Many “DataFetchers” in single
class
● Smart Relay conventions
@MarcinStachniuk
Cons:
● Production ready?
● Zero Javadoc or other
documentation
● Missing big picture
● Backend part needs to be
implemented first
● Ugly names for generics:
MutationPayload<User> =>
MutationPayload_User
● Hard to change smart
conventions
GraphQL in Java
26. What to choose?
Classic Code First Approach is the worst
@MarcinStachniuk
● Big project
● High risk project
● Old project
● Small project
● Low risk project
● New Project
?
GraphQL in Java
34. From Code First to Schema First migration
https://github.com/mstachniuk/graphql-schema-from-introspection-generator
@MarcinStachniukGraphQL in Java
35. From Code First to Schema First migration alternatives
● In graphql-java - see: IntrospectionResultToSchema
● In graphql-js:
const graphql = require("graphql");
const schema = require("path/to/schema.json");
const clientSchema = graphql.buildClientSchema(schema.data);
const schemaString = graphql.printSchema(clientSchema);
console.log(schemaString)
@MarcinStachniukGraphQL in Java
36. GraphQL downsides: N+1 problem
{
customers { 1 call
id
name
orders { n calls
id
status
}
}
}
java-dataloader
● Add async BatchLoader
● Add caching
@MarcinStachniuk
graphQL = GraphQL.newGraphQL(schema)
.queryExecutionStrategy(
new BatchedExecutionStrategy())
.build();
+ @Batched in DataFetcher#get()
GraphQL in Java
37. GraphQL downsides: N+1 problem
If you have N + 1 problem
use java-dataloader
@MarcinStachniukGraphQL in Java
38. Complex Queries
fragment Friends on Friend {
id
name
friends {
id
name
friends {
id
name
...
}
}
}
@MarcinStachniuk
graphQL = GraphQL.newGraphQL(schema)
.instrumentation(
new ChainedInstrumentation(asList(
new MaxQueryComplexityInstrumentation(20),
new MaxQueryDepthInstrumentation(2)
)))
.build();
GraphQL in Java
42. Recommended tools: Altair GraphQL Client
@MarcinStachniukGraphQL in Java
https://altair.sirmuel.design/
43. Recommended tools: Relay
@MarcinStachniukGraphQL in Java @MarcinStachniuk
user(...) {
photo(width: "120", height: "120")
}
user(...) {
name
}
user(...) {
email
}
user(...) {
name
email
photo(width: "120", height: "120")
}
GraphQL in Java
44. Recommended tools: GraphQL schema linter
https://github.com/cjoudrey/graphql-schema-linter
Validate GraphQL schema definitions against a set of rules
@MarcinStachniukGraphQL in Java
47. More libraries and projects related to graphql-java
https://github.com/graphql-java/awesome-graphql-java
@MarcinStachniukGraphQL in Java
48. Good practices
● Always generate GraphQL Schema and commit into your repository
● Follow conventions (e.g. Relay)
● Validate your schema
● Do not use classic code first approach to define schema
@MarcinStachniukGraphQL in Java