了解如何在微服务架构上构建无服务器 GraphQL API,第一部分
在Twitter上关注我,很高兴接受您对主题或改进的建议/Chris
本文的目的是展示如何构建微服务,将它们 docker 化,并将它们组合到 GraphQL API 中,然后通过无服务器函数进行查询,这么多流行语合在一起怎么样?;)微服务、Docker、GraphQL、无服务器
这是系列文章的一部分:
- 构建微服务和 GraphQL API,第一部分,我们在这里
- 在无服务器应用中托管 GraphQL API 并将其全部迁移到云端(第二部分)
因此,在一篇文章中实现微服务、无服务器架构并部署到云端是一个相当雄心勃勃的目标,所以这篇文章分为两部分。这是第一部分,主要介绍微服务和 GraphQL。在第二部分中,我们将实现无服务器架构并进行部署。
在本文中,我们将介绍:
- GraphQL 和微服务,这两个伟大的范例确实能够很好地结合在一起,让我们来解释一下
- 构建微服务并进行 Docker 化,让我们创建两个微服务,每个服务都有各自的职责范围,然后对它们进行 Docker 化
- 实现 GraphQl,我们将展示如何使用模式和解析器定义 GraphQL 后端以及如何查询它,当然,我们还将在这里绑定我们的微服务
资源
我们将首先向您介绍如何使用Docker
,GraphQL
以及一些Serverless
Azure 函数。本文更多的是向您介绍如何使用上述技术,因此,如果您觉得需要了解上述内容的入门知识,以下是我撰写的文章列表:
- Docker 系列包含 5 部分,涵盖 Docker 基础知识,包括 Dockerfile、容器、镜像和 Docker Compose
- GraphQL 系列文章分为 3 部分,涵盖 GraphQL 的基础知识
- 免费 Azure 帐户为了能够部署您的服务并托管您的无服务器功能,您需要一个免费的 Azure 帐户
- 使用 VS Code 的 Azure 函数教程,本文介绍如何使用 VS Code 创建无服务器函数
- Github repo 包含创建 GraphQL API 和无服务器函数的代码本系列文章很大程度上受到了我的同事Simona Cotin创建的研讨会的启发
- 云中的容器,因为我们即将在本系列的下一部分中将容器放入云中,所以阅读有关云中容器的所有产品是一个好主意
GraphQL 和微服务
像 GraphQL 这样的库和范式,最有用的就是能够将不同的数据源合并为一个,并将其作为统一的 API 提供。这样,前端应用程序的开发人员只需一个请求就可以查询所需的数据。
如今,将单体架构拆分成微服务变得越来越普遍,这样就可以获得许多独立运行的小型 API。GraphQL 和微服务是两种能够完美结合的范例。你想知道吗?GraphQL 不仅擅长描述架构,还能将不同的 API 拼接在一起,最终对于构建应用程序的人来说非常有用,因为数据查询将变得非常简单。
当我们拥有微服务架构时,我们拥有的正是不同的 API。在此基础上使用 GraphQL 意味着我们可以从所选的架构中获益,同时应用程序也可以获取其所需的数据。
你怎么会好奇呢?继续阅读本文,你就会明白具体方法。打开你的代码编辑器,和我一起构建吧 :)
计划
好的,那么我们在构建什么?以电子商务公司为目标总是令人欣慰的,因为它包含许多有趣的问题供我们解决。我们将特别关注两个主题,即产品和评论。当谈到产品时,我们需要一种方法来跟踪我们销售的产品及其所有元数据。对于评论,我们需要一种方法让我们的客户能够评论我们的产品,给它一个grade
,一个comment
等等。这两个概念可以被看作是两个可以独立维护和开发的孤岛。例如,如果一个产品有了新的描述,那没有理由影响该产品的评论。
好的,我们将这两个概念转化为product service
和review service
。
服务的数据结构
这些服务的数据是什么样的?它们还处于起步阶段,所以我们假设product service
目前的产品列表如下:
[{
id: 1,
name: 'Avengers - infinity war',
description: 'a Blue-ray movie'
}]
评论服务还会将数据保存在如下列表中:
[{
id: 2,
title: 'Oh snap what an ending',
grade: 5,
comment: 'I need therapy after this...',
product: 1
}]
从上面的数据描述中可以看出,评论服务保存了对产品服务中产品的引用,通过查询产品服务,您可以全面了解所涉及的评论和产品。
Docker化
好的,我们了解了服务在模式方面需要提供什么。这些服务还需要容器化,因此我们将描述如何使用 Docker Compose 构建Docker
它们Dockerfile
。
GraphQL
因此,GraphQL API 充当了高级 API,能够组合我们product service
以及的结果review service
。它的架构应该如下所示:
type Product {
id: ID,
name: String,
description: String
}
type Review {
id: ID,
title: String,
grade: Int,
comment: String,
product: Product
}
type Query {
products: [Product]
reviews: [Review]
}
我们假设,当 GraphQL API 用户查询“评论”时,他们不仅希望查看评论本身,还希望查看产品的一些额外数据,例如产品名称、产品类型等等。因此,我们在上面的架构中product
为Review
类型添加了属性,以便在深入查询时,能够同时获取评论和产品信息。
无服务器
那么,无服务器究竟有何用处呢?我们需要一种托管 API 的方式。我们可以使用应用服务 (App Service),但由于我们的 GraphQL API 无需保存任何状态,而且它只执行计算(组装结果),因此将其设计为轻量级的按需 Azure Function 更为合理。这就是我们要做的 :) 正如开头所述,我们将把这部分内容留到本系列的第二部分,我们不想用一篇冗长的文章让您感到厌烦 :)
创建并 Docker 化我们的微服务
我们选择使这些服务尽可能简单,因此我们使用 Node.js 和 Express 创建 REST API,如下所示:
/products
app.js
Dockerfile
package.json
/reviews
app.js
Dockerfile
package.json
该app.js
文件/products
如下所示:
// products/app.js
const express = require('express')
const app = express()
const port = process.env.PORT || 3000
app.get('/', (req, res) => res.json([{
id: "1",
name: 'Avengers - infinity war',
description: 'a Blue ray movie'
}]))
app.listen(port, () => console.log(`Example app listening on port port!`))
并且app.js
看起来/reviews
像这样:
// reviews.app.js
const express = require('express')
const app = express()
const port = process.env.PORT || 3000
app.get('/', (req, res) => res.json([{
id: "2",
title: 'Oh snap what an ending',
grade: 5,
comment: 'I need therapy after this...',
product: 1
}]))
app.listen(port, () => console.log(`Example app listening on port port!`))
看起来差不多吧?好吧,我们现在尽量保持简单,返回静态数据,但以后添加数据库也很简单。
Docker化
在开始 Dockerizing 之前,我们需要Express
像这样安装依赖项:
npm install express
每项服务都需要这样做。
好的,我们向您展示了每个服务的目录中都有一个Dockerfile
。它看起来像这样:
// Dockerfile
FROM node:latest
WORKDIR /app
ENV PORT=3000
COPY . .
RUN npm install
EXPOSE $PORT
ENTRYPOINT ["npm", "start"]
让我们更上一层楼,创建一个docker-compose.yaml
文件,这样创建镜像和容器会更容易。你的文件系统现在应该如下所示:
docker.compose.yaml
/products
app.js
Dockerfile
package.json
/reviews
app.js
Dockerfile
package.json
您的docker-compose.yaml
内容应该如下:
version: '3.3'
services:
product-service:
build:
context: ./products
ports:
- "8000:3000"
networks:
- microservices
review-service:
build:
context: ./reviews
ports:
- "8001:3000"
networks:
- microservices
networks:
microservices:
现在我们可以启动并运行我们的服务了
docker-compose up -d
当我运行此命令时,我总感觉自己正在启动喷气发动机,因为我的所有容器都同时启动,所以点火吧:)
您应该能够在 找到产品服务,http://localhost:8000
并在 找到评论服务http://localhost:8001
。目前的微服务介绍到此为止,接下来让我们构建 GraphQL API。
实现 GraphQL
构建 GraphQL 服务器的方法有很多,我们可以使用原生的graphql
NPM 库,或者使用 GraphQL 库express-graphql
,这会将我们的服务器托管在 Node.js Express 服务器中。或者,我们也可以使用 Apollo 的库等等。我们选择第一种方法graphql
,因为我们最终将通过无服务器函数来提供服务。
那么我们需要做什么:
- 定义架构
- 定义可用于解析模式不同部分的服务
- 试用 API
定义架构
现在,这很有趣,我们在这里有两个定义模式的选项:要么使用辅助函数buildSchema()
,要么使用原始方法,并使用原语构建我们的模式。在本例中,我们将使用原始方法,原因是尽管我通读了两遍手册,但我就是找不到如何使用原始方法进行深度解析buildSchema()
。奇怪的是,如果我们使用原始方法,这很容易做到express-graphql
,所以Apollo
如果您感觉有点眼花缭乱,我很抱歉 ;)
好的,让我们首先定义我们的模式:
// schema.js
const {
GraphQLSchema,
GraphQLObjectType,
GraphQLInt,
GraphQLNonNull,
GraphQLList,
GraphQLString
} = require('graphql');
const {
getProducts,
getReviews,
getProduct
} = require('./services');
const reviewType = new GraphQLObjectType({
name: 'Review',
description: 'A review',
fields: () => ({
id: {
type: GraphQLNonNull(GraphQLString),
description: 'The id of Review.',
},
title: {
type: GraphQLString,
description: 'The title of the Review.',
},
comment: {
type: GraphQLString,
description: 'The comment of the Review.',
},
grade : {
type: GraphQLInt
},
product: {
type: productType,
description: 'The product of the Review.',
resolve: (review) => getProduct(review.product)
}
})
})
const productType = new GraphQLObjectType({
name: 'Product',
description: 'A product',
fields: () => ({
id: {
type: GraphQLNonNull(GraphQLString),
description: 'The id of Product.',
},
name: {
type: GraphQLString,
description: 'The name of the Product.',
},
description: {
type: GraphQLString,
description: 'The description of the Product.',
}
})
});
const queryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
hello: {
type: GraphQLString,
resolve: (root) => 'world'
},
products: {
type: new GraphQLList(productType),
resolve: (root) => getProducts(),
},
reviews: {
type: new GraphQLList(reviewType),
resolve: (root) => getReviews(),
}
}),
});
module.exports = new GraphQLSchema({
query: queryType,
types: [reviewType, productType],
});
上面我们定义了两种类型Review
和,Product
并且我们公开了两个查询字段products
和reviews
。
我希望你特别注意变量reviewType
以及我们如何解析该product
字段。我们像这样解析它:
resolve: (review) => getProduct(review.product)
我们为什么要这么做?嗯,这与评论中数据的存储方式有关。让我们重新回顾一下。评论的数据存储方式如下:
{
title: ''
comment: '',
grade: 5
product: 1
}
如上所示,该product
字段是一个整数。它是指向产品服务中真实产品的外键。因此,我们需要解析它,以便 API 可以像这样进行查询:
{
reviews {
product {
name
}
}
}
如果我们不解析product
产品对象,则上述查询将会出错。
创建服务
在我们的中,schema.js
我们调用了诸如getProducts()
、getReviews()
和getProduct()
之类的方法,我们需要它们存在,所以我们创建一个文件services.js
,如下所示:
const fetch = require('node-fetch');
const getProducts = async() => {
const res = await fetch(process.env.PRODUCTS_URL)
const json = res.json();
return json;
}
const getProduct = async(product) => {
const products = await getProducts()
return products.find(p => p.id == product);
}
const getReviews = async() => {
const res = await fetch(process.env.REVIEW_URL)
const json = res.json();
return json;
}
module.exports = {
getProducts,
getReviews,
getProduct
}
好的,我们可以在上面看到方法getProducts()
和getReviews()
向 URL 发出 HTTP 请求,至少从名称process.env.PRODUCTS_URL
和来看是这样process.env.REVIEW_URL
。目前,我们创建了一个.env
文件,在其中创建了这两个环境变量,如下所示:
PRODUCTS_URL = http://localhost:8000
REVIEW_URL = http://localhost:8001
等等,是不是?没错,就是这个。它就是我们之前用来product service
启动微服务时的 URL。这不仅能让你本地测试微服务架构,还能为部署到云端做好准备。部署到云端几乎和将这些环境变量切换到云端一样简单,你将在本系列文章的下一篇中看到这一点 :)review service
docker-compose
尝试我们的 GraphQL API
好的,我们需要测试一下代码。为此,我们创建一个app.js
文件,在其中调用该graphql()
函数,并为其提供我们的模式和查询,如下所示:
const { graphql } = require('graphql');
const rawSchema = require('./raw-schema');
require('dotenv').config()
const query = `{ hello products { name, description } reviews { title, comment, grade, product { name, description } } }`;
graphql({
schema: rawSchema,
source: query
}).then(result => {
console.log('result', result);
console.log('reviews', result.data.reviews);
})
在上面的代码中,我们指定了一个查询,并期望返回字段hello
、products
和,最后我们在回调中调用该查询来提供结果。结果应该如下所示:reviews
graphql()
then()
概括
我们踏上了一段最终将引领我们走向云端的旅程。我们尚未到达那里,但第二部分将带我们一路前行。在第一部分中,我们成功创建了微服务并将其 Docker 化。此外,我们还构建了一个 GraphQL API,它能够定义一个模式,将我们的两个 API 合并在一起并提供相应的服务。
剩下的工作,也就是第二部分的任务,就是将容器推送到云端并创建服务端点。有了服务端点后,我们就可以替换环境变量的值,使用云端 URL,而不是我们现在使用的 localhost URL。
最后,我们需要搭建一个无服务器功能,但这一切都将在下一部分中进行,所以我希望你期待这一点:)
文章来源:https://dev.to/azure/learn-how-you-can-build-a-serverless-graphql-api-on-top-of-a-microservice-architecture-233g