了解如何在微服务架构上构建无服务器 GraphQL API,第一部分

2025-05-24

了解如何在微服务架构上构建无服务器 GraphQL API,第一部分

在Twitter上关注我,很高兴接受您对主题或改进的建议/Chris

本文的目的是展示如何构建微服务,将它们 docker 化,并将它们组合到 GraphQL API 中,然后通过无服务器函数进行查询,这么多流行语合在一起怎么样?;)微服务​​、DockerGraphQL无服务器

这是系列文章的一部分:

因此,在一篇文章中实现微服务、无服务器架构并部署到云端是一个相当雄心勃勃的目标,所以这篇文章分为两部分。这是第一部分,主要介绍微服务和 GraphQL。在第二部分中,我们将实现无服务器架构并进行部署。

在本文中,我们将介绍:

  • GraphQL 和微服务,这两个伟大的范例确实能够很好地结合在一起,让我们来解释一下
  • 构建微服务并进行 Docker 化,让我们创建两个微服务,每个服务都有各自的职责范围,然后对它们进行 Docker 化
  • 实现 GraphQl,我们将展示如何使用模式和解析器定义 GraphQL 后端以及如何查询它,当然,我们还将在这里绑定我们的微服务

资源

我们将首先向您介绍如何使用DockerGraphQL以及一些ServerlessAzure 函数。本文更多的是向您介绍如何使用上述技术,因此,如果您觉得需要了解上述内容的入门知识,以下是我撰写的文章列表:

 GraphQL 和微服务

像 GraphQL 这样的库和范式,最有用的就是能够将不同的数据源合并为一个,并将其作为统一的 API 提供。这样,前端应用程序的开发人员只需一个请求就可以查询所需的数据。

如今,将单体架构拆分成微服务变得越来越普遍,这样就可以获得许多独立运行的小型 API。GraphQL 和微服务是两种能够完美结合的范例。你想知道吗?GraphQL 不仅擅长描述架构,还能将不同的 API 拼接在一起,最终对于构建应用程序的人来说非常有用,因为数据查询将变得非常简单。

当我们拥有微服务架构时,我们拥有的正是不同的 API。在此基础上使用 GraphQL 意味着我们可以从所选的架构中获益,同时应用程序也可以获取其所需的数据。

你怎么会好奇呢?继续阅读本文,你就会明白具体方法。打开你的代码编辑器,和我一起构建吧 :)

 计划

好的,那么我们在构建什么?以电子商务公司为目标总是令人欣慰的,因为它包含许多有趣的问题供我们解决。我们将特别关注两个主题,即产品评论。当谈到产品时,我们需要一种方法来跟踪我们销售的产品及其所有元数据。对于评论,我们需要一种方法让我们的客户能够评论我们的产品,给它一个grade,一个comment等等。这两个概念可以被看作是两个可以独立维护和开发的孤岛。例如,如果一个产品有了新的描述,那没有理由影响该产品的评论。

好的,我们将这两个概念转化为product servicereview service

服务的数据结构

这些服务的数据是什么样的?它们还处于起步阶段,所以我们假设product service目前的产品列表如下:



[{
  id: 1,
  name: 'Avengers - infinity war',
  description: 'a Blue-ray movie'
}]


Enter fullscreen mode Exit fullscreen mode

评论服务还会将数据保存在如下列表中:



[{
  id: 2,
  title: 'Oh snap what an ending',
  grade: 5,
  comment: 'I need therapy after this...',
  product: 1
}]


Enter fullscreen mode Exit fullscreen mode

从上面的数据描述中可以看出,评论服务保存了对产品服务中产品的引用,通过查询产品服务,您可以全面了解涉及的评论和产品。

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


Enter fullscreen mode Exit fullscreen mode

我们假设,当 GraphQL API 用户查询“评论”时,他们不仅希望查看评论本身,还希望查看产品的一些额外数据,例如产品名称、产品类型等等。因此,我们在上面的架构中productReview类型添加了属性,以便在深入查询时,能够同时获取评论和产品信息。

 无服务器

那么,无服务器究竟有何用处呢?我们需要一种托管 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


Enter fullscreen mode Exit fullscreen mode

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!`))


Enter fullscreen mode Exit fullscreen mode

并且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!`))


Enter fullscreen mode Exit fullscreen mode

看起来差不多吧?好吧,我们现在尽量保持简单,返回静态数据,但以后添加数据库也很简单。

Docker化

在开始 Dockerizing 之前,我们需要Express像这样安装依赖项:



npm install express


Enter fullscreen mode Exit fullscreen mode

每项服务都需要这样做。

好的,我们向您展示了每个服务的目录中都有一个Dockerfile。它看起来像这样:



// Dockerfile

FROM node:latest
WORKDIR /app
ENV PORT=3000
COPY . .
RUN npm install
EXPOSE $PORT
ENTRYPOINT ["npm", "start"]


Enter fullscreen mode Exit fullscreen mode

让我们更上一层楼,创建一个docker-compose.yaml文件,这样创建镜像和容器会更容易。你的文件系统现在应该如下所示:



docker.compose.yaml
/products
  app.js
  Dockerfile
  package.json
/reviews
  app.js
  Dockerfile
  package.json


Enter fullscreen mode Exit fullscreen mode

您的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:


Enter fullscreen mode Exit fullscreen mode

现在我们可以启动并运行我们的服务了



docker-compose up -d


Enter fullscreen mode Exit fullscreen mode

当我运行此命令时,我总感觉自己正在启动喷气发动机,因为我的所有容器都同时启动,所以点火吧:)

您应该能够在 找到产品服务,http://localhost:8000并在 找到评论服务http://localhost:8001。目前的微服务介绍到此为止,接下来让我们构建 GraphQL API。

您的产品服务应如下所示:

您的审核服务应如下所示:

 实现 GraphQL

构建 GraphQL 服务器的方法有很多,我们可以使用原生的graphqlNPM 库,或者使用 GraphQL 库express-graphql,这会将我们的服务器托管在 Node.js Express 服务器中。或者,我们也可以使用 Apollo 的库等等。我们选择第一种方法graphql,因为我们最终将通过无服务器函数来提供服务。

那么我们需要做什么:

  1. 定义架构
  2. 定义可用于解析模式不同部分的服务
  3. 试用 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],
});


Enter fullscreen mode Exit fullscreen mode

上面我们定义了两种类型Review和,Product并且我们公开了两个查询字段productsreviews

我希望你特别注意变量reviewType以及我们如何解析该product字段。我们像这样解析它:



resolve: (review) => getProduct(review.product) 


Enter fullscreen mode Exit fullscreen mode

我们为什么要这么做?嗯,这与评论中数据的存储方式有关。让我们重新回顾一下。评论的数据存储方式如下:



{
  title: ''
  comment: '',
  grade: 5
  product: 1
}


Enter fullscreen mode Exit fullscreen mode

如上所示,该product字段是一个整数。它是指向产品服务中真实产品的外键。因此,我们需要解析它,以便 API 可以像这样进行查询:



{
  reviews {
    product { 
      name
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

如果我们不解析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
}


Enter fullscreen mode Exit fullscreen mode

好的,我们可以在上面看到方法getProducts()getReviews()向 URL 发出 HTTP 请求,至少从名称process.env.PRODUCTS_URL和来看是这样process.env.REVIEW_URL。目前,我们创建了一个.env文件,在其中创建了这两个环境变量,如下所示:



PRODUCTS_URL = http://localhost:8000
REVIEW_URL = http://localhost:8001


Enter fullscreen mode Exit fullscreen mode

等等,是不是?没错,就是这个。它就是我们之前用来product service启动微服务时的 URL。这不仅能让你本地测试微服务架构,还能为部署到云端做好准备。部署到云端几乎和将这些环境变量切换到云端一样简单,你将在本系列文章的下一篇中看到这一点 :)review servicedocker-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);
})


Enter fullscreen mode Exit fullscreen mode

在上面的代码中,我们指定了一个查询,并期望返回字段helloproducts和,最后我们在回调中调用该查询来提供结果。结果应该如下所示:reviewsgraphql()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
PREV
了解如何开始使用 Docker 和 Kubernetes
NEXT
学习 Kubernetes,第一部分,基础知识、部署和 Minikube