graphiql Archives - Piotr's TechBlog https://piotrminkowski.com/tag/graphiql/ Java, Spring, Kotlin, microservices, Kubernetes, containers Wed, 18 Jan 2023 11:15:47 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.1 https://i0.wp.com/piotrminkowski.com/wp-content/uploads/2020/08/cropped-me-2-tr-x-1.png?fit=32%2C32&ssl=1 graphiql Archives - Piotr's TechBlog https://piotrminkowski.com/tag/graphiql/ 32 32 181738725 An Advanced GraphQL with Spring Boot https://piotrminkowski.com/2023/01/18/an-advanced-graphql-with-spring-boot/ https://piotrminkowski.com/2023/01/18/an-advanced-graphql-with-spring-boot/#comments Wed, 18 Jan 2023 11:15:39 +0000 https://piotrminkowski.com/?p=13938 In this article, you will learn how to use Spring for GraphQL in your Spring Boot app. Spring for GraphQL is a relatively new project. The 1.0 version was released a few months ago. Before that release, we had to include third-party libraries to simplify GraphQL implementation in the Spring Boot app. I have already […]

The post An Advanced GraphQL with Spring Boot appeared first on Piotr's TechBlog.

]]>
In this article, you will learn how to use Spring for GraphQL in your Spring Boot app. Spring for GraphQL is a relatively new project. The 1.0 version was released a few months ago. Before that release, we had to include third-party libraries to simplify GraphQL implementation in the Spring Boot app. I have already described two alternative solutions in my previous articles. In the following article, you will learn about the GraphQL Java Kickstart project. In the other article, you can see how to create some more advanced GraphQL queries with the Netflix DGS library.

We will use a very similar schema and entity model as in those two articles about Spring Boot and GraphQL.

Source Code

If you would like to try it by yourself, you may always take a look at my source code. In order to do that you need to clone my GitHub repository. Then you should just follow my instructions.

Firstly, you should go to the sample-app-spring-graphql directory. Our sample Spring Boot exposes API over GraphQL and connects to the in-memory H2 database. It uses Spring Data JPA as a layer to interact with the database. There are three entities Employee, Department and Organization. Each of them is stored in a separate table. Here’s a relationship model.

Getting started with Spring for GraphQL

In addition to the standard Spring Boot modules we need to include the following two dependencies:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-graphql</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.graphql</groupId>
  <artifactId>spring-graphql-test</artifactId>
  <scope>test</scope>
</dependency>

The spring-graph-test provides additional capabilities for building unit tests. The starter comes with required libraries and auto-configuration. However, it does not enable the GraphiQL interface. In order to enable it, we should set the following property in the application.yml file:

spring:
  graphql:
    graphiql:
      enabled: true

By default, Spring for GraphQL tries to load schema files from the src/main/resources/graphql directory. It looks there for the files with the .graphqls or .gqls extensions. Let’s GraphQL schema for the Department entity. The Department type references the two other types: Organization and Employee (the list of employees). There are two queries for searching all departments and a department by id, and a single mutation for adding a new department.

type Query {
   departments: [Department]
   department(id: ID!): Department!
}

type Mutation {
   newDepartment(department: DepartmentInput!): Department
}

input DepartmentInput {
   name: String!
   organizationId: Int
}

type Department {
   id: ID!
   name: String!
   organization: Organization
   employees: [Employee]
}

The Organization type schema is pretty similar. From the more advanced stuff, we need to handle joins to the Employee and Department types.

extend type Query {
   organizations: [Organization]
   organization(id: ID!): Organization!
}

extend type Mutation {
   newOrganization(organization: OrganizationInput!): Organization
}

input OrganizationInput {
   name: String!
}

type Organization {
   id: ID!
   name: String!
   employees: [Employee]
   departments: [Department]
}

And the last schema – for the Employee type. Unlike the previous schemas, it defines the type responsible for handling filtering. The EmployeeFilter is able to filter by salary, position, or age. There is also the query method for handling filtering – employeesWithFilter.

extend type Query {
  employees: [Employee]
  employeesWithFilter(filter: EmployeeFilter): [Employee]
  employee(id: ID!): Employee!
}

extend type Mutation {
  newEmployee(employee: EmployeeInput!): Employee
}

input EmployeeInput {
  firstName: String!
  lastName: String!
  position: String!
  salary: Int
  age: Int
  organizationId: Int!
  departmentId: Int!
}

type Employee {
  id: ID!
  firstName: String!
  lastName: String!
  position: String!
  salary: Int
  age: Int
  department: Department
  organization: Organization
}

input EmployeeFilter {
  salary: FilterField
  age: FilterField
  position: FilterField
}

input FilterField {
  operator: String!
  value: String!
}

Create Entities

Do not hold that against me, but I’m using Lombok in entity implementation. Here’s the Employee entity corresponding to the Employee type defined in GraphQL schema.

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Employee {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @EqualsAndHashCode.Include
  private Integer id;
  private String firstName;
  private String lastName;
  private String position;
  private int salary;
  private int age;
  @ManyToOne(fetch = FetchType.LAZY)
  private Department department;
  @ManyToOne(fetch = FetchType.LAZY)
  private Organization organization;
}

Here we have the Department entity.

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Department {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @EqualsAndHashCode.Include
  private Integer id;
  private String name;
  @OneToMany(mappedBy = "department")
  private Set<Employee> employees;
  @ManyToOne(fetch = FetchType.LAZY)
  private Organization organization;
}

Finally, we can take a look at the Organization entity.

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Organization {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @EqualsAndHashCode.Include
  private Integer id;
  private String name;
  @OneToMany(mappedBy = "organization")
  private Set<Department> departments;
  @OneToMany(mappedBy = "organization")
  private Set<Employee> employees;
}

Using GraphQL for Spring with Spring Boot

Spring for GraphQL provides an annotation-based programming model using the well-known @Controller pattern. It is also possible to adapt the Querydsl library and use it together with Spring Data JPA. You can then use it in your Spring Data repositories annotated with @GraphQLRepository. In this article, I will use the standard JPA Criteria API for generating more advanced queries with filters and joins.

Let’s start with our first controller. In comparison to both previous articles about Netflix DGS and GraphQL Java Kickstart, we will keep queries and mutations in the same class. We need to annotate query methods with the @QueryMapping, and mutation methods with @MutationMapping. The last query method employeesWithFilter performs advanced filtering based on the dynamic list of fields passed in the input EmployeeFilter type. To pass an input parameter we should annotate the method argument with @Argument.

@Controller
public class EmployeeController {

   DepartmentRepository departmentRepository;
   EmployeeRepository employeeRepository;
   OrganizationRepository organizationRepository;

   EmployeeController(DepartmentRepository departmentRepository,
                      EmployeeRepository employeeRepository, 
                      OrganizationRepository organizationRepository) {
      this.departmentRepository = departmentRepository;
      this.employeeRepository = employeeRepository;
      this.organizationRepository = organizationRepository;
   }

   @QueryMapping
   public Iterable<Employee> employees() {
       return employeeRepository.findAll();
   }

   @QueryMapping
   public Employee employee(@Argument Integer id) {
       return employeeRepository.findById(id).orElseThrow();
   }

   @MutationMapping
   public Employee newEmployee(@Argument EmployeeInput employee) {
      Department department = departmentRepository
         .findById(employee.getDepartmentId()).get();
      Organization organization = organizationRepository
         .findById(employee.getOrganizationId()).get();
      return employeeRepository.save(new Employee(null, employee.getFirstName(), employee.getLastName(),
                employee.getPosition(), employee.getAge(), employee.getSalary(),
                department, organization));
   }

   @QueryMapping
   public Iterable<Employee> employeesWithFilter(
         @Argument EmployeeFilter filter) {
      Specification<Employee> spec = null;
      if (filter.getSalary() != null)
         spec = bySalary(filter.getSalary());
      if (filter.getAge() != null)
         spec = (spec == null ? byAge(filter.getAge()) : spec.and(byAge(filter.getAge())));
      if (filter.getPosition() != null)
         spec = (spec == null ? byPosition(filter.getPosition()) :
                    spec.and(byPosition(filter.getPosition())));
      if (spec != null)
         return employeeRepository.findAll(spec);
      else
         return employeeRepository.findAll();
   }

   private Specification<Employee> bySalary(FilterField filterField) {
      return (root, query, builder) -> filterField
         .generateCriteria(builder, root.get("salary"));
   }

   private Specification<Employee> byAge(FilterField filterField) {
      return (root, query, builder) -> filterField
         .generateCriteria(builder, root.get("age"));
   }

   private Specification<Employee> byPosition(FilterField filterField) {
      return (root, query, builder) -> filterField
         .generateCriteria(builder, root.get("position"));
   }
}

Here’s our JPA repository implementation. In order to use JPA Criteria API we need it needs to extend the JpaSpecificationExecutor interface. The same rule applies to both others DepartmentRepository and OrganizationRepository.

public interface EmployeeRepository extends 
   CrudRepository<Employee, Integer>, JpaSpecificationExecutor<Employee> {
}

Now, let’s switch to another controller. Here’s the implementation of DepartmentController. It shows the example of relationship fetching. We use DataFetchingEnvironment to detect if the input query contains a relationship field. In our case, it may be employees or organization. If any of those fields is defined we add the particular relation to the JOIN statement. The same approach applies to both department and deparments methods

@Controller
public class DepartmentController {

   DepartmentRepository departmentRepository;
   OrganizationRepository organizationRepository;

   DepartmentController(DepartmentRepository departmentRepository, OrganizationRepository organizationRepository) {
      this.departmentRepository = departmentRepository;
      this.organizationRepository = organizationRepository;
   }

   @MutationMapping
   public Department newDepartment(@Argument DepartmentInput department) {
      Organization organization = organizationRepository
         .findById(department.getOrganizationId()).get();
      return departmentRepository.save(new Department(null, department.getName(), null, organization));
   }

   @QueryMapping
   public Iterable<Department> departments(DataFetchingEnvironment environment) {
      DataFetchingFieldSelectionSet s = environment.getSelectionSet();
      List<Specification<Department>> specifications = new ArrayList<>();
      if (s.contains("employees") && !s.contains("organization"))
         return departmentRepository.findAll(fetchEmployees());
      else if (!s.contains("employees") && s.contains("organization"))
         return departmentRepository.findAll(fetchOrganization());
      else if (s.contains("employees") && s.contains("organization"))
         return departmentRepository.findAll(fetchEmployees().and(fetchOrganization()));
      else
         return departmentRepository.findAll();
   }

   @QueryMapping
   public Department department(@Argument Integer id, DataFetchingEnvironment environment) {
      Specification<Department> spec = byId(id);
      DataFetchingFieldSelectionSet selectionSet = environment
         .getSelectionSet();
      if (selectionSet.contains("employees"))
         spec = spec.and(fetchEmployees());
      if (selectionSet.contains("organization"))
         spec = spec.and(fetchOrganization());
      return departmentRepository.findOne(spec).orElseThrow(NoSuchElementException::new);
   }

    private Specification<Department> fetchOrganization() {
        return (root, query, builder) -> {
            Fetch<Department, Organization> f = root
               .fetch("organization", JoinType.LEFT);
            Join<Department, Organization> join = (Join<Department, Organization>) f;
            return join.getOn();
        };
    }

   private Specification<Department> fetchEmployees() {
      return (root, query, builder) -> {
         Fetch<Department, Employee> f = root
            .fetch("employees", JoinType.LEFT);
         Join<Department, Employee> join = (Join<Department, Employee>) f;
         return join.getOn();
      };
   }

   private Specification<Department> byId(Integer id) {
      return (root, query, builder) -> builder.equal(root.get("id"), id);
   }
}

Here’s the OrganizationController implementation.

@Controller
public class OrganizationController {

   OrganizationRepository repository;

   OrganizationController(OrganizationRepository repository) {
      this.repository = repository;
   }

   @MutationMapping
   public Organization newOrganization(@Argument OrganizationInput organization) {
      return repository.save(new Organization(null, organization.getName(), null, null));
   }

   @QueryMapping
   public Iterable<Organization> organizations() {
      return repository.findAll();
   }

   @QueryMapping
   public Organization organization(@Argument Integer id, DataFetchingEnvironment environment) {
      Specification<Organization> spec = byId(id);
      DataFetchingFieldSelectionSet selectionSet = environment
         .getSelectionSet();
      if (selectionSet.contains("employees"))
         spec = spec.and(fetchEmployees());
      if (selectionSet.contains("departments"))
         spec = spec.and(fetchDepartments());
      return repository.findOne(spec).orElseThrow();
   }

   private Specification<Organization> fetchDepartments() {
      return (root, query, builder) -> {
         Fetch<Organization, Department> f = root
            .fetch("departments", JoinType.LEFT);
         Join<Organization, Department> join = (Join<Organization, Department>) f;
         return join.getOn();
      };
   }

   private Specification<Organization> fetchEmployees() {
      return (root, query, builder) -> {
         Fetch<Organization, Employee> f = root
            .fetch("employees", JoinType.LEFT);
         Join<Organization, Employee> join = (Join<Organization, Employee>) f;
         return join.getOn();
      };
   }

   private Specification<Organization> byId(Integer id) {
      return (root, query, builder) -> builder.equal(root.get("id"), id);
   }
}

Create Unit Tests

Once we created the whole logic it’s time to test it. In the next section, I’ll show you how to use GraphiQL IDE for that. Here, we are going to focus on unit tests. The simplest way to start with Spring for GraphQL tests is through the GraphQLTester bean. We can use in mocked web environment. You can also build tests for HTTP layer with another bean – HttpGraphQlTester. However, it requires us to provide an instance of WebTestClient.

Here are the test for the Employee @Controller. Each time we are building an inline query using the GraphQL notation. We need to annotate the whole test class with @AutoConfigureGraphQlTester. Then we can use the DSL API provided by the GraphQLTester to get and verify data from backend. Besides two simple tests we also verifies if EmployeeFilter works fine in the findWithFilter method.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureGraphQlTester
public class EmployeeControllerTests {

   @Autowired
   private GraphQlTester tester;

   @Test
   void addEmployee() {
      String query = "mutation { newEmployee(employee: { firstName: \"John\" lastName: \"Wick\" position: \"developer\" salary: 10000 age: 20 departmentId: 1 organizationId: 1}) { id } }";
      Employee employee = tester.document(query)
              .execute()
              .path("data.newEmployee")
              .entity(Employee.class)
              .get();
      Assertions.assertNotNull(employee);
      Assertions.assertNotNull(employee.getId());
   }

   @Test
   void findAll() {
      String query = "{ employees { id firstName lastName salary } }";
      List<Employee> employees = tester.document(query)
             .execute()
             .path("data.employees[*]")
             .entityList(Employee.class)
             .get();
      Assertions.assertTrue(employees.size() > 0);
      Assertions.assertNotNull(employees.get(0).getId());
      Assertions.assertNotNull(employees.get(0).getFirstName());
   }

   @Test
   void findById() {
      String query = "{ employee(id: 1) { id firstName lastName salary } }";
      Employee employee = tester.document(query)
             .execute()
             .path("data.employee")
             .entity(Employee.class)
             .get();
      Assertions.assertNotNull(employee);
      Assertions.assertNotNull(employee.getId());
      Assertions.assertNotNull(employee.getFirstName());
   }

   @Test
   void findWithFilter() {
      String query = "{ employeesWithFilter(filter: { salary: { operator: \"gt\" value: \"12000\" } }) { id firstName lastName salary } }";
      List<Employee> employees = tester.document(query)
             .execute()
             .path("data.employeesWithFilter[*]")
             .entityList(Employee.class)
             .get();
      Assertions.assertTrue(employees.size() > 0);
      Assertions.assertNotNull(employees.get(0).getId());
      Assertions.assertNotNull(employees.get(0).getFirstName());
   }
}

Test tests for the Deparment type are very similar. Additionally, we are testing join statements in the findById test method by declaring the organization field in the query.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureGraphQlTester
public class DepartmentControllerTests {

   @Autowired
   private GraphQlTester tester;

   @Test
   void addDepartment() {
      String query = "mutation { newDepartment(department: { name: \"Test10\" organizationId: 1}) { id } }";
      Department department = tester.document(query)
             .execute()
             .path("data.newDepartment")
             .entity(Department.class)
             .get();
      Assertions.assertNotNull(department);
      Assertions.assertNotNull(department.getId());
   }

   @Test
   void findAll() {
      String query = "{ departments { id name } }";
      List<Department> departments = tester.document(query)
             .execute()
             .path("data.departments[*]")
             .entityList(Department.class)
             .get();
      Assertions.assertTrue(departments.size() > 0);
      Assertions.assertNotNull(departments.get(0).getId());
      Assertions.assertNotNull(departments.get(0).getName());
   }

   @Test
   void findById() {
      String query = "{ department(id: 1) { id name organization { id } } }";
      Department department = tester.document(query)
             .execute()
             .path("data.department")
             .entity(Department.class)
             .get();
      Assertions.assertNotNull(department);
      Assertions.assertNotNull(department.getId());
      Assertions.assertNotNull(department.getOrganization());
      Assertions.assertNotNull(department.getOrganization().getId());
   }
    
}

Each time you are cloning my repository you can be sure that the examples work fine thanks to automated tests. You can always verify the status of the repository build in my CircleCI pipeline.

Testing with GraphiQL

We can easily start the application with the following Maven command:

$ mvn clean spring-boot:run

Once you do it, you can access the GraphiQL tool under the address http://localhost:8080/graphiql. The app inserts some demo data to the H2 database on startup. GraphiQL provides content assist for building GraphQL queries. Here’s the sample query tested there.

Final Thoughts

Spring for GraphQL is very interesting project and I will be following its development closely. Besides @Controller support, I tried to use the querydsl integration with Spring Data JPA repositories. However, I’ve got some problems with it, and therefore I avoided to place that topic in the article. Currently, Spring for GraphQL is the third solid Java framework with high-level GraphQL support for Spring Boot. My choice is still Netflix DGS, but Spring for GraphQL is rather during the active development. So we can probably except some new and useful features soon.

The post An Advanced GraphQL with Spring Boot appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2023/01/18/an-advanced-graphql-with-spring-boot/feed/ 3 13938
An Advanced GraphQL with Spring Boot and Netflix DGS https://piotrminkowski.com/2021/04/08/an-advanced-graphql-with-spring-boot-and-netflix-dgs/ https://piotrminkowski.com/2021/04/08/an-advanced-graphql-with-spring-boot-and-netflix-dgs/#comments Thu, 08 Apr 2021 08:05:27 +0000 https://piotrminkowski.com/?p=9639 In this article, you will learn how to use the Netflix DGS library to simplify GraphQL development with Spring Boot. We will discuss more advanced topics related to GraphQL and databases, like filtering or relationship fetching. I published a similar article some months ago: An Advanced Guide to GraphQL with Spring Boot. However, it is […]

The post An Advanced GraphQL with Spring Boot and Netflix DGS appeared first on Piotr's TechBlog.

]]>
In this article, you will learn how to use the Netflix DGS library to simplify GraphQL development with Spring Boot. We will discuss more advanced topics related to GraphQL and databases, like filtering or relationship fetching. I published a similar article some months ago: An Advanced Guide to GraphQL with Spring Boot. However, it is based on a different library called GraphQL Java Kickstart (https://github.com/graphql-java-kickstart/graphql-spring-boot). Since Netflix DGS has been released some months ago, you might want to take look at it. So, that’s what we will do now.

Netflix DGS is an annotation-based GraphQL Java library built on top of Spring Boot. Consequently, it is dedicated to Spring Boot applications. Besides the annotation-based programming model, it provides several useful features. Netflix DGS allows generating source code from GraphQL schemas. It simplifies writing unit tests and also supports websockets, file uploads, or GraphQL federation. In order to show you the differences between this library and the previously described Kickstart library, I’ll use the same Spring Boot application as before. Let me just briefly describe our scenario.

Source Code

If you would like to try it by yourself, you may always take a look at my source code. In order to do that you need to clone my GitHub repository. Then you should just follow my instructions.

First, you should go to the sample-app-netflix-dgs directory. The example with GraphQL Java Kickstart is available inside the sample-app-kickstart directory.

As I mentioned before, we use the same schema and entity model as before. I created an application that exposes API using GraphQL and connects to H2 in-memory database. We will discuss Spring Boot GraphQL JPA support. For integration with the H2 database, I’m using Spring Data JPA and Hibernate. I have implemented three entities EmployeeDepartment and Organization – each of them stored in the separated table. A relationship model between them is visualized in the picture below.

spring-boot-graphql-netflix-domain

1. Dependencies for Spring Boot and Netflix GraphQL

Let’s start with dependencies. We need to include Spring Web, Spring Data JPA, and the com.database:h2 artifact for running an in-memory database with our application. Of course, we also have to include Netflix DGS Spring Boot Starter. Here’s a list of required dependencies in Maven pom.xml.

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
   <scope>runtime</scope>
</dependency>
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
</dependency>
<dependency>
   <groupId>com.netflix.graphql.dgs</groupId>
   <artifactId>graphql-dgs-spring-boot-starter</artifactId>
   <version>${netflix-dgs.spring.version}</version>
</dependency>

2. GraphQL schemas

Before we start implementation, we need to create GraphQL schemas with objects, queries, and mutations. A schema may be defined in multiple graphqls files, but all of them have to be placed inside the /src/main/resources/schemas directory. Thanks to that, the Netflix DGS library detects and loads them automatically.

GraphQL schema for each entity is located in the separated file. Let’s take a look at the department.graphqls file. There is the QueryResolver with two find methods and the MutationResolver with a single method for adding new departments. We also have an input object for mutation and a standard type definition for queries.

type QueryResolver {
   departments: [Department]
   department(id: ID!): Department!
}

type MutationResolver {
   newDepartment(department: DepartmentInput!): Department
}

input DepartmentInput {
   name: String!
   organizationId: Int
}

type Department {
   id: ID!
   name: String!
   organization: Organization
   employees: [Employee]
}

Then we may take a look at the organization.graphqls file. It is a little bit more complicated than the previous schema. As you see I’m using the keyword extend on QueryResolver and MutationResolver. That’s because we have several files with GraphQL schemas.

extend type QueryResolver {
  organizations: [Organization]
  organization(id: ID!): Organization!
}

extend type MutationResolver {
  newOrganization(organization: OrganizationInput!): Organization
}

input OrganizationInput {
  name: String!
}

type Organization {
  id: ID!
  name: String!
  employees: [Employee]
  departments: [Department]
}

Finally, the schema for the Employee entity. In contrast to the previous schemas, it has objects responsible for filtering like EmployeeFilter. We also need to define the schema object with mutation and query.

extend type QueryResolver {
  employees: [Employee]
  employeesWithFilter(filter: EmployeeFilter): [Employee]
  employee(id: ID!): Employee!
}

extend type MutationResolver {
  newEmployee(employee: EmployeeInput!): Employee
}

input EmployeeInput {
  firstName: String!
  lastName: String!
  position: String!
  salary: Int
  age: Int
  organizationId: Int!
  departmentId: Int!
}

type Employee {
  id: ID!
  firstName: String!
  lastName: String!
  position: String!
  salary: Int
  age: Int
  department: Department
  organization: Organization
}

input EmployeeFilter {
  salary: FilterField
  age: FilterField
  position: FilterField
}

input FilterField {
  operator: String!
  value: String!
}

schema {
  query: QueryResolver
  mutation: MutationResolver
}

3. Domain Model for GraphQL and Hibernate

We could have generated Java source code using previously defined GraphQL schemas. However, I prefer to use Lombok annotations, so I will do it manually. Here’s the Employee entity corresponding to the Employee object defined in GraphQL schema.

@Entity
@Data
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Employee {
   @Id
   @GeneratedValue
   @EqualsAndHashCode.Include
   private Integer id;
   private String firstName;
   private String lastName;
   private String position;
   private int salary;
   private int age;
   @ManyToOne(fetch = FetchType.LAZY)
   private Department department;
   @ManyToOne(fetch = FetchType.LAZY)
   private Organization organization;
}

Also, let’s take a look at the Department entity.

@Entity
@Data
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Department {
   @Id
   @GeneratedValue
   @EqualsAndHashCode.Include
   private Integer id;
   private String name;
   @OneToMany(mappedBy = "department")
   private Set<Employee> employees;
   @ManyToOne(fetch = FetchType.LAZY)
   private Organization organization;
}

The input objects are much simpler. Just to compare, here’s the DepartmentInput class.

@Data
@NoArgsConstructor
public class DepartmentInput {
   private String name;
   private Integer organizationId;
}

4. Using Netflix DGS with Spring Boot

Netflix DGS provides annotation-based support for Spring Boot. Let’s analyze the most interesting features using the example implementation of a query resolver. The EmployeeFetcher is responsible for defining queries related to the Employee object. We should annotate such a class with @DgsComponent (1). We may create our custom context definition to pass data between different methods or even different query resolvers (2). Then, we have to annotate every query method with @DgsData (3). The fields parentType and fields should match the names declared in GraphQL schemas. We defined three queries in the employee.graphqls file, so we have three methods inside EmployeeFetcher. After fetching all employees, we may save them in our custom context object (4), and then reuse them in other methods or resolvers (5).

The last query method findWithFilter performs advanced filtering based on the dynamic list of fields passed in the input (6). To pass an input parameter we should annotate the method argument with @InputArgument.

@DgsComponent // (1)
public class EmployeeFetcher {

   private EmployeeRepository repository;
   private EmployeeContextBuilder contextBuilder; // (2)

   public EmployeeFetcher(EmployeeRepository repository, 
         EmployeeContextBuilder contextBuilder) {
      this.repository = repository;
      this.contextBuilder = contextBuilder;
    }

   @DgsData(parentType = "QueryResolver", field = "employees") // (3)
   public List<Employee> findAll() {
      List<Employee> employees = (List<Employee>) repository.findAll();
      contextBuilder.withEmployees(employees).build(); // (4)
      return employees;
   }

   @DgsData(parentType = "QueryResolver", field = "employee") 
   public Employee findById(@InputArgument("id") Integer id, 
               DataFetchingEnvironment dfe) {
      EmployeeContext employeeContext = DgsContext.getCustomContext(dfe); // (5)
      List<Employee> employees = employeeContext.getEmployees();
      Optional<Employee> employeeOpt = employees.stream()
         .filter(employee -> employee.getId().equals(id)).findFirst();
      return employeeOpt.orElseGet(() -> 
         repository.findById(id)
            .orElseThrow(DgsEntityNotFoundException::new));
   }

   @DgsData(parentType = "QueryResolver", field = "employeesWithFilter")
   public Iterable<Employee> findWithFilter(@InputArgument("filter") EmployeeFilter filter) { // (6)
      Specification<Employee> spec = null;
      if (filter.getSalary() != null)
         spec = bySalary(filter.getSalary());
      if (filter.getAge() != null)
         spec = (spec == null ? byAge(filter.getAge()) : spec.and(byAge(filter.getAge())));
      if (filter.getPosition() != null)
         spec = (spec == null ? byPosition(filter.getPosition()) :
                spec.and(byPosition(filter.getPosition())));
     if (spec != null)  
        return repository.findAll(spec);
     else
        return repository.findAll();
   }

   private Specification<Employee> bySalary(FilterField filterField) {
      return (root, query, builder) -> 
         filterField.generateCriteria(builder, root.get("salary"));
   }

   private Specification<Employee> byAge(FilterField filterField) {
      return (root, query, builder) -> 
         filterField.generateCriteria(builder, root.get("age"));
   }

   private Specification<Employee> byPosition(FilterField filterField) {
      return (root, query, builder) -> 
         filterField.generateCriteria(builder, root.get("position"));
   }
}

Then, we may switch to the DepartmentFetcher class. It shows the example of relationship fetching. We use DataFetchingEnvironment to detect if the input query contains a relationship field (1). In our case, it may be employees or organization. If any of those fields is defined we add the relation to the JOIN statement (2). We implement the same approach for both findById (3) and findAll methods. However, the findById method also uses data stored in the custom context represented by the EmployeeContext bean (4). If the method findAll in EmployeeFetcher has already been invoked, we can fetch employees assigned to the particular department from the context instead of including the relation to the JOIN statement (5).

@DgsComponent
public class DepartmentFetcher {

   private DepartmentRepository repository;

   DepartmentFetcher(DepartmentRepository repository) {
      this.repository = repository;
   }

   @DgsData(parentType = "QueryResolver", field = "departments")
   public Iterable<Department> findAll(DataFetchingEnvironment environment) {
      DataFetchingFieldSelectionSet s = environment.getSelectionSet(); // (1)
      List<Specification<Department>> specifications = new ArrayList<>();
      if (s.contains("employees") && !s.contains("organization")) // (2)
         return repository.findAll(fetchEmployees());
      else if (!s.contains("employees") && s.contains("organization"))
         return repository.findAll(fetchOrganization());
      else if (s.contains("employees") && s.contains("organization"))
         return repository.findAll(fetchEmployees().and(fetchOrganization()));
      else
         return repository.findAll();
   }

   @DgsData(parentType = "QueryResolver", field = "department")
   public Department findById(@InputArgument("id") Integer id, 
               DataFetchingEnvironment environment) { // (3)
      Specification<Department> spec = byId(id);
      DataFetchingFieldSelectionSet selectionSet = environment.getSelectionSet();
      EmployeeContext employeeContext = DgsContext.getCustomContext(environment); // (4)
      Set<Employee> employees = null;
      if (selectionSet.contains("employees")) {
         if (employeeContext.getEmployees().size() == 0) // (5)
            spec = spec.and(fetchEmployees());
         else
            employees = employeeContext.getEmployees().stream()
               .filter(emp -> emp.getDepartment().getId().equals(id))
               .collect(Collectors.toSet());
      }
      if (selectionSet.contains("organization"))
         spec = spec.and(fetchOrganization());
      Department department = repository
         .findOne(spec).orElseThrow(DgsEntityNotFoundException::new);
      if (employees != null)
         department.setEmployees(employees);
      return department;
   }

   private Specification<Department> fetchOrganization() {
      return (root, query, builder) -> {
         Fetch<Department, Organization> f = root.fetch("organization", JoinType.LEFT);
         Join<Department, Organization> join = (Join<Department, Organization>) f;
         return join.getOn();
      };
   }

   private Specification<Department> fetchEmployees() {
      return (root, query, builder) -> {
         Fetch<Department, Employee> f = root.fetch("employees", JoinType.LEFT);
         Join<Department, Employee> join = (Join<Department, Employee>) f;
         return join.getOn();
      };
   }

   private Specification<Department> byId(Integer id) {
      return (root, query, builder) -> builder.equal(root.get("id"), id);
   }
}

In comparison to the data fetchers implementation of mutation handlers is rather simple. We just need to define a single method for adding new entities. Here’s the implementation of DepartmentMutation.

@DgsComponent
public class DepartmentMutation {

   private DepartmentRepository departmentRepository;
   private OrganizationRepository organizationRepository;

   DepartmentMutation(DepartmentRepository departmentRepository, 
               OrganizationRepository organizationRepository) {
      this.departmentRepository = departmentRepository;
      this.organizationRepository = organizationRepository;
   }

   @DgsData(parentType = "MutationResolver", field = "newDepartment")
   public Department newDepartment(DepartmentInput input) {
      Organization organization = organizationRepository
         .findById(departmentInput.getOrganizationId())
         .orElseThrow();
      return departmentRepository
         .save(new Department(null, input.getName(), null, organization));
   }

}

5. Running Spring Boot application and testing Netflix GraphQL support

The last step in our exercise is to run and test the Spring Boot application. It inserts some test data to the H2 database on startup. So, let’s just use the GraphiQL tool to run test queries. It is automatically included in the application by the Netflix DGS library. We may display it by invoking the URL http://localhost:8080/graphiql.

In the first step, we run the GraphQL query responsible for fetching all employees with departments. The method that handles the query also builds a custom context and stores there all existing employees.

spring-boot-graphql-netflix-query

Then, we may run a query responsible for finding a single department by its id. We will fetch both relations one-to-many with Employee and many-to-one with Organization.

While the Organization entity is fetched using the JOIN statement, Employee is taken from the context. Here’s the SQL query generated for our current scenario.

spring-boot-graphql-netflix-query-next

Finally, we can test our filtering feature. Let’s filter employees using salary and age criteria.

Let’s take a look at the SQL query for the recently called method.

Final Thoughts

Netflix DGS seems to be an interesting alternative to other libraries that provide support for GraphQL with Spring Boot. It has been open-sourced some weeks ago, but it is rather a stable solution. I guess that before releasing it publicly, the Netflix team has tested it in the battle. I like its annotation-based programming style and several other features. This article will help you in starting with Netflix DGS.

The post An Advanced GraphQL with Spring Boot and Netflix DGS appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2021/04/08/an-advanced-graphql-with-spring-boot-and-netflix-dgs/feed/ 20 9639
An Advanced Guide to GraphQL with Spring Boot https://piotrminkowski.com/2020/07/31/an-advanced-guide-to-graphql-with-spring-boot/ https://piotrminkowski.com/2020/07/31/an-advanced-guide-to-graphql-with-spring-boot/#comments Fri, 31 Jul 2020 09:31:27 +0000 http://piotrminkowski.com/?p=8220 In this guide I’m going to discuss some more advanced topics related to GraphQL and databases, like filtering or relationship fetching. Of course, before proceeding to the more advanced issues I will take a moment to describe the basics – something you can be found in many other articles. If you already had the opportunity […]

The post An Advanced Guide to GraphQL with Spring Boot appeared first on Piotr's TechBlog.

]]>
In this guide I’m going to discuss some more advanced topics related to GraphQL and databases, like filtering or relationship fetching. Of course, before proceeding to the more advanced issues I will take a moment to describe the basics – something you can be found in many other articles. If you already had the opportunity to familiarize yourself with the concept over GraphQL you may have some questions. Probably one of them is: “Ok. It’s nice. But what if I would like to use GraphQL in the real application that connects to the database and provides API for more advanced queries?”.
If that is your main question, my current article is definitely for you. If you are thinking about using GraphQL in your microservices architecture you may also refer to my previous article GraphQL – The Future of Microservices?.

Example

As you know it is best to learn from examples, so I have created a sample Spring Boot application that exposes API using GraphQL and connects to H2 in-memory database. We will discuss Spring Boot GraphQL JPA support. For integration with the H2 database I’m using Spring Data JPA and Hibernate. I have implemented three entities Employee, Department and Organization – each of them stored in the separated table. A relationship model between them has been visualized in the picture below.

graphql-spring-boot-relations.png

A source code with sample application is available on GitHub in repository: https://github.com/piomin/sample-spring-boot-graphql.git

1. Dependencies

Let’s start from dependencies. Here’s a list of required dependencies for our application. We need to include projects Spring Web, Spring Data JPA and com.database:h2 artifact for embedding in-memory database to our application. I’m also using Spring Boot library offering support for GraphQL. In fact, you may find some other Spring Boot GraphQL JPA libraries, but the one under group com.graphql-java-kickstart (https://www.graphql-java-kickstart.com/spring-boot/) seems to be actively developed and maintained.


<properties>
   <graphql.spring.version>7.1.0</graphql.spring.version>
</properties>
<dependencies>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
   </dependency>
   <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
   </dependency>
   <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
   </dependency>
   <dependency>
      <groupId>com.graphql-java-kickstart</groupId>
      <artifactId>graphql-spring-boot-starter</artifactId>
      <version>${graphql.spring.version}</version>
   </dependency>
   <dependency>
      <groupId>com.graphql-java-kickstart</groupId>
      <artifactId>graphiql-spring-boot-starter</artifactId>
      <version>${graphql.spring.version}</version>
   </dependency>
   <dependency>
      <groupId>com.graphql-java-kickstart</groupId>
      <artifactId>graphql-spring-boot-starter-test</artifactId>
      <version>${graphql.spring.version}</version>
      <scope>test</scope>
   </dependency>
   <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
   </dependency>
</dependencies>

2. Schemas

We are starting implementation from defining GraphQL schemas with objects, queries and mutations definitions. The files are located inside /src/main/resources/graphql directory and after adding graphql-spring-boot-starter they are automatically detected by the application basing on their suffix *.graphqls.
GraphQL schema for each entity is located in the separated file. Let’s take a look on department.graphqls. It’s a very trivial definition.

type QueryResolver {
    departments: [Department]
    department(id: ID!): Department!
}

type MutationResolver {
    newDepartment(department: DepartmentInput!): Department
}

input DepartmentInput {
    name: String!
    organizationId: Int
}

type Department {
    id: ID!
    name: String!
    organization: Organization
    employees: [Employee]
}

Here’s the schema inside file organization.graphqls. As you see I’m using keyword extend on QueryResolver and MutationResolver.

extend type QueryResolver {
    organizations: [Organization]
    organization(id: ID!): Organization!
}

extend type MutationResolver {
    newOrganization(organization: OrganizationInput!): Organization
}

input OrganizationInput {
    name: String!
}

type Organization {
    id: ID!
    name: String!
    employees: [Employee]
    departments: [Department]
}

Schema for Employee is a little bit more complicated than two previously demonstrated schemas. I have defined an input object for filtering. It will be discussed in the next section in detail.

extend type QueryResolver {
  employees: [Employee]
  employeesWithFilter(filter: EmployeeFilter): [Employee]
  employee(id: ID!): Employee!
}

extend type MutationResolver {
  newEmployee(employee: EmployeeInput!): Employee
}

input EmployeeInput {
  firstName: String!
  lastName: String!
  position: String!
  salary: Int
  age: Int
  organizationId: Int!
  departmentId: Int!
}

type Employee {
  id: ID!
  firstName: String!
  lastName: String!
  position: String!
  salary: Int
  age: Int
  department: Department
  organization: Organization
}

input EmployeeFilter {
  salary: FilterField
  age: FilterField
  position: FilterField
}

input FilterField {
  operator: String!
  value: String!
}

schema {
  query: QueryResolver
  mutation: MutationResolver
}

3. Domain model

Let’s take a look at the corresponding domain model. Here’s Employee entity. Each Employee is assigned to a single Department and Organization.

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Employee {
   @Id
   @GeneratedValue
   @EqualsAndHashCode.Include
   private Integer id;
   private String firstName;
   private String lastName;
   private String position;
   private int salary;
   private int age;
   @ManyToOne(fetch = FetchType.LAZY)
   private Department department;
   @ManyToOne(fetch = FetchType.LAZY)
   private Organization organization;
}

Here’s Department entity. It contains a list of employees and a reference to a single organization.


@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Department {
   @Id
   @GeneratedValue
   @EqualsAndHashCode.Include
   private Integer id;
   private String name;
   @OneToMany(mappedBy = "department")
   private Set<Employee> employees;
   @ManyToOne(fetch = FetchType.LAZY)
   private Organization organization;
}

And finally Organization entity.

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Organization {
   @Id
   @GeneratedValue
@EqualsAndHashCode.Include
   private Integer id;
   private String name;
   @OneToMany(mappedBy = "organization")
   private Set<Department> departments;
   @OneToMany(mappedBy = "organization")
   private Set<Employee> employees;
}

Entity classes are returned as a result by queries. In mutations we are using input objects that have slightly different implementation. They do not contain reference to a relationship, but only an id of related objects.

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DepartmentInput {
   private String name;
   private Integer organizationId;
}

4. Fetch relations

As you probably figured out, all the JPA relations are configured in lazy mode. To fetch them we should explicitly set such a request in our GraphQL query. For example, we may query all departments and fetch organization to each of the department returned on the list.


{
  departments {
    id
    name
    organization {
      id
      name
    }
  }
}

Now, the question is how to handle it on the server side. The first thing we need to do is to detect the existence of such a relationship field in our GraphQL query. Why? Because we need to avoid possible N+1 problem, which happens when the data access framework executes N additional SQL statements to fetch the same data that could have been retrieved when executing the primary SQL query. So, we need to prepare different JPA queries depending on the parameters set in the GraphQL query. We may do it in several ways, but the most convenient way is by using DataFetchingEnvironment parameter inside QueryResolver implementation.
Let’s take a look on the implementation of QueryResolver for Department. If we annotate class that implements GraphQLQueryResolver with @Component it is automatically detected by Spring Boot (thanks to graphql-spring-boot-starter). Then we are adding DataFetchingEnvironment as a parameter to each query. After that we should invoke method getSelectionSet() on DataFetchingEnvironment object and check if it contains word organization (for fetching Organization) or employees (for fetching list of employees). Depending on requested relations we build different queries. In the following fragment of code we have two methods implemented for DepartmentQueryResolver: findAll and findById.

@Component
public class DepartmentQueryResolver implements GraphQLQueryResolver {

   private DepartmentRepository repository;

   DepartmentQueryResolver(DepartmentRepository repository) {
      this.repository = repository;
   }

   public Iterable<Department> departments(DataFetchingEnvironment environment) {
      DataFetchingFieldSelectionSet s = environment.getSelectionSet();
      List<Specification<Department>> specifications = new ArrayList<>();
      if (s.contains("employees") && !s.contains("organization"))
         return repository.findAll(fetchEmployees());
      else if (!s.contains("employees") && s.contains("organization"))
         return repository.findAll(fetchOrganization());
      else if (s.contains("employees") && s.contains("organization"))
         return repository.findAll(fetchEmployees().and(fetchOrganization()));
      else
         return repository.findAll();
   }

   public Department department(Integer id, DataFetchingEnvironment environment) {
      Specification<Department> spec = byId(id);
      DataFetchingFieldSelectionSet selectionSet = environment.getSelectionSet();
      if (selectionSet.contains("employees"))
         spec = spec.and(fetchEmployees());
      if (selectionSet.contains("organization"))
         spec = spec.and(fetchOrganization());
      return repository.findOne(spec).orElseThrow(NoSuchElementException::new);
   }
   
   // REST OF IMPLEMENTATION ...
}

The most convenient way to build dynamic queries is by using JPA Criteria API. To be able to use it with Spring Data JPA we first need to extend our repository interface with JpaSpecificationExecutor interface. After that you may use the additional interface methods that let you execute specifications in a variety of ways. You may choose between findAll and findOne methods.

public interface DepartmentRepository extends CrudRepository<Department, Integer>,
      JpaSpecificationExecutor<Department> {

}

Finally, we may just prepare methods that build Specification the object. This object contains a predicate. In that case we are using three predicates for fetching organization, employees and filtering by id.

private Specification<Department> fetchOrganization() {
   return (Specification<Department>) (root, query, builder) -> {
      Fetch<Department, Organization> f = root.fetch("organization", JoinType.LEFT);
      Join<Department, Organization> join = (Join<Department, Organization>) f;
      return join.getOn();
   };
}

private Specification<Department> fetchEmployees() {
   return (Specification<Department>) (root, query, builder) -> {
      Fetch<Department, Employee> f = root.fetch("employees", JoinType.LEFT);
      Join<Department, Employee> join = (Join<Department, Employee>) f;
      return join.getOn();
   };
}

private Specification<Department> byId(Integer id) {
   return (Specification<Department>) (root, query, builder) -> builder.equal(root.get("id"), id);
}

5. Filtering

For a start, let’s refer to the section 2 – Schemas. Inside employee.graphqls I defined two additional inputs FilterField and EmployeeFilter, and also a single method employeesWithFilter that takes EmployeeFilter as an argument. The FieldFilter class is my custom implementation of a filter for GraphQL queries. It is very trivial. It provides an implementation of two filter types: for number or for string. It generates JPA Criteria Predicate. Of course, instead creating such filter implementation by yourself (like me), you may leverage some existing libraries for that. However, it does not require much time to do it by yourself as you see in the following code. Our custom filter implementation has two parameters: operator and value.

@Data
public class FilterField {
   private String operator;
   private String value;

   public Predicate generateCriteria(CriteriaBuilder builder, Path field) {
      try {
         int v = Integer.parseInt(value);
         switch (operator) {
         case "lt": return builder.lt(field, v);
         case "le": return builder.le(field, v);
         case "gt": return builder.gt(field, v);
         case "ge": return builder.ge(field, v);
         case "eq": return builder.equal(field, v);
         }
      } catch (NumberFormatException e) {
         switch (operator) {
         case "endsWith": return builder.like(field, "%" + value);
         case "startsWith": return builder.like(field, value + "%");
         case "contains": return builder.like(field, "%" + value + "%");
         case "eq": return builder.equal(field, value);
         }
      }

      return null;
   }
}

Now, with FilterField we may create a concrete implementation of filters consisting of several simple FilterField. The example of such implementation is EmployeeFilter class that has three possible criterias of filtering by salary, age and position.

@Data
public class EmployeeFilter {
   private FilterField salary;
   private FilterField age;
   private FilterField position;
}

Now if you would like to use that filter in your GraphQL query you should create something like that. In that query we are searching for all developers that has a salary greater than 12000 and age greater than 30 years.

{
  employeesWithFilter(filter: {
    salary: {
      operator: "gt"
      value: "12000"
    },
    age: {
      operator: "gt"
      value: "30"
    },
    position: {
      operator: "eq",
      value: "Developer"
    }
  }) {
    id
    firstName
    lastName
    position
  }
}

Let’s take a look at the implementation of query resolver. The same as for fetching relations we are using JPA Criteria API and Specification class. I have three methods that creates Specification for each of possible filter fields. Then I’m building dynamically filtering criterias based on the content of EmployeeFilter.

@Component
public class EmployeeQueryResolver implements GraphQLQueryResolver {

   private EmployeeRepository repository;

   EmployeeQueryResolver(EmployeeRepository repository) {
      this.repository = repository;
   }

   // OTHER FIND METHODS ...
   
   public Iterable<Employee&qt; employeesWithFilter(EmployeeFilter filter) {
      Specification<Employee&qt; spec = null;
      if (filter.getSalary() != null)
         spec = bySalary(filter.getSalary());
      if (filter.getAge() != null)
         spec = (spec == null ? byAge(filter.getAge()) : spec.and(byAge(filter.getAge())));
      if (filter.getPosition() != null)
         spec = (spec == null ? byPosition(filter.getPosition()) :
               spec.and(byPosition(filter.getPosition())));
      if (spec != null)
         return repository.findAll(spec);
      else
         return repository.findAll();
   }

   private Specification<Employee&qt; bySalary(FilterField filterField) {
      return (Specification<Employee&qt;) (root, query, builder) -&qt; filterField.generateCriteria(builder, root.get("salary"));
   }

   private Specification<Employee&qt; byAge(FilterField filterField) {
      return (Specification<Employee&qt;) (root, query, builder) -&qt; filterField.generateCriteria(builder, root.get("age"));
   }

   private Specification<Employee&qt; byPosition(FilterField filterField) {
      return (Specification<Employee&qt;) (root, query, builder) -&qt; filterField.generateCriteria(builder, root.get("position"));
   }
}

6. Testing Spring Boot GraphQL JPA support

We will insert some test data into the H2 database by defining data.sql inside src/main/resources directory.

insert into organization (id, name) values (1, 'Test1');
insert into organization (id, name) values (2, 'Test2');
insert into organization (id, name) values (3, 'Test3');
insert into organization (id, name) values (4, 'Test4');
insert into organization (id, name) values (5, 'Test5');
insert into department (id, name, organization_id) values (1, 'Test1', 1);
insert into department (id, name, organization_id) values (2, 'Test2', 1);
insert into department (id, name, organization_id) values (3, 'Test3', 1);
insert into department (id, name, organization_id) values (4, 'Test4', 2);
insert into department (id, name, organization_id) values (5, 'Test5', 2);
insert into department (id, name, organization_id) values (6, 'Test6', 3);
insert into department (id, name, organization_id) values (7, 'Test7', 4);
insert into department (id, name, organization_id) values (8, 'Test8', 5);
insert into department (id, name, organization_id) values (9, 'Test9', 5);
insert into employee (id, first_name, last_name, position, salary, age, department_id, organization_id) values (1, 'John', 'Smith', 'Developer', 10000, 30, 1, 1);
insert into employee (id, first_name, last_name, position, salary, age, department_id, organization_id) values (2, 'Adam', 'Hamilton', 'Developer', 12000, 35, 1, 1);
insert into employee (id, first_name, last_name, position, salary, age, department_id, organization_id) values (3, 'Tracy', 'Smith', 'Architect', 15000, 40, 1, 1);
insert into employee (id, first_name, last_name, position, salary, age, department_id, organization_id) values (4, 'Lucy', 'Kim', 'Developer', 13000, 25, 2, 1);
insert into employee (id, first_name, last_name, position, salary, age, department_id, organization_id) values (5, 'Peter', 'Wright', 'Director', 50000, 50, 4, 2);
insert into employee (id, first_name, last_name, position, salary, age, department_id, organization_id) values (6, 'Alan', 'Murray', 'Developer', 20000, 37, 4, 2);
insert into employee (id, first_name, last_name, position, salary, age, department_id, organization_id) values (7, 'Pamela', 'Anderson', 'Analyst', 7000, 27, 4, 2);

Now, we can easily perform some test queries by using GraphiQL that is embedded into our application and available under address http://localhost:8080/graphiql after startup. First, let’s verify the filtering query.

graphql-spring-boot-query-1

Now, we may test fetching by searching Department by id and fetching a list of employees and organization.

graphql-spring-boot-query-2

The post An Advanced Guide to GraphQL with Spring Boot appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2020/07/31/an-advanced-guide-to-graphql-with-spring-boot/feed/ 6 8220
GraphQL – The Future of Microservices? https://piotrminkowski.com/2018/08/16/graphql-the-future-of-microservices/ https://piotrminkowski.com/2018/08/16/graphql-the-future-of-microservices/#comments Thu, 16 Aug 2018 07:34:42 +0000 https://piotrminkowski.wordpress.com/?p=6783 Often, GraphQL is presented as a revolutionary way of designing web APIs in comparison to REST. However, if you would take a closer look at that technology you will see that there are so many differences between them. GraphQL is a relatively new solution that has been open-sourced by Facebook in 2015. Today, REST is […]

The post GraphQL – The Future of Microservices? appeared first on Piotr's TechBlog.

]]>
Often, GraphQL is presented as a revolutionary way of designing web APIs in comparison to REST. However, if you would take a closer look at that technology you will see that there are so many differences between them. GraphQL is a relatively new solution that has been open-sourced by Facebook in 2015. Today, REST is still the most popular paradigm used for exposing APIs and inter-service communication between microservices. Is GraphQL going to overtake REST in the future? Let’s take a look at how to create microservices communicating through GraphQL API using Spring Boot and Apollo client.

Let’s begin with the Spring Boot GraphQL microservices architecture of our sample system. We have three microservices that communicate to each other using URLs taken from Eureka service discovery.

spring-boot-microservices-graphql-arch"

1. Enabling Spring Boot support for GraphQL

We can easily enable support for GraphQL on the server-side Spring Boot application just by including some starters. After including graphql-spring-boot-starter the GraphQL servlet would be automatically accessible under path /graphql. We can override that default path by settings property graphql.servlet.mapping in application.yml file. We should also enable GraphiQL – an in-browser IDE for writing, validating, and testing GraphQL queries, and GraphQL Java Tools library, which contains useful components for creating queries and mutations. Thanks to that library any files on the classpath with .graphqls extension will be used to provide the schema definition.

<dependency>
   <groupId>com.graphql-java</groupId>
   <artifactId>graphql-spring-boot-starter</artifactId>
   <version>5.0.2</version>
</dependency>
<dependency>
   <groupId>com.graphql-java</groupId>
   <artifactId>graphiql-spring-boot-starter</artifactId>
   <version>5.0.2</version>
</dependency>
<dependency>
   <groupId>com.graphql-java</groupId>
   <artifactId>graphql-java-tools</artifactId>
   <version>5.2.3</version>
</dependency>

2. Building GraphQL schema definition

Every schema definition contains data types declaration, relationships between them, and a set of operations including queries for searching objects and mutations for creating, updating or deleting data. Usually we will start from creating type declarations, which is responsible for domain object definition. You can specify if the field is required using ! char or if it is an array using [...]. The definition has to contain type declaration or reference to other types available in the specification.

type Employee {
  id: ID!
  organizationId: Int!
  departmentId: Int!
  name: String!
  age: Int!
  position: String!
  salary: Int!
}

Here’s an equivalent Java class to GraphQL definition visible above. GraphQL type Int can be also mapped to Java Long. The ID scalar type represents a unique identifier – in that case it also would be Java Long.

public class Employee {

   private Long id;
   private Long organizationId;
   private Long departmentId;
   private String name;
   private int age;
   private String position;
   private int salary;
   
   // constructor
   
   // getters
   // setters
   
}

The next part of schema definition contains queries and mutations declaration. Most of the queries return list of objects – what is marked with [Employee]. Inside EmployeeQueries type we have declared all find methods, while inside EmployeeMutations type methods for adding, updating and removing employees. If you pass the whole object to that method you need to declare it as an input type.

schema {
  query: EmployeeQueries
  mutation: EmployeeMutations
}

type EmployeeQueries {
  employees: [Employee]
  employee(id: ID!): Employee!
  employeesByOrganization(organizationId: Int!): [Employee]
  employeesByDepartment(departmentId: Int!): [Employee]
}

type EmployeeMutations {
  newEmployee(employee: EmployeeInput!): Employee
  deleteEmployee(id: ID!) : Boolean
  updateEmployee(id: ID!, employee: EmployeeInput!): Employee
}

input EmployeeInput {
  organizationId: Int
  departmentId: Int
  name: String
  age: Int
  position: String
  salary: Int
}

3. Queries and mutation implementation

Thanks to GraphQL Java Tools and Spring Boot GraphQL auto-configuration we don’t need to do much to implement queries and mutations in our application. The EmployeesQuery bean has to GraphQLQueryResolver interface. Based on that Spring would be able to automatically detect and call the right method as a response to one of the GraphQL queries declared inside the schema. Here’s a class containing an implementation of queries.

@Component
public class EmployeeQueries implements GraphQLQueryResolver {

   private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeQueries.class);
   
   @Autowired
   EmployeeRepository repository;
   
   public List<Employee> employees() {
      LOGGER.info("Employees find");
      return repository.findAll();
   }
   
   public List<Employee> employeesByOrganization(Long organizationId) {
      LOGGER.info("Employees find: organizationId={}", organizationId);
      return repository.findByOrganization(organizationId);
   }

   public List<Employee> employeesByDepartment(Long departmentId) {
      LOGGER.info("Employees find: departmentId={}", departmentId);
      return repository.findByDepartment(departmentId);
   }
   
   public Employee employee(Long id) {
      LOGGER.info("Employee find: id={}", id);
      return repository.findById(id);
   }
   
}

If you would like to call, for example method employee(Long id) you should build the following query. You can easily test it in your application using the GraphiQL tool available under path /graphiql.

graphql-1
The bean responsible for implementation of mutation methods needs to implement GraphQLMutationResolver. Despite declaration of EmployeeInput we still use the same domain object as returned by queries – Employee.

@Component
public class EmployeeMutations implements GraphQLMutationResolver {

   private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeQueries.class);
   
   @Autowired
   EmployeeRepository repository;
   
   public Employee newEmployee(Employee employee) {
      LOGGER.info("Employee add: employee={}", employee);
      return repository.add(employee);
   }
   
   public boolean deleteEmployee(Long id) {
      LOGGER.info("Employee delete: id={}", id);
      return repository.delete(id);
   }
   
   public Employee updateEmployee(Long id, Employee employee) {
      LOGGER.info("Employee update: id={}, employee={}", id, employee);
      return repository.update(id, employee);
   }
   
}

We can also use GraphiQL to test mutations. Here’s the command that adds a new employee, and receives response with employee’s id and name.

graphql-2

4. Generating client-side classes

Ok, we have successfully created a server-side application. We have already tested some queries using GraphiQL. But our main goal is to create some other microservices that communicate with employee-service application through GraphQL API. Here are most of the tutorials about Spring Boot and GraphQL ending.
To be able to communicate with our first application through GraphQL API we have two choices. We can get a standard REST client and implement GraphQL API by ourselves with HTTP GET requests or use one of existing Java clients. Surprisingly, there are not many GraphQL Java client implementations available. The most serious choice is Apollo GraphQL Client for Android. Of course it is not designed only for Android devices, and you can successfully use it in your microservice Java application.
Before using the client we need to generate classes from schema and .grapql files. The recommended way to do it is through the Apollo Gradle Plugin. There are also some Maven plugins, but none of them provide the level of automation as Gradle plugin, for example it automatically downloads node.js required for generating client-side classes. So, the first step is to add Apollo plugin and runtime to the project dependencies.

buildscript {
  repositories {
    jcenter()
    maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
  }
  dependencies {
    classpath 'com.apollographql.apollo:apollo-gradle-plugin:1.0.1-SNAPSHOT'
  }
}

apply plugin: 'com.apollographql.android'

dependencies {
  compile 'com.apollographql.apollo:apollo-runtime:1.0.1-SNAPSHOT'
}

GraphQL Gradle plugin tries to find files with .graphql extension and schema.json inside src/main/graphql directory. GraphQL JSON schema can be obtained from your Spring Boot application by calling resource /graphql/schema.json. File .graphql contains queries definition. Query employeesByOrganization will be called by organization-service, while employeesByDepartment by both department-service and organization-service. Those two applications need a little different set of data in the response. Application department-service requires more detailed information about every employee than organization-service. GraphQL is an excellent solution in that case, because we can define the required set of data in the response on the client side. Here’s the query definition of employeesByOrganization called by organization-service.

query EmployeesByOrganization($organizationId: Int!) {
  employeesByOrganization(organizationId: $organizationId) {
    id
    name
  }
}

Application organization-service would also call employeesByDepartment query.

query EmployeesByDepartment($departmentId: Int!) {
  employeesByDepartment(departmentId: $departmentId) {
    id
    name
  }
}

The query employeesByDepartment is also called by department-service, which requires not only id and name fields, but also position and salary.

query EmployeesByDepartment($departmentId: Int!) {
  employeesByDepartment(departmentId: $departmentId) {
    id
    name
    position
    salary
  }
}

All the generated classes are available under build/generated/source/apollo directory.

5. Building Apollo client with discovery

After generating all required classes and including them into calling microservices we may proceed to the client implementation. Apollo client has two important features that will affect our development:

  • It provides only asynchronous methods based on callback
  • It does not integrate with service discovery based on Spring Cloud Netflix Eureka

Here’s an implementation of employee-service client inside department-service. I used EurekaClient directly (1). It gets all running instances registered as EMPLOYEE-SERVICE. Then it selects one instance form the list of available instances randomly (2). The port number of that instance is passed to ApolloClient (3). Before calling asynchronous method enqueue provided by ApolloClient we create lock (4), which waits max. 5 seconds for releasing (8). Method enqueue returns response in the callback method onResponse (5). We map the response body from GraphQL Employee object to the returned object (6) and then release the lock (7).

@Component
public class EmployeeClient {

   private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeClient.class);
   private static final int TIMEOUT = 5000;
   private static final String SERVICE_NAME = "EMPLOYEE-SERVICE"; 
   private static final String SERVER_URL = "http://localhost:%d/graphql";
   
   Random r = new Random();
   
   @Autowired
   private EurekaClient discoveryClient; // (1)
   
   public List<Employee> findByDepartment(Long departmentId) throws InterruptedException {
      List<Employee> employees = new ArrayList<>();
      Application app = discoveryClient.getApplication(SERVICE_NAME); // (2)
      InstanceInfo ii = app.getInstances().get(r.nextInt(app.size()));
      ApolloClient client = ApolloClient.builder().serverUrl(String.format(SERVER_URL, ii.getPort())).build(); // (3)
      CountDownLatch lock = new CountDownLatch(1); // (4)
      client.query(EmployeesByDepartmentQuery.builder().build()).enqueue(new Callback<EmployeesByDepartmentQuery.Data>() {

         @Override
         public void onFailure(ApolloException ex) {
            LOGGER.info("Err: {}", ex);
            lock.countDown();
         }

         @Override
         public void onResponse(Response<EmployeesByDepartmentQuery.Data> res) { // (5)
            LOGGER.info("Res: {}", res);
            employees.addAll(res.data().employees().stream().map(emp -> new Employee(Long.valueOf(emp.id()), emp.name(), emp.position(), emp.salary())).collect(Collectors.toList())); // (6)
            lock.countDown(); // (7)
         }

      });
      lock.await(TIMEOUT, TimeUnit.MILLISECONDS); // (8)
      return employees;
   }
   
}

Finally, EmployeeClient is injected into the query resolver class – DepartmentQueries, and used inside query departmentsByOrganizationWithEmployees.

@Component
public class DepartmentQueries implements GraphQLQueryResolver {

   private static final Logger LOGGER = LoggerFactory.getLogger(DepartmentQueries.class);
   
   @Autowired
   EmployeeClient employeeClient;
   @Autowired
   DepartmentRepository repository;

   public List<Department> departmentsByOrganizationWithEmployees(Long organizationId) {
      LOGGER.info("Departments find: organizationId={}", organizationId);
      List<Department> departments = repository.findByOrganization(organizationId);
      departments.forEach(d -> {
         try {
            d.setEmployees(employeeClient.findByDepartment(d.getId()));
         } catch (InterruptedException e) {
            LOGGER.error("Error calling employee-service", e);
         }
      });
      return departments;
   }
   
   // other queries
   
}

Before calling the target query we should take a look at the schema created for department-service. Every Department object can contain the list of assigned employees, so we also define type Employee referenced by Department type.

schema {
  query: DepartmentQueries
  mutation: DepartmentMutations
}

type DepartmentQueries {
  departments: [Department]
  department(id: ID!): Department!
  departmentsByOrganization(organizationId: Int!): [Department]
  departmentsByOrganizationWithEmployees(organizationId: Int!): [Department]
}

type DepartmentMutations {
  newDepartment(department: DepartmentInput!): Department
  deleteDepartment(id: ID!) : Boolean
  updateDepartment(id: ID!, department: DepartmentInput!): Department
}

input DepartmentInput {
  organizationId: Int!
  name: String!
}

type Department {
  id: ID!
  organizationId: Int!
  name: String!
  employees: [Employee]
}

type Employee {
  id: ID!
  name: String!
  position: String!
  salary: Int!
}

Now, we can call our test query with a list of required fields using GraphiQL. An application department-service is by default available under port 8091, so we may call it using address http://localhost:8091/graphiql.

graphql-3

Conclusion

GraphQL seems to be an interesting alternative to standard REST APIs. However, we should not consider it as a replacement to REST. There are some use cases where GraphQL may be a better choice, and some use cases where REST is a better choice. If your clients do not need the full set of fields returned by the server-side, and moreover you have many clients with different requirements to the single endpoint – GraphQL is a good choice. When it comes to Spring Boot microservices there are no solutions based on Java that allow you to use GraphQL together with service discovery, load balancing or API gateway out-of-the-box. In this article, I have shown an example of usage of Apollo GraphQL client together with Spring Cloud Eureka for inter-service communication. Sample applications source code is available on GitHub https://github.com/piomin/sample-graphql-microservices.git.

The post GraphQL – The Future of Microservices? appeared first on Piotr's TechBlog.

]]>
https://piotrminkowski.com/2018/08/16/graphql-the-future-of-microservices/feed/ 13 6783