跳到主要内容

5 篇博文 含有标签「GraphQL」

客户端接口查询语言

查看所有标签

GraphQL Mesh v1 × Hive:从 gRPC 到联邦网关的全链路实战

· 阅读需 37 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

严格基于 GraphQL Mesh v1 官方文档,使用 bun 包管理器,配合 Mermaid 流程图,带你从零走通 gRPC 接入、协议转换、请求头管理、权限控制、子图联邦到 Hive CLI 推送的完整链路。


Mesh Compose 是什么?

Mesh Compose 是 GraphQL Mesh v1 的核心工具,它的能力一句话概括:把任何数据源(gRPC、REST、GraphQL、SOAP 等)组合成一个 Supergraph,交给 Hive Gateway 或其他网关运行。

与 v0 的关键区别:v1 使用 mesh.config.ts(TypeScript 配置)+ @graphql-mesh/compose-cli(Compose CLI),替代了 v0 的 .meshrc.yaml + @graphql-mesh/cli


第一步:环境初始化

mkdir mesh-grpc-demo && cd mesh-grpc-demo
bun init -y

# 安装 Compose CLI(v1 核心)
bun add @graphql-mesh/compose-cli

# 安装 gRPC Handler(v1 中包名改为 @omnigraph/grpc)
bun add @omnigraph/grpc

v1 vs v0 包名变化:gRPC Handler 从 @graphql-mesh/grpc 改为 @omnigraph/grpc,配置方式从 YAML 声明式改为 TypeScript 函数式。


第二步:接入 gRPC 服务

2.1 基础配置:使用 Proto 文件

假设你有一个 gRPC 服务运行在 localhost:50051,Proto 文件如下:

// proto/example.proto
syntax = "proto3";

package example;

service ExampleService {
rpc GetUser (GetUserRequest) returns (User) {}
rpc ListUsers (Empty) returns (UserList) {}
rpc CreateUser (CreateUserRequest) returns (User) {}
}

message GetUserRequest {
string id = 1;
}

message CreateUserRequest {
string name = 1;
string email = 2;
}

message Empty {}

message User {
string id = 1;
string name = 2;
string email = 3;
}

message UserList {
repeated User users = 1;
}

创建 mesh.config.ts

import loadGrpcSubgraph from '@omnigraph/grpc'
import { defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadGrpcSubgraph('MyGrpcApi', {
// gRPC 服务地址
endpoint: 'localhost:50051',
// Proto 文件路径
source: 'proto/example.proto',
})
}
]
})

生成 Supergraph:

bunx mesh-compose > supergraph.graphql

2.2 使用 gRPC Reflection(无需 Proto 文件)

如果你的 gRPC 服务开启了 Server Reflection,Mesh 可以自动发现服务定义,无需提供 source

import loadGrpcSubgraph from '@omnigraph/grpc'
import { defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadGrpcSubgraph('MyGrpcApi', {
endpoint: 'localhost:50051'
// 无需 source —— Mesh 通过 Reflection 自动获取
})
}
]
})

2.3 Proto 文件高级加载选项

当 Proto 文件有 import 依赖时,需要指定 includeDirs

sourceHandler: loadGrpcSubgraph('MyGrpcApi', {
endpoint: 'localhost:50051',
source: {
file: 'proto/example.proto',
load: {
defaults: true, // 加载默认的 google protobuf
includeDirs: ['proto', 'vendor'] // Proto 搜索路径
}
}
})

2.4 SSL/TLS 安全连接

生产环境中 gRPC 通常使用 TLS 加密:

sourceHandler: loadGrpcSubgraph('MyGrpcApi', {
endpoint: 'grpc.example.com:443',
source: 'proto/example.proto',
useHTTPS: true,
credentialsSsl: {
rootCA: 'certs/rootCA.pem',
certChain: 'certs/client-cert.pem',
privateKey: 'certs/client-key.pem',
}
})

2.5 请求超时控制

sourceHandler: loadGrpcSubgraph('MyGrpcApi', {
endpoint: 'localhost:50051',
source: 'proto/example.proto',
requestTimeout: 200_000, // 毫秒,默认 200000
})

第三步:控制 Query 与 Mutation 分类

Mesh 默认将以 listget 开头的 gRPC 方法映射为 Query,其余映射为 Mutation

3.1 使用 prefixQueryMethod 自定义前缀

sourceHandler: loadGrpcSubgraph('MyGrpcApi', {
endpoint: 'localhost:50051',
source: 'proto/example.proto',
prefixQueryMethod: ['list', 'get', 'find', 'search'],
// findUser → Query, searchProducts → Query
// createUser → Mutation, deleteOrder → Mutation
})

3.2 使用 selectQueryOrMutationField 精确控制

selectQueryOrMutationField覆盖 prefixQueryMethod 的默认行为:

sourceHandler: loadGrpcSubgraph('MyGrpcApi', {
endpoint: 'localhost:50051',
source: 'proto/example.proto',
selectQueryOrMutationField: [
{
// 支持通配符 * 模式匹配
fieldName: '*RetrieveMovies',
type: 'Query',
},
{
// 精确匹配:将 GetMovie 从默认 Query 改为 Mutation
fieldName: 'GetMovie',
type: 'Mutation',
}
]
})

第四步:请求头与认证传递

4.1 schemaHeaders —— Schema 内省时的请求头

当你的 Proto 文件通过远程 URL 获取时,schemaHeaders 用于 构建时 的认证:

sourceHandler: loadGrpcSubgraph('MyGrpcApi', {
endpoint: 'localhost:50051',
source: 'proto/example.proto',
schemaHeaders: {
'x-api-key': 'my-api-key'
}
})

4.2 metaData —— gRPC 调用时的 Metadata 传递

metaData 用于在 运行时 将认证信息传递给 gRPC 服务。这是最常用的认证方式:

sourceHandler: loadGrpcSubgraph('MyGrpcApi', {
endpoint: 'localhost:50051',
source: 'proto/example.proto',
metaData: {
// 从客户端请求头动态提取 Token,传递给 gRPC Metadata
authorization: "Bearer {context.headers['x-my-token']}",
// 也可以设置静态值
someStaticValue: 'MyStaticValue',
}
})

4.3 operationHeaders —— HTTP 类 Handler 的运行时请求头

注意operationHeadersschemaHeaders 是 HTTP 类 Handler(如 OpenAPI、GraphQL)的配置。gRPC Handler 使用 metaData

对于 OpenAPI / GraphQL 子图,请求头配置如下:

import { loadOpenApiSubgraph } from '@graphql-mesh/compose-cli'

sourceHandler: loadOpenApiSubgraph('MyRestApi', {
source: './openapi.yaml',
endpoint: 'https://api.example.com/v1',
// 运行时请求头(执行 API 调用时)
operationHeaders: {
// 从 Context 动态获取
Authorization: 'Bearer {context.headers["x-my-api-token"]}',
// 从环境变量获取
'X-Api-Key': '{env.API_KEY}',
// 静态值
'Content-Type': 'application/json',
},
// Schema 内省请求头(构建时)
schemaHeaders: {
'X-Schema-Token': '{env.SCHEMA_TOKEN}',
},
})

第五步:权限控制与安全

Mesh v1 提供了多层权限控制机制,从 Schema 级别的指令到网关级别的插件。

5.1 @authenticated —— 认证指令

在 Supergraph Schema 中使用 @authenticated 指令标记需要认证的字段:

extend schema
@link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@authenticated"])

type Query {
me: User! @authenticated
protectedField: String @authenticated
publicField: String
# publicField 无需认证即可访问
}

5.2 @skipAuth —— 跳过认证

当整个 Supergraph 默认需要认证时,可以用 @skipAuth 标记特定公开字段:

# 定义 @skipAuth 指令
extend schema
@link(url: "https://specs.apollo.dev/link/v1.0")
@link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@composeDirective"])
@link(url: "https://the-guild.dev/graphql/mesh/spec/v1.0", import: ["@skipAuth"])
@composeDirective(name: "@skipAuth")

directive @skipAuth on FIELD_DEFINITION | OBJECT | INTERFACE

type Query {
me: User!
protectedField: String
publicField: String @skipAuth # 即使全局认证开启,此字段也无需认证
}

前提:需要在 Hive Gateway 中配置 Generic Auth 插件并启用 Complete Protection

5.3 @requiresScopes —— 细粒度权限

extend schema
@link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@authenticated", "@requiresScopes"])

type Query {
adminDashboard: Dashboard @authenticated @requiresScopes(scopes: ["admin:read"])
userProfile: User @authenticated @requiresScopes(scopes: ["user:read"])
}

5.4 @policy —— 策略化授权

extend schema
@link(url: "https://specs.apollo.dev/federation/v2.6", import: ["@policy"])

type Mutation {
deleteAccount: Boolean @policy(policy: "adminOnly")
}

5.5 @rateLimit —— 限流保护

在 Schema 中声明限流规则,由 Gateway 执行:

directive @rateLimit(
limit: Int!
duration: Int!
) on FIELD_DEFINITION

type Query {
frequentOperation: String @rateLimit(limit: 100, duration: 60)
}

第六步:Schema Transform(转换)

Transform 允许你在生成 Supergraph 之前对子图 Schema 进行修改。

6.1 Prefix Transform —— 添加前缀

避免多个子图中的同名类型冲突:

import {
createPrefixTransform,
defineConfig,
loadGraphQLHTTPSubgraph
} from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadGraphQLHTTPSubgraph('Countries', {
endpoint: 'https://countries.trevorblades.com'
}),
transforms: [
createPrefixTransform({
value: 'Countries_' // 所有类型和字段添加 Countries_ 前缀
})
]
}
]
})

6.2 Rename Transform —— 重命名

import {
createRenameTransform,
defineConfig,
loadOpenApiSubgraph
} from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadOpenApiSubgraph('MyApi', {
source: './openapi.yaml',
}),
transforms: [
createRenameTransform({
renames: [
{ from: { type: 'User', field: 'lastName' }, to: { type: 'User', field: 'surname' } },
{ from: { type: 'BookCategory' }, to: { type: 'Category' } },
]
})
]
}
]
})

6.3 Filter Schema Transform —— 过滤字段

只暴露需要的字段,隐藏内部接口:

import {
createFilterSchemaTransform,
defineConfig,
loadOpenApiSubgraph
} from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadOpenApiSubgraph('InternalApi', {
source: './internal-api.yaml',
}),
transforms: [
createFilterSchemaTransform({
filters: [
'Query.getUser',
'Query.listProducts',
'Mutation.createUser',
]
})
]
}
]
})

6.4 Federation Transform —— 添加联邦注解

将非联邦的 GraphQL 服务转换为联邦子图:

import {
createFederationTransform,
defineConfig,
loadGraphQLHTTPSubgraph
} from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadGraphQLHTTPSubgraph('myApi', {
endpoint: 'http://localhost:4001/my-api'
}),
transforms: [
createFederationTransform({
// 为 Product 类型添加 @key 指令
Product: {
key: {
fields: 'id',
resolveReference: {
fieldName: 'productById' // 通过此查询解析引用
}
},
},
// 标记 Query 为 extend 类型
Query: {
extends: true
}
})
]
}
]
})

第七步:Type Merging(类型合并)

Type Merging 是 Mesh v1 中跨子图组合实体的首选方式。当多个子图定义了同名类型时,Mesh 会自动将它们合并。

7.1 自动合并

Mesh 会自动检测以下模式并启用 Type Merging:

  • 多个子图定义了相同的类型
  • 存在 TypeNameById(id: ID!): TypeName 模式的查询字段

7.2 手动合并

当 Schema 不符合自动模式时,使用 Federation Transform 手动配置:

createFederationTransform({
User: {
key: { fields: 'id' },
resolveReference: { fieldName: 'userById' }
}
})

第八步:生成 Supergraph 与子图

8.1 生成 Supergraph

# 生成完整的超级图
bunx mesh-compose > supergraph.graphql

8.2 生成单个子图(用于 Schema Registry)

# 生成指定子图(用于推送到 Hive Registry)
bunx mesh-compose --subgraph MyGrpcApi > my-grpc-subgraph.graphql

第九步:Hive CLI 推送

9.1 安装 Hive CLI

# 使用 bun 安装
bun add -d @graphql-hive/cli

# 验证
bunx hive --version

9.2 创建 Access Token

  1. 登录 Hive Console
  2. 进入组织 → SettingsAccess Token
  3. 点击 Create new access token
  4. 勾选权限:✅ Check schema / ✅ Publish schema / ✅ Report usage data
  5. 选择 Full Access → 创建 → 复制 Token

9.3 推送子图到 Hive

bunx hive schema:publish \
--registry.accessToken "YOUR_TOKEN" \
--target "myorg/myproject/development" \
--service "MyGrpcApi" \
--url "http://localhost:50051" \
--author "YourName" \
--commit "feat: initial gRPC subgraph" \
my-grpc-subgraph.graphql

9.4 Target 地址格式

<组织slug>/<项目slug>/<环境>

示例:
myorg/myproject/development ← 开发环境
myorg/myproject/staging ← 预发布
myorg/myproject/production ← 生产

9.5 Schema 检查(CI/CD)

bunx hive schema:check \
--registry.accessToken "YOUR_TOKEN" \
--target "myorg/myproject/production" \
--service "MyGrpcApi" \
--url "http://localhost:50051" \
my-grpc-subgraph.graphql

第十步:Hive Gateway 运行

10.1 安装

bun add @graphql-hive/gateway

10.2 从 Hive CDN 启动

# 获取 CDN Token:Hive Console → Target Settings → CDN Tokens → Create

bunx hive-gateway supergraph \
"https://cdn.graphql-hive.com/artifacts/v1/<project-id>/supergraph" \
--hive-cdn-key "YOUR_CDN_KEY"

10.3 使用配置文件

创建 gateway.config.ts

import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
supergraph: {
type: 'hive',
endpoint: 'https://cdn.graphql-hive.com/artifacts/v1/<project-id>/supergraph',
key: 'YOUR_CDN_KEY'
}
})

然后直接运行:

bunx hive-gateway supergraph

10.4 本地开发 Supergraph

# 使用本地生成的 supergraph.graphql
bunx hive-gateway supergraph ./supergraph.graphql

端到端完整示例

以下是一个将 gRPC 服务接入 Mesh Compose 并推送到 Hive 的完整流程:

完整 mesh.config.ts

import loadGrpcSubgraph from '@omnigraph/grpc'
import { loadGraphQLHTTPSubgraph } from '@graphql-mesh/compose-cli'
import {
createFederationTransform,
createPrefixTransform,
defineConfig
} from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
// ─── 子图 1:gRPC 服务 ───────────────────────
{
sourceHandler: loadGrpcSubgraph('InventoryService', {
endpoint: 'localhost:50051',
source: {
file: 'proto/inventory.proto',
load: {
defaults: true,
includeDirs: ['proto']
}
},
requestTimeout: 200_000,
useHTTPS: false,
metaData: {
authorization: "Bearer {context.headers['x-my-token']}",
},
prefixQueryMethod: ['list', 'get'],
selectQueryOrMutationField: [
{ fieldName: '*RetrieveMovies', type: 'Query' },
{ fieldName: 'GetMovie', type: 'Mutation' },
],
schemaHeaders: {
'x-api-key': '{env.SCHEMA_API_KEY}'
}
}),
transforms: [
createPrefixTransform({ value: 'Inventory_' }),
createFederationTransform({
Product: {
key: { fields: 'upc' },
resolveReference: { fieldName: 'getProduct' }
}
})
]
},

// ─── 子图 2:GraphQL 服务 ─────────────────────
{
sourceHandler: loadGraphQLHTTPSubgraph('Users', {
endpoint: 'http://localhost:4001/graphql',
operationHeaders: {
Authorization: 'Bearer {context.headers["authorization"]}',
},
}),
transforms: [
createFederationTransform({
User: {
key: { fields: 'id' },
resolveReference: { fieldName: 'user' }
}
})
]
}
]
})

一键执行脚本

#!/bin/bash
set -e

echo "📦 安装依赖..."
bun add @graphql-mesh/compose-cli @omnigraph/grpc @graphql-hive/gateway

echo "🔧 生成 Supergraph..."
bunx mesh-compose > supergraph.graphql

echo "📤 生成子图..."
bunx mesh-compose --subgraph InventoryService > inventory-subgraph.graphql

echo "🚀 推送到 Hive..."
bunx hive schema:publish \
--registry.accessToken "$HIVE_TOKEN" \
--target "myorg/myproject/development" \
--service "InventoryService" \
--url "http://localhost:50051" \
--author "$(git config user.name)" \
--commit "$(git rev-parse --short HEAD)" \
inventory-subgraph.graphql

echo "🌐 启动 Gateway..."
bunx hive-gateway supergraph ./supergraph.graphql

echo "✅ 完成!访问 http://localhost:4000/graphql"

第十一步:安全防护 —— 深度、别名、Token、字符限制

Hive Gateway 内置了多层安全防护,防止恶意 GraphQL 操作导致的服务过载。这些防护在 Gateway 层统一执行,对所有子图(无论原始协议是 gRPC、REST 还是 GraphQL)都生效。

11.1 Max Tokens —— Token 数量限制

防止攻击者发送超大量 Token 的文档导致解析阻塞和堆溢出。Gateway 内置支持,无需额外安装:

// gateway.config.ts
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
maxTokens: true // 启用,默认上限 1000 个 Token
})

需要更精细控制时,安装 GraphQL Armor 插件:

bun add @escape.tech/graphql-armor-max-tokens
import { maxTokensPlugin } from '@escape.tech/graphql-armor-max-tokens'
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
plugins: () => [
maxTokensPlugin({
enabled: true,
n: 5000, // 允许的最大 Token 数
propagateOnRejection: true, // 是否向客户端返回拒绝信息
allowList: [], // 白名单(跳过检查的查询)
onAccept: [], // 接受时的回调
onReject: [], // 拒绝时的回调
})
]
})

11.2 Max Depth —— 查询深度限制

防止攻击者发送深层嵌套的查询(如 author → posts → author → posts → ...):

bun add @escape.tech/graphql-armor-max-depth
import { maxDepthPlugin } from '@escape.tech/graphql-armor-max-depth'
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
plugins: () => [
maxDepthPlugin({
enabled: true,
n: 6, // 最大嵌套深度,默认 6
propagateOnRejection: true,
allowList: [],
})
]
})

11.3 Max Aliases —— 别名数量限制

攻击者可以通过大量别名绕过深度限制,因此需要单独限制:

bun add @escape.tech/graphql-armor-max-aliases
import { maxAliasesPlugin } from '@escape.tech/graphql-armor-max-aliases'
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
plugins: () => [
maxAliasesPlugin({
enabled: true,
maxAliases: 5, // 最大别名数,默认 5
propagateOnRejection: true,
allowList: [],
})
]
})

11.4 Character Limit —— 字符数限制

限制查询文档的字符总数,防止超大文档:

bun add @escape.tech/graphql-armor-character-limit
import { characterLimitPlugin } from '@escape.tech/graphql-armor-character-limit'
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
plugins: () => [
characterLimitPlugin({
maxLength: 15000 // 最大字符数,默认 15000
})
]
})

建议:官方推荐优先使用 Max Tokens 而非 Character Limit,因为 Token 限制更精确。

11.5 Rate Limiting —— 限流

支持 编程式配置Schema 指令 两种方式:

编程式

import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
rateLimiting: {
rules: [
{
type: 'Query',
field: 'foo',
max: 5, // 时间窗口内最大请求数
ttl: 5000, // 时间窗口(毫秒)
identifier: '{context.headers.authorization}' // 按用户限流
}
]
}
})

Schema 指令式(需在 mesh.config.ts 中声明 @rateLimit 指令):

// gateway.config.ts
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
rateLimiting: true // 启用指令式限流
})
# 在子图 Schema 中声明
type Query {
getItems: [Item] @rateLimit(window: "1s", max: 5, message: "请求过于频繁")
}

11.6 Request Batching —— 请求批处理

将多个 GraphQL 请求合并为一个 HTTP 请求,减少网络开销:

import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
batching: {
limit: 10 // 单次 HTTP 请求最多包含 10 个 GraphQL 操作,默认 10
}
})
# 发送批处理请求
curl -X POST -H 'Content-Type: application/json' http://localhost:4000/graphql \
-d '[{"query": "{ hee: __typename }"}, {"query": "{ ho: __typename }"}]'

11.7 其他安全特性

特性说明配置
禁用 Introspection防止 Schema 泄露插件 disableIntrospectionPlugin
阻止字段建议防止攻击者探测 Schema插件 blockFieldSuggestionsPlugin
错误掩码默认启用,隐藏内部错误详情内置
CORS默认启用跨域支持可配置
CSRF 防护防止跨站请求伪造插件
HMAC 签名Gateway → Subgraph 请求签名插件
持久化文档只允许白名单中的操作配置 persistedDocuments

第十二步:日志、监控与可观测性

12.1 健康检查

Hive Gateway 内置两个健康检查端点:

import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
healthCheckEndpoint: '/healthcheck', // 存活检查
readinessCheckEndpoint: '/readiness', // 就绪检查
})

12.2 OpenTelemetry 链路追踪

Hive Gateway 完整支持 OpenTelemetry,可追踪:

  • HTTP 请求/响应
  • GraphQL 生命周期(parse → validate → execute)
  • 上游子图调用
  • 上下文传播

安装依赖

bun add @opentelemetry/context-async-hooks @opentelemetry/exporter-trace-otlp-http

配置 telemetry.ts(必须在 gateway.config.ts 之前导入):

// telemetry.ts
import { openTelemetrySetup } from '@graphql-hive/gateway/opentelemetry/setup'
import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'

openTelemetrySetup({
contextManager: new AsyncLocalStorageContextManager(),
traces: {
exporter: new OTLPTraceExporter({ url: process.env['OTLP_URL'] }),
console: process.env['DEBUG_TRACES'] === '1' // 调试时输出到控制台
},
resource: {
serviceName: 'my-gateway',
serviceVersion: '1.0.0'
}
})

gateway.config.ts

import './telemetry' // 必须是第一个导入!
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
openTelemetry: {
traces: true
}
})

CLI 快捷方式

bunx hive-gateway supergraph ./supergraph.graphql \
--opentelemetry "http://localhost:4318"

12.3 Prometheus 指标

Hive Gateway 内置 Prometheus 指标导出:

import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
prometheus: {
enabled: true
}
})

访问 /metrics 端点即可获取 Prometheus 格式的指标数据,配合 Grafana 可视化。

12.4 Sentry 错误追踪

import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
sentry: {
dsn: 'YOUR_SENTRY_DSN'
}
})

12.5 StatsD 指标

import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
statsd: {
host: 'localhost',
port: 8125
}
})

第十三步:gRPC 子图的权限 —— @authenticated 等指令如何生效

这是一个关键问题:gRPC 服务本身不懂 GraphQL 指令,那 @authenticated 怎么对 gRPC 子图生效?

核心原理:权限指令不在子图层面执行,而是在 Gateway 层统一执行。

13.1 在 Supergraph Schema 中声明权限

mesh.config.ts 中通过 additionalTypeDefs 为来自 gRPC 的字段添加权限指令:

import loadGrpcSubgraph from '@omnigraph/grpc'
import { defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadGrpcSubgraph('InventoryService', {
endpoint: 'localhost:50051',
source: 'proto/inventory.proto',
metaData: {
authorization: "Bearer {context.headers['x-my-token']}",
},
}),
}
],
// 为 gRPC 生成的字段添加权限注解
additionalTypeDefs: /* GraphQL */ `
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.6",
import: ["@authenticated", "@requiresScopes"])

extend type Query {
listProducts: [Product] @authenticated @requiresScopes(scopes: ["inventory:read"])
getProduct: Product @authenticated
}

extend type Mutation {
createProduct: Product @authenticated @requiresScopes(scopes: ["inventory:write"])
}
`
})

13.2 执行流程

13.3 完整安全配置示例

将所有安全能力整合到一个 gateway.config.ts 中:

import './telemetry'
import { maxDepthPlugin } from '@escape.tech/graphql-armor-max-depth'
import { maxTokensPlugin } from '@escape.tech/graphql-armor-max-tokens'
import { maxAliasesPlugin } from '@escape.tech/graphql-armor-max-aliases'
import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
// ─── Supergraph 来源 ─────────────────────
supergraph: {
type: 'hive',
endpoint: 'https://cdn.graphql-hive.com/artifacts/v1/<id>/supergraph',
key: '<CDN_KEY>'
},

// ─── 安全防护 ───────────────────────────
maxTokens: true, // 内置 Token 限制(默认 1000)
plugins: () => [
maxDepthPlugin({ n: 7 }), // 查询深度限制
maxTokensPlugin({ n: 5000 }), // Token 限制(覆盖内置)
maxAliasesPlugin({ maxAliases: 5 }), // 别名限制
],

// ─── 限流 ───────────────────────────────
rateLimiting: {
rules: [
{
type: 'Query',
field: 'listProducts',
max: 100,
ttl: 60000,
identifier: '{context.headers.authorization}'
}
]
},

// ─── 请求批处理 ─────────────────────────
batching: { limit: 10 },

// ─── 健康检查 ───────────────────────────
healthCheckEndpoint: '/healthcheck',
readinessCheckEndpoint: '/readiness',

// ─── 可观测性 ───────────────────────────
openTelemetry: { traces: true },
prometheus: { enabled: true },
})

第十四步:Schema Extensions —— 跨子图类型关联

Schema Extensions 是在 Supergraph 层面 添加类型定义和解析器的能力,用于建立跨子图的类型关联。虽然 Type Merging 能自动处理很多场景,但 Schema Extensions 提供了更精细的控制。

14.1 基本用法:@resolveTo 委托查询

@resolveTo 指令将字段解析委托给另一个子图的根查询:

import { defineConfig } from '@graphql-mesh/compose-cli'
import { loadGraphQLHTTPSubgraph } from '@graphql-mesh/graphql'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadGraphQLHTTPSubgraph('Posts', {
endpoint: 'http://localhost:4001/posts'
})
},
{
sourceHandler: loadGraphQLHTTPSubgraph('Users', {
endpoint: 'http://localhost:4002/users'
})
}
],
additionalTypeDefs: /* GraphQL */ `
extend type Post {
user: User!
@resolveTo(
sourceName: "Users"
sourceTypeName: "Query"
sourceFieldName: "userById"
requiredSelectionSet: "{ id }"
sourceArgs: { id: "{root.userId}" }
)
}
extend type User {
posts: [Post!]!
@resolveTo(
sourceName: "Posts"
sourceTypeName: "Query"
sourceFieldName: "postsByUserId"
requiredSelectionSet: "{ id }"
sourceArgs: { userId: "{root.id}" }
)
}
`
})

@resolveTo 参数说明

参数说明
sourceName委托目标子图名称
sourceTypeName目标子图中的类型(通常是 Query
sourceFieldName目标子图中的查询字段
sourceArgs传递给查询的参数,{root.xxx} 引用父对象字段
requiredSelectionSet从父对象中必须获取的字段(用于构建参数)
keyField批量委托时的键字段
keysArg批量委托时的参数名
returnType返回类型(当委托操作返回类型与字段类型不同时)
result响应数据路径(当数据嵌套在响应中时)

14.2 批量委托(Array Batching)

单个 resolveTo 调用会产生 N+1 查询问题。批量委托将多个解析合并为一次请求:

additionalTypeDefs: /* GraphQL */ `
extend type Post {
user: User!
@resolveTo(
sourceName: "Users"
sourceTypeName: "Query"
sourceFieldName: "usersByIds"
keyField: "userId"
keysArg: "ids"
)
}
`

原理:内部使用 DataLoader 按 context、字段、参数和查询选择集进行批处理。

14.3 隐藏内部字段

批量委托通常需要子图暴露一个 usersByIds 查询,但你可能不想让它对外可见。使用 Filter Transform 隐藏:

import { createFilterTransform } from '@graphql-mesh/compose-cli'

// 在 Users 子图上应用
transforms: [
createFilterTransform({
fieldFilter: (typeName, fieldName) => fieldName !== 'usersByIds'
})
]

14.4 与 Hive Schema Registry 配合

如果你使用 Hive 管理 Supergraph(而非本地 Mesh Compose),可以在 Hive Console 的 Settings → Base Schema 中填写 additionalTypeDefs


第十五步:Response Caching —— 响应缓存

响应缓存是减少服务器负载的关键手段。Mesh v1 + Hive Gateway 提供了完整的缓存方案,支持 TTL、Session 缓存、Mutation 自动失效等。

15.1 在子图 Schema 中声明缓存指令

# 导入 @cacheControl 指令
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.1", import: ["@composeDirective"])
@link(url: "https://the-guild.dev/mesh/v1.0", import: ["@cacheControl"])
@composeDirective(name: "@cacheControl")

enum CacheControlScope {
PUBLIC
PRIVATE
}

directive @cacheControl(
maxAge: Int
scope: CacheControlScope
inheritMaxAge: Boolean
) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION

type Query {
# 缓存 10 秒
topProducts: [Product] @cacheControl(maxAge: 10000)
# 不缓存
currentTime: String @cacheControl(maxAge: 0)
# 仅认证用户缓存
me: User @cacheControl(maxAge: 500, scope: PRIVATE)
}

# 整个 Product 类型缓存 60 秒
type Product @cacheControl(maxAge: 60000) {
id: ID!
name: String!
}

15.2 Gateway 端启用缓存

import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
responseCaching: {
// 全局缓存(无 Session 隔离)
session: () => null,
}
})

15.3 Session 缓存(按用户隔离)

export const gatewayConfig = defineConfig({
responseCaching: {
// 基于认证头隔离缓存
session: request => request.headers.get('authorization'),
}
})

15.4 TTL 配置

export const gatewayConfig = defineConfig({
responseCaching: {
session: () => null,
// 全局默认 TTL
ttl: 2_000,
// 按 Schema 坐标设置不同 TTL
ttlPerSchemaCoordinate: {
'Query.topProducts': 60_000, // 60 秒
'Query.me': 500, // 0.5 秒
'User': 10_000, // User 类型相关查询 10 秒
'Query.currentTime': 0, // 禁止缓存
}
}
})

15.5 Mutation 自动失效

当执行 Mutation 时,包含相同实体的缓存查询会自动失效:

mutation UpdateProduct {
updateProduct(id: "1", name: "New Name") {
__typename
id
name
}
}

所有包含 Product(id: "1") 的缓存查询将自动失效。可通过 invalidateViaMutation: false 关闭。

15.6 自定义实体标识

默认基于 id 字段识别实体,可自定义:

export const gatewayConfig = defineConfig({
responseCaching: {
session: () => null,
idFields: ['id', 'email'] // 优先使用 id,其次 email
}
})

15.7 三种缓存模式对比

模式跳过 HTTP 调用?Mutation 自动失效?说明
Gateway Response Cache + @composeDirective推荐:Gateway 完全控制缓存
Apollo Server + HTTP Cache-Control子图设置 HTTP 头,Gateway 按头缓存
Yoga Server + ETag🟠 部分每次验证 ETag,返回 304

第十六步:更多 Transform 详解

16.1 Naming Convention —— 统一命名规范

import { createNamingConventionTransform, defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadOpenApiSubgraph('MyApi', {
source: './openapi.yaml',
}),
transforms: [
createNamingConventionTransform({
typeNames: 'pascalCase', // 类型名:PascalCase
fieldNames: 'camelCase', // 字段名:camelCase
enumValues: 'UPPER_CASE', // 枚举值:UPPER_CASE
})
]
}
]
})

16.2 Encapsulate —— 封装子图到单个字段

将整个子图 Schema 封装到一个命名空间字段下,避免多子图间的命名冲突:

import { createEncapsulateTransform, defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadGraphQLHTTPSubgraph('Users', {
endpoint: 'http://localhost:4001/graphql'
}),
transforms: [
createEncapsulateTransform({
applyTo: {
query: true,
mutation: false,
subscription: false
}
})
]
}
]
})

16.3 Hoist Field —— 字段提升

将嵌套字段提升到父类型,简化查询路径:

import { createHoistFieldTransform, defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadGraphQLHTTPSubgraph('Users', {
endpoint: 'http://localhost:4001/users'
}),
transforms: [
createHoistFieldTransform({
mapping: [
{
typeName: 'Query',
pathConfig: ['users', 'results'],
newFieldName: 'users'
}
]
})
]
}
]
})

过滤参数:提升时可以过滤掉不需要的参数:

createHoistFieldTransform({
mapping: [
{
typeName: 'Query',
pathConfig: [
{ fieldName: 'users', filterArgs: ['limit'] }, // 过滤掉 limit 参数
'results'
],
newFieldName: 'users'
}
]
})

16.4 Prune —— 移除未使用的类型

自动移除 Schema 中未被任何字段引用的类型和枚举值:

import { createPruneTransform, defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadOpenApiSubgraph('MyApi', {
source: './openapi.yaml',
}),
transforms: [
createPruneTransform({
// 可选:指定跳过的类型
skipPruning: ['DeprecatedType']
})
]
}
]
})

第十七步:Subscriptions & Webhooks

Mesh 支持将 Webhook 事件消费为 GraphQL Subscription,实现实时数据推送。

17.1 使用 additionalTypeDefs 添加 Subscription

import { defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
// ... 其他子图
],
additionalTypeDefs: /* GraphQL */ `
extend schema {
subscription: Subscription
}

type Subscription {
todoAdded: Todo
@resolveTo(
pubsubTopic: "webhook:post:/webhooks/todo_added"
# 可选:过滤
# filterBy: "root.userId === args.userId"
# 可选:嵌套数据路径
# result: "data.someProp.someOtherProp"
)
}
`
})

17.2 使用 JSON Schema Handler

如果你不想手写 GraphQL 类型,可以用 JSON Schema Handler 从示例 JSON 自动生成:

import { loadJSONSchemaSubgraph } from '@omnigraph/json-schema'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadJSONSchemaSubgraph('API', {
operations: [
{
type: 'Subscription' as any,
field: 'todoAdded',
pubsubTopic: 'webhook:post:/webhooks/todo_added',
responseSample: './todo.json',
responseTypeName: 'Todo'
}
]
})
}
]
})

17.3 Gateway 端启用 Webhooks

import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
webhooks: true // 启用 Webhook 接收
})

第十八步:更多 Source Handler 完整示例

18.1 OpenAPI / REST Handler

bun add @omnigraph/openapi
import { loadOpenApiSubgraph } from '@omnigraph/openapi'
import { defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadOpenApiSubgraph('PaymentService', {
source: './openapi.yaml',
// 或远程 URL
// source: 'https://api.example.com/openapi.json',
endpoint: 'https://api.example.com/v1',
operationHeaders: {
Authorization: 'Bearer {context.headers["authorization"]}',
'X-Request-Id': '{context.headers["x-request-id"]}',
},
schemaHeaders: {
'X-Schema-Token': '{env.SCHEMA_TOKEN}',
},
queryParams: {
api_key: '{env.API_KEY}',
},
ignoreErrorResponses: true,
})
}
]
})

18.2 GraphQL Handler

bun add @graphql-mesh/graphql
import { loadGraphQLHTTPSubgraph } from '@graphql-mesh/graphql'
import { defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadGraphQLHTTPSubgraph('Users', {
endpoint: 'http://localhost:4001/graphql',
operationHeaders: {
Authorization: 'Bearer {context.headers["authorization"]}',
},
schemaHeaders: {
'X-Internal-Token': '{env.INTERNAL_TOKEN}',
},
})
}
]
})

18.3 Supergraph Handler(消费预构建的 Supergraph)

bun add @omnigraph/federation-supergraph
import { loadSupergraphSubgraph } from '@omnigraph/federation-supergraph'
import { defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadSupergraphSubgraph('Supergraph', {
source: 'https://cdn.graphql-hive.com/artifacts/v1/<project-id>/supergraph',
schemaHeaders: {
'X-Hive-CDN-Key': 'your-cdn-key',
},
// 为每个子图配置独立的 endpoint 和请求头
subgraphs: [
{
name: 'accounts',
endpoint: 'http://localhost:9871/graphql',
operationHeaders: {
Authorization: "Bearer {context.headers['x-accounts-token']}",
}
},
{
name: 'reviews',
endpoint: '{env.REVIEWS_ENDPOINT:https://default-reviews.com/graphql}',
operationHeaders: {
Authorization: "Bearer {context.headers['x-reviews-token']}",
}
}
]
})
}
]
})

18.4 JSON Schema Handler

bun add @omnigraph/json-schema
import { loadJSONSchemaSubgraph } from '@omnigraph/json-schema'
import { defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadJSONSchemaSubgraph('MyApi', {
source: './schema.json',
endpoint: 'https://api.example.com',
operationHeaders: {
Authorization: 'Bearer {context.headers["authorization"]}',
},
})
}
]
})

18.5 SOAP Handler

bun add @omnigraph/soap
import { loadSoapSubgraph } from '@omnigraph/soap'
import { defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadSoapSubgraph('LegacyService', {
source: 'http://www.example.com/wsdl?WSDL',
operationHeaders: {
'SOAPAction': '{env.SOAP_ACTION}',
},
})
}
]
})

18.6 数据库 Handler

# SQLite
bun add @omnigraph/sqlite

# MySQL
bun add @omnigraph/mysql

# Neo4j
bun add @omnigraph/neo4j
import { loadSQLiteSubgraph } from '@omnigraph/sqlite'
import { defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
subgraphs: [
{
sourceHandler: loadSQLiteSubgraph('LocalDB', {
source: './data.db',
})
}
]
})

第十九步:Local Execution —— 将 Mesh 作为 TypeScript SDK 使用

Mesh 不仅是一个网关构建工具,还可以作为完全类型安全的 SDK 在你的 TypeScript 项目中直接使用。

19.1 输出 Supergraph 为 JS 模块

// mesh.config.ts
import { defineConfig } from '@graphql-mesh/compose-cli'

export const composeConfig = defineConfig({
output: 'supergraph.js', // 输出为 JS 模块
subgraphs: [
// ... 子图配置
]
})

19.2 编程式执行查询

import { parse } from 'graphql'
import { getExecutorForUnifiedGraph } from '@graphql-hive/gateway'

const executor = getExecutorForUnifiedGraph({
getUnifiedGraph: () => import('./supergraph.js').then(m => m.default)
})

const result = await executor({
document: parse(/* GraphQL */ `
query myQuery($someVar: String!) {
foo(someArg: $someVar) { bar }
}
`),
variables: { someVar: 'SOME_VALUE' }
})

console.log(result)
// { data: { foo: { bar: 'SOME_VALUE' } } }

19.3 生成完全类型安全的 SDK

配合 GraphQL Code Generator 生成类型安全的 SDK:

import { getSdkRequesterForUnifiedGraph } from '@graphql-hive/gateway'
import { getSdk } from './generated/sdk'

const sdkRequester = getSdkRequesterForUnifiedGraph({
getUnifiedGraph: () => import('./supergraph.js').then(m => m.default)
})

const sdk = getSdk(sdkRequester)

// 完全类型安全!
const result = await sdk.myQuery({ someVar: 'SOME_VALUE' })
// result 自动推导类型

第二十步:Consume in Other Gateways

如果你不使用 Hive Gateway,而是使用 Apollo Gateway、Apollo Router 或 Cosmo Router,Mesh Compose 生成的子图同样可以接入。

20.1 生成子图

bunx mesh-compose --subgraph Wiki > wiki-subgraph.graphql

20.2 用 Hive Gateway 作为子图服务器

bunx hive-gateway subgraph wiki-subgraph.graphql

20.3 推送到 Schema Registry

bunx hive schema:publish \
--registry.accessToken "YOUR_TOKEN" \
--target "myorg/myproject/development" \
--service "wiki" \
--url "http://localhost:4000/graphql" \
--author "Me" \
--commit "Initial wiki subgraph" \
wiki-subgraph.graphql

注意:其他网关不支持 Mesh Compose 的非 GraphQL 源处理指令,因此需要先用 Hive Gateway 作为子图服务器包装一层。


第二十一步:Gateway 高级特性

21.1 Custom Plugins —— 自定义插件

Hive Gateway 支持通过插件机制扩展功能:

import { defineConfig, useGenericAuth, useLogger } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
plugins: () => [
useLogger(),
useGenericAuth({
mode: 'protect',
// 自定义认证逻辑
}),
// 自定义插件
{
onGatewayInit({ gateway }) {
console.log('Gateway 初始化完成')
}
}
]
})

21.2 HTTP Caching(ETag)

Gateway 支持 HTTP 层面的 ETag / If-None-Match 缓存:

curl -X POST http://localhost:4000/graphql \
-H 'Content-Type: application/json' \
-d '{"query": "{ products { name } }"}' \
-v

# 响应包含 ETag
# ETag: "abc123"

# 后续请求带上 If-None-Match
curl -X POST http://localhost:4000/graphql \
-H 'Content-Type: application/json' \
-H 'If-None-Match: "abc123"' \
-d '{"query": "{ products { name } }"}' \
-v

# 返回 304 Not Modified(无 Body)

21.3 Persisted Documents —— 持久化文档

只允许白名单中的 GraphQL 操作执行,防止任意查询:

import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
persistedDocuments: {
// 从 Hive Registry 加载
hive: {
endpoint: 'https://cdn.graphql-hive.com/artifacts/v1/<id>/persisted-documents',
key: 'your-cdn-key',
}
}
})

21.4 Compression —— 端到端压缩

import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
compression: true // 默认启用
})

21.5 CORS 配置

import { defineConfig } from '@graphql-hive/gateway'

export const gatewayConfig = defineConfig({
cors: {
origin: ['https://myapp.example.com'],
methods: ['GET', 'POST'],
allowedHeaders: ['Authorization', 'Content-Type'],
}
})

v0 → v1 迁移速查

项目v0v1
配置文件.meshrc.yamlmesh.config.ts
CLI@graphql-mesh/cli@graphql-mesh/compose-cli
命令mesh dev / mesh startmesh-compose + hive-gateway
gRPC 包@graphql-mesh/grpc@omnigraph/grpc
子图配置sources[].handler.grpcloadGrpcSubgraph()
TransformYAML 声明式createXxxTransform() 函数式
请求头operationHeaders (YAML)operationHeaders (TS 对象)
gRPC 认证metaData (YAML)metaData (TS 对象)
输出内存运行supergraph.graphql 文件

参考资源

Mesh v1 核心

Hive Gateway

GraphQL Hive 平台


最后更新:2026-04-10 · 严格基于 GraphQL Mesh v1Hive Gateway 官方文档

破除厂商绑定:开源 GraphQL 联邦替代方案 WunderGraph Cosmo 全景解析

· 阅读需 17 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

在上一篇文章 《Apollo Router 由浅入深》 中,我们深入探讨了 Apollo 为微服务架构提供的高性能联邦网关解决方案。然而在实际落地时,许多团队会面临一个严峻的挑战:Apollo 的控制面板(GraphOS / Studio)是闭源的商业 SaaS 产品,存在较高的费用门槛和厂商锁定(Vendor Lock-in)风险

为了解决这个问题,社区与业界孕育了完全开源(Apache 2.0)的替代方案 —— WunderGraph Cosmo。它旨在成为 Apollo Federation 的"Drop-in"级增强替代品。

Apollo Router 由浅入深:从 Federation 到请求生命周期的全链路剖析

· 阅读需 9 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

当你的 GraphQL 服务从一个 monolith 发展到十几个甚至几十个微服务时,如何让客户端只面对一个端点、同时让后端团队各自独立迭代?Apollo 的答案是 Federation(联邦架构) 和一个用 Rust 编写的高性能入口——Apollo Router

本文将带你从最基础的概念一路走到生产级配置,完整覆盖 Apollo Router 的请求生命周期(Request Lifecycle)

面向生产环境的 GraphQL 架构实战:网关、安全、权限与监控

· 阅读需 11 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

GraphQL 赋予了前端极大的灵活性,允许客户端按需获取数据,避免了 RESTful API 常见的过度获取(Over-fetching)或获取不足(Under-fetching)问题。然而,随着灵活性的增加,GraphQL 在生产环境中也面临着巨大的挑战——尤其是安全防护、性能瓶颈、细粒度权限管控等问题。

本文将结合一线大厂的实践经验,从流量网关、代码侧深度与大小防护、基于 OPA 的动态权限管控、以及监控指标四个维度,为你梳理一套可落地的 GraphQL 生产环境架构闭环。

深入了解 OPA:与 GraphQL 结合的实战演练及 Casbin 对比分析

· 阅读需 8 分钟
Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡

在云原生系统和微服务架构的今天,权限控制和策略管理(Authorization & Policy Management)变得越来越复杂。硬编码权限逻辑不仅难以维护,而且无法适应快速变化的业务需求。

本文将带你深入了解业界流行的策略引擎 Open Policy Agent (OPA),并通过实战演示如何将其应用在 GraphQL 接口中进行细粒度的权限控制。此外,我们还会将 OPA 与另一款热门的权限框架 Casbin 进行全方面对比,帮你更好地在项目中做出技术选型。