基於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 入門教程

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM