GraphQL是微服务的未来吗?

与REST相比,GraphQL通常被认为是设计Web API的革命性方法。 但是,如果仔细研究这些技术,将会发现它们之间有很多差异。 GraphQL是一个相对较新的解决方案,其资源于2015年向Facebook社区开放。 如今,REST仍然是用于提供微服务之间的API和互操作性的最流行范例。 GraphQL将来能够超越REST吗? 让我们看看如何使用Spring Boot和GQL库通过GraphQL API进行微服务交互。

让我们从系统的体系结构示例开始。 假设我们有三个微服务,它们通过从Spring Cloud Eureka应用程序接收的URL相互通信。

图片

在Spring Boot中启用GraphQL支持


我们可以使用启动器在Spring Boot应用程序的服务器端轻松启用对GraphQL的支持。 添加graphql-spring-boot-starter之后,GraphQL servlet将在/ graphql上自动可用。 我们可以通过在application.yml文件中指定graphql.servlet.mapping属性来覆盖此默认路径。 我们还包括GraphiQL(用于编写,验证和测试GraphQL查询的基于浏览器的IDE)以及GraphQL Java工具库,其中包含用于创建查询和变异的有用组件。 由于有了这个库,类路径中所有扩展名为.graphqls的文件都将用于创建模式定义。

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') 

GrpahQL模式的说明


该方案的每个描述都包含类型声明,它们之间的关系以及许多操作,其中包括查询以搜索对象以及创建或更新数据的突变。 我们通常从定义负责所描述对象的域的类型开始。 您可以使用!指示是否需要该字段! 字符,或者它是一个数组- […] 。 该描述必须包含声明的类型或对规范中可用其他类型的引用。

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

模式定义的下一部分包含查询和变异的声明。 大多数查询返回在模式中标记为[Employee]的对象的列表。 在EmployeeQueries类型中,我们声明所有搜索方法,而在EmployeeMutations类型中,我们声明用于添加,更新和删除员工的方法。 如果将整个对象传递给方法,则必须将其声明为输入类型。

 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 } 

实现查询和变异


得益于GraphQL Java工具和Spring Boot GraphQL的自动配置,我们无需花很多精力在应用程序中实现查询和变异。 EmployeesQuery bean必须实现GraphQLQueryResolver接口。 基于此,Spring将能够自动找到并调用正确的方法,作为对已在架构内部声明的GraphQL查询之一的响应。 这是包含查询响应的实现的类:

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

例如,如果要调用employee(Long id)方法,请编写以下查询。 要在您的应用程序中对其进行测试,请使用GraphiQL,该软件可在/ graphiql中获得。

图片

负责实现变异方法的Bean需要实现GraphQLMutationResolver接口。 尽管名称为EmployeeInput,我们仍将使用该请求返回的同一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); } } 

在这里,我们使用GraphiQL测试突变。 这是一个添加新员工并接受带有员工ID和姓名的响应的命令。

图片

在此,我暂停本文的翻译并写上“抒情离题”,但实际上是通过Apollo客户端替换了微服务部分的描述,以通过GQL和Unirest库(用于执行HTTP请求的库)进行交互。


Groovy上的GraphQL客户端。


为了在部门服务微服务中创建GraphQL查询,我将使用查询构建器

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

DSL GQL上的此构造创建以下形式的查询:

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

此外,我将在传递给该方法的地址处执行HTTP请求。
我们将找出请求地址的进一步构成。

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

收到响应后,我们将其从JSONObject转换为Employee列表视图。

员工微服务的GrpahQL客户端


考虑实施微服务员工。 在此示例中,我直接使用了Eureka客户端。 eurekaClient将所有正在运行的服务实例注册为员工服务。 然后,他从注册的实例中随机选择某个实例(2)。 接下来,它获取其端口号并形成请求地址(3),并将其传递给EmployeeGQL对象,该对象是Groovy上的GraphQL客户端,在上一段中进行了描述。

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

此外,我再次将该词“传递”给作者,或者我继续翻译他的文章。

最后,将EmployeeClient注入到响应DepartmentQueries请求的类中,并在departmentByOrganizationWithEmployees请求中使用该类。

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

在提出必要的请求之前,我们应该看一下为部门服务创建的图表。 每个Department对象都可以包含已分配员工的列表,我们还定义了Department类型引用的Employee类型。

 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! } 

现在,我们可以使用GraphiQL调用带有所需字段列表的测试查询。 默认情况下,部门服务应用程序在端口8091上可用,也就是说,我们可以在http:// localhost:8091 / graphiql上看到它

结论


也许GraphQL可能是标准REST API的有趣替代。 但是,我们不应该将其视为REST的替代品。 在某些情况下,GraphQL可能是最佳选择,而在其他情况下,REST是最佳选择。 如果您的客户端不需要服务器端返回所有字段,而且您有多个客户端对一个入口点有不同的要求,那么GraphQL是一个不错的选择。 如果您查看微服务社区中的内容,那么您会发现,现在没有基于Java的解决方案允许您将GraphQL与服务发现,平衡器或网关API结合使用。 在本文中,我展示了一个示例,该示例使用GQL和Unirest通过Spring Cloud Eureka创建用于微服务通信的GraphQL客户端。 GitHub github.com/piomin/sample-graphql-microservices.git上英文文章作者的示例代码。

我的一个使用GQL库的示例github.com/lynx-r/sample-graphql-microservices

Source: https://habr.com/ru/post/zh-CN421985/


All Articles