zoukankan      html  css  js  c++  java
  • 基于GraphQL的微服务实践-spring cloud 入门教程

    通常,与 REST 相比,GraphQL 被认为是一种革命性的 Web API 设计方式。但是,如果您仔细研究该技术,您会发现它们之间存在很多差异。GraphQL 是一种相对较新的解决方案,已于 2015 年由 Facebook 开源。今天,REST 仍然是最流行的用于公开 API 和微服务之间的服务间通信的范式。GraphQL 会在未来超过 REST 吗?让我们来看看如何使用 Spring Boot 和 Apollo 客户端创建通过 GraphQL API 进行通信的微服务。

    让我们从示例系统的 Spring Boot GraphQL 微服务架构开始。我们有三个微服务,它们使用从 Eureka 服务发现中获取的 URL 相互通信。

    1. 为 GraphQL 启用 Spring Boot 支持

    只需包含一些启动器,我们就可以轻松地在服务器端 Spring Boot 应用程序上启用对 GraphQL 的支持。包含graphql-spring-boot-starterGraphQL servlet 后将在 path 下自动访问/graphql我们可以覆盖设置属性,默认路径graphql.servlet.mappingapplication.yml文件。我们还应该启用 GraphiQL——一个用于编写、验证和测试 GraphQL 查询的浏览器内 IDE,以及 GraphQL Java 工具库,它包含用于创建查询和突变的有用组件。由于该库,类路径上带有.graphqls扩展名的任何文件都将用于提供模式定义。

    <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. 构建 GraphQL 模式定义

    每个模式定义都包含数据类型声明、它们之间的关系以及一组操作,包括用于搜索对象的查询和用于创建、更新或删除数据的更改。通常我们会从创建类型声明开始,它负责域对象定义。您可以使用!char指定该字段是必需的还是使用[...]定义必须包含类型声明或对规范中可用的其他类型的引用。

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

    这是上面可见的 GraphQL 定义的等效 Java 类。GraphQL 类型Int也可以映射到 Java LongID标量类型代表的唯一标识符-在这种情况下,它也将成为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
       
    }
    

    模式定义的下一部分包含查询和变更声明。大多数查询返回对象列表——用[Employee]EmployeeQueriestype 中我们声明了所有的 find 方法,而在EmployeeMutationstype 中添加、更新和删除员工的方法。如果将整个对象传递给该方法,则需要将其声明为input类型。

    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. 查询和​​变异实现

    多亏了 GraphQL Java 工具和 Spring Boot GraphQL 自动配置,我们不需要做太多的事情来在我们的应用程序中实现查询和更改。EmployeesQuery豆有GraphQLQueryResolver接口。基于这一点,Spring 将能够自动检测并调用正确的方法,作为对模式内声明的 GraphQL 查询之一的响应。这是一个包含查询实现的类。

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

    如果您想调用例如方法,employee(Long id)您应该构建以下查询。您可以使用 path 下提供的 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 和姓名的响应的命令。

    4. 生成客户端类

    好的,我们已经成功创建了一个服务器端应用程序。我们已经使用 GraphiQL 测试了一些查询。但我们的主要目标是创建一些其他微服务,employee-service通过 GraphQL API应用程序通信这里是大部分关于 Spring Boot 和 GraphQL 结尾的教程。
    为了能够通过 GraphQL API 与我们的第一个应用程序通信,我们有两种选择。我们可以使用 HTTP GET 请求或使用现有的 Java 客户端之一来获取标准的 REST 客户端并自己实现 GraphQL API。令人惊讶的是,可用的 GraphQL Java 客户端实现并不多。最严重的选择是适用于 Android 的 Apollo GraphQL 客户端。当然,它不仅是为 Android 设备设计的,您也可以在您的微服务 Java 应用程序中成功使用它。
    在使用客户端之前,我们需要从模式和.grapql文件生成类推荐的方法是通过 Apollo Gradle 插件。还有一些 Maven 插件,但它们都没有提供 Gradle 插件的自动化级别,例如它会自动下载生成客户端类所需的 node.js。因此,第一步是将 Apollo 插件和运行时添加到项目依赖项中。

    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 插件尝试查找具有.graphql扩展名和schema.json内部src/main/graphql目录的文件。可以通过调用 resource 从 Spring Boot 应用程序获取 GraphQL JSON 模式/graphql/schema.json文件.graphql包含查询定义。查询employeesByOrganization将由 调用organization-service,而employeesByDepartmentdepartment-service调用organization-service这两个应用程序在响应中需要一些不同的数据集。应用程序department-service需要比organization-service在这种情况下,GraphQL 是一个很好的解决方案,因为我们可以在客户端的响应中定义所需的数据集。这是employeesByOrganization调用者的查询定义organization-service

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

    应用程序organization-service也会调用employeesByDepartment查询。

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

    查询employeesByDepartment也被调用department-service,它不仅需要idandname字段,还需要positionand salary

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

    所有生成的类都在build/generated/source/apollo目录下可用

    5. 使用发现构建 Apollo 客户端

    在生成所有必需的类并将它们包含到调用微服务中后,我们可以继续进行客户端实现。Apollo 客户端有两个重要的特性会影响我们的开发:

    • 它只提供基于回调的异步方法
    • 它没有与基于 Spring Cloud Netflix Eureka 的服务发现集成

    这是employee-serviceclient inside的一个实现department-serviceEurekaClient直接使用(1)它将所有正在运行的实例注册为EMPLOYEE-SERVICE然后它从可用实例列表中随机选择一个实例(2)该实例的端口号被传递给ApolloClient (3)在调用我们enqueue提供的异步方法之前ApolloClient我们创建了 lock (4),它最大等待。5 秒释放(8)方法 enqueue 在回调方法onResponse (5) 中返回响应我们将响应主体从 GraphQLEmployee对象映射到返回的对象(6),然后释放锁(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;
       }
       
    }
    

    最后,EmployeeClient注入查询解析器类 – DepartmentQueries,并在 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
       
    }
    

    在调用目标查询之前,我们应该查看为department-service每个Department对象都可以包含分配的员工列表,因此我们还定义了EmployeeDepartment类型引用的类型。

    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 使用必填字段列表调用我们的测试查询。department-service默认情况下,应用程序在端口 8091 下可用,因此我们可以使用 address 调用它http://localhost:8091/graphiql

    结论

    GraphQL 似乎是标准 REST API 的有趣替代品。但是,我们不应将其视为 REST 的替代品。在某些用例中,GraphQL 可能是更好的选择,而在某些用例中,REST 是更好的选择。如果您的客户端不需要服务器端返回的完整字段集,而且您有许多客户端对单个端点有不同的要求,那么 GraphQL 是一个不错的选择。对于 Spring Boot 微服务,没有基于 Java 的解决方案允许您将 GraphQL 与服务发现、负载平衡或开箱即用的 API 网关一起使用。

    使用 Zuul、Ribbon、Feign、Eureka 和 Sleuth、Zipkin 创建简单spring cloud微服务用例-spring cloud 入门教程
    微服务集成SPRING CLOUD SLEUTH、ELK 和 ZIPKIN 进行监控-spring cloud 入门教程
    使用Hystrix 、Feign 和 Ribbon构建微服务-spring cloud 入门教程

    使用 Spring Boot Admin 监控微服务-spring cloud 入门教程

    基于Redis做Spring Cloud Gateway 中的速率限制实践-spring cloud 入门教程
    集成SWAGGER2服务-spring cloud 入门教程
    Hystrix 简介-spring cloud 入门教程
    Hystrix 原理深入分析-spring cloud 入门教程 
    使用Apache Camel构建微服务-spring cloud 入门教程
    集成 Kubernetes 来构建微服务-spring cloud 入门教程
    集成SPRINGDOC OPENAPI 的微服务实践-spring cloud 入门教程
    SPRING CLOUD 微服务快速指南-spring cloud 入门教程
    基于GraphQL的微服务实践-spring cloud 入门教程
    最火的Spring Cloud Gateway 为经过身份验证的用户启用速率限制实践-spring cloud 入门教程
  • 相关阅读:
    利用connect建立前端开发服务器
    Bootstrap Popover 隐藏的Javasript方法
    Kafka 2.1.0压缩算法性能测试
    关于Kafka java consumer管理TCP连接的讨论
    Java API获取consumer group最新提交位移的时间
    关于Kafka producer管理TCP连接的讨论
    【译】Apache Kafka支持单集群20万分区
    关于Kafka broker IO的讨论
    Kafka 2.0 ConsumerGroupCommand新功能
    关于Kafka high watermark的讨论2
  • 原文地址:https://www.cnblogs.com/BlogNetSpace/p/15165403.html
Copyright © 2011-2022 走看看