⬆️ ⬇️

GraphQL future of microservices?

GraphQL is often presented as a revolutionary way for web API design compared to REST. However, if you take a closer look at these technologies, then you will see that there are so many differences between them. GraphQL is a relatively new solution, the source code of which was opened to the Facebook community in 2015. Today, REST is still the most popular paradigm used to provide API and interaction between microservices. Will GraphQL overtake REST in the future? Let's take a look at how microservice interaction occurs via the GraphQL API using the Spring Boot and the GQL library.



Let's start with the architecture of our system example. Suppose we have three microservices that communicate with each other through the URL obtained from the Spring Cloud Eureka application.



image



Enable GraphQL Support in Spring Boot



We can easily enable support for GraphQL on the server side of the Spring Boot application using starters. After adding the graphql-spring-boot-starter, the GraphQL servlet will automatically be accessible via the path / graphql. We can override this default path by specifying the graphql.servlet.mapping property in the application.yml file. We also include a GraphiQL browser-based IDE for writing, testing, and testing GraphQL queries and the GraphQL Java Tools library, which contains useful components for creating queries and mutations. Thanks to this library, all files in the classpath with the .graphqls extension will be used to create the schema definition.

')

compile('com.graphql-java:graphql-spring-boot-starter:5.0.2') compile('com.graphql-java:graphiql-spring-boot-starter:5.0.2') compile('com.graphql-java:graphql-java-tools:5.2.3') 


Description GrpahQL schema



Each schema description contains a type declaration, relationships between them, and a variety of operations involving queries to search for objects and mutations to create, update, or delete data. Usually we begin with a type definition that is responsible for the domain of the object being described. You can specify whether the field is required with ! character or if it is an array - […] . The description must contain a declared type 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! } 


The next part of the schema definition contains the declaration of requests and mutations. Most queries return a list of objects that are labeled as [Employee] in the schema. Inside the EmployeeQueries type, we declare all the search methods, while in the EmployeeMutations type we add, update, and delete employees. If you pass an entire object to a method, you must 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 } 


Implementation of requests and mutations



Thanks to GraphQL Java Tools autoconfiguration and Spring Boot GraphQL, we don’t need to make a lot of effort to implement queries and mutations in our application. The EmployeesQuery bean must implement the GraphQLQueryResolver interface. Based on this, Spring will be able to automatically find and call the right method as an answer to one of the GraphQL queries that were declared inside the schema. Here is the class containing the implementation of responses to requests:



 @Component public class EmployeeQueries implements GraphQLQueryResolver { private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeQueries.class); @Autowired EmployeeRepository repository; public List employees() { LOGGER.info("Employees find"); return repository.findAll(); } public List employeesByOrganization(Long organizationId) { LOGGER.info("Employees find: organizationId={}", organizationId); return repository.findByOrganization(organizationId); } public List 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 want to call, for example, the employee (Long id) method, write the following query. To test it in your application, use GraphiQL, which is available at / graphiql.



image



The bin responsible for implementing mutation methods needs to implement a GraphQLMutationResolver interface. Despite the name EmployeeInput, we continue to use the same domain object Employee that is returned by the query.



 @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); } } 


And here we use GraphiQL to test mutations. Here is a team that adds a new employee and accepts the answer with the employee id and name.



image



With this, I suspend the translation of this article and write my “lyrical digression”, and in fact I substitute the description of the part of the microservice interaction through the Apollo Client, for interaction through the GQL library and Unirest - the library for performing HTTP requests.





GraphQL client on Groovy.



To create graphQL queries in department-servive microservice, I will use Query builders :



 String queryString = DSL.buildQuery { query('employeesByDepartment', [departmentId: departmentId]) { returns { id name position salary } } } 


This construction in GQL DSL creates a query of the form:



 { employeesByDepartment (departmentId: 1) { id name position salary } } 


And, further, I will execute HTTP request on the address transferred to a method.

How the request address is formed will be found later.



 (Unirest.post(serverUrl) .body(JsonOutput.toJson([query: queryString])) .asJson() .body.jsonObject['data']['employeesByDepartment'] as List) .collect { JsonUtils.jsonToData(it.toString(), Employee.class) } 


After receiving the response, we perform its conversion from JSONObject to the Employee list view.



GrpahQL client for microservice employees



Consider the implementation of microservice employees. In this example, I used Eureka client directly. eurekaClient gets all running instances of services registered as employee-service. Then he randomly selects one of the registered copies (2). Next, it takes its port number and forms the request address (3) and sends it to the EmployeeGQL object, which is a GraphQL client on Groovy and, which is described in the previous paragraph.



 @Component public class EmployeeClient { private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeClient.class); 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) { Application app = discoveryClient.getApplication(SERVICE_NAME); InstanceInfo ii = app.getInstances().get(r.nextInt(app.size())); // (2) String serverUrl = String.format(SERVER_URL, ii.getPort()); // (3) EmployeeGQL clientGQL = new EmployeeGQL(); return clientGQL.getEmployeesByDepartmentQuery(serverUrl, departmentId.intValue()); // (4) } } 


Further, I “hand over” the word to the author again, or rather continue the translation of his article.



Finally, EmployeeClient is implemented in a class that responds to DepartmentQueries requests and is used inside departmentsByOrganizationWithEmployees.



 public List<Department> departmentsByOrganizationWithEmployees(Long organizationId) { LOGGER.info("Departments find: organizationId={}", organizationId); List<Department> departments = repository.findByOrganization(organizationId); for (int i = 0; i < departments.size(); i++) { departments.get(i).setEmployees(employeeClient.findByDepartment(departments.get(i).getId())); } return departments; } 


Before executing the necessary requests, we follow the diagram created for the department-service. Each Department object can contain a list of assigned employees, and we also defined the type of Employee referred to by the 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 the required fields using GraphiQL. The department-service application is available by default on port 8091, that is, we can see it at http: // localhost: 8091 / graphiql



Conclusion



Perhaps GraphQL may be an interesting alternative to the standard REST API. However, we should not consider it as a replacement for REST. There are several cases where GraphQL may be the best choice, but those where REST is the best choice. If your clients do not need to have all the fields returned by the server side, and moreover, you have many clients with different requirements for one entry point, then GraphQL is a good choice. If you look at what is in the microservice community, you can see that now there is no Java-based solution that allows you to use GraphQL with service discovery, balancer, or API gateway out of the box. In this article, I showed an example of using GQL and Unirest to create a GraphQL client with Spring Cloud Eureka for microservice communication. Sample code of the author of the article in English on GitHub github.com/piomin/sample-graphql-microservices.git .



My example when with the GQL library : github.com/lynx-r/sample-graphql-microservices

Source: https://habr.com/ru/post/421985/



All Articles