Files
Edu/services/parent-bff/src/graphql/yoga.ts
SpecialX 2229309a1e feat: initialize parent-bff service with full core features
add complete parent-bff implementation including:
- GraphQL endpoint with depth/cost validation
- ChildGuard越权校验 with redis cache and singleflight
- parallel orchestration with partial failure fallback
- three-level cache fallback strategy (Redis + LRU + downstream)
- Kafka consumer for cache invalidation and notification push
- opossum circuit breaker for downstream services
- Prometheus metrics and SLO alerts
- Helm chart for k8s deployment with multi-environment configs
- Grafana dashboard for observability
- complete unit and integration tests
2026-07-10 18:49:06 +08:00

97 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { createYoga, type Plugin } from "graphql-yoga";
import depthLimit from "graphql-depth-limit";
import {
createComplexityRule,
simpleEstimator,
} from "graphql-query-complexity";
import type { Request, Response } from "express";
import type { IResolvers } from "@graphql-tools/utils";
import { buildSchema } from "./schema.js";
import { buildSession } from "./context.js";
import type { GraphqlContext, ParentSession } from "./context.js";
import { env } from "../config/env.js";
import { logger } from "../shared/observability/logger.js";
import {
graphqlRequestsTotal,
graphqlDurationSeconds,
} from "../shared/observability/metrics.js";
import type { DataLoaderFactory } from "../dataloader/dataloader.factory.js";
/**
* 校验规则插件:注入深度限制 + 复杂度限制02 §9 #5
*/
const validationRulesPlugin: Plugin = {
onValidate: ({ addValidationRule }) => {
addValidationRule(depthLimit(env.GRAPHQL_DEPTH_LIMIT));
addValidationRule(
createComplexityRule({
maximumComplexity: env.GRAPHQL_COST_LIMIT,
estimators: [simpleEstimator({ defaultComplexity: 1 })],
onComplete: (complexity: number) => {
logger.debug({ complexity }, "GraphQL query complexity");
},
}),
);
},
};
/**
* 指标埋点插件onExecute 记录开始onExecuteDone 记录完成 + 延迟。
*/
const metricsPlugin: Plugin = {
onExecute: ({ args }) => {
const opName = args.operationName ?? "anonymous";
const startedAt = Date.now();
graphqlRequestsTotal.inc({ operation: opName, status: "started" });
return {
onExecuteDone: () => {
const elapsed = (Date.now() - startedAt) / 1000;
graphqlDurationSeconds.observe({ operation: opName }, elapsed);
graphqlRequestsTotal.inc({
operation: opName,
status: "completed",
});
},
};
},
};
/**
* 创建 GraphQL Yoga 实例(工厂模式,注入 DataLoaderFactory + Resolvers
*
* 对齐 02-architecture-design.md §1.1 + §9 #1 + §9 #5。
*
* 每次 GraphQL 请求构建 per-request context包含
* - ParentSession从 x-user-id 等头解析)
* - Loadersper-request DataLoader 实例,防 N+1
*/
export function createYogaInstance(
loaderFactory: DataLoaderFactory,
resolvers: IResolvers,
) {
const schema = buildSchema(resolvers);
return createYoga<{
req: Request;
res: Response;
session: ParentSession;
}>({
schema,
graphqlEndpoint: "/graphql",
graphiql:
env.NODE_ENV === "development" && env.GRAPHQL_INTROSPECTION_ENABLED,
context: ({ req }): GraphqlContext => {
const session = buildSession(req);
const loaders = loaderFactory.createLoaders();
return { session, req, res: req.res as Response, loaders };
},
plugins: [validationRulesPlugin, metricsPlugin],
});
}
/** Yoga 实例类型(供 GraphqlModule provider 类型推导) */
export type YogaInstance = ReturnType<typeof createYogaInstance>;