通常,與 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-starter
GraphQL servlet 後将在 path 下自動通路
/graphql
。我們可以覆寫設定屬性,預設路徑
graphql.servlet.mapping
的
application.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
Long
。該
ID
标量類型代表的唯一辨別符-在這種情況下,它也将成為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]
. 在
EmployeeQueries
type 中我們聲明了所有的 find 方法,而在
EmployeeMutations
type 中添加、更新和删除員工的方法。如果将整個對象傳遞給該方法,則需要将其聲明為
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
,而
employeesByDepartment
由
department-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
,它不僅需要
id
and
name
字段,還需要
position
and
salary
query EmployeesByDepartment($departmentId: Int!) {
employeesByDepartment(departmentId: $departmentId) {
id
name
position
salary
}
}
所有生成的類都在
build/generated/source/apollo
目錄下可用。
5. 使用發現建構 Apollo 用戶端
在生成所有必需的類并将它們包含到調用微服務中後,我們可以繼續進行用戶端實作。Apollo 用戶端有兩個重要的特性會影響我們的開發:
- 它隻提供基于回調的異步方法
- 它沒有與基于 Spring Cloud Netflix Eureka 的服務發現內建
這是
employee-service
client inside的一個實作
department-service
。我
EurekaClient
直接使用(1)。它将所有正在運作的執行個體注冊為
EMPLOYEE-SERVICE
. 然後它從可用執行個體清單中随機選擇一個執行個體(2)。該執行個體的端口号被傳遞給
ApolloClient
(3)。在調用我們
enqueue
提供的異步方法之前,
ApolloClient
我們建立了 lock (4),它最大等待。5 秒釋放(8)。方法 enqueue 在回調方法
onResponse
(5) 中傳回響應。我們将響應主體從 GraphQL
Employee
對象映射到傳回的對象(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
對象都可以包含配置設定的員工清單,是以我們還定義了
Employee
Department
類型引用的類型。
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 入門教程