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
97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
import type { GraphqlContext } from "../context.js";
|
||
import type { DashboardDataType } from "../types.js";
|
||
import type { ResolverDeps } from "./index.js";
|
||
import { mapChild, mapParent, mapViewport } from "../../aggregation/response-mapper.js";
|
||
import { orchestrate } from "../../aggregation/orchestrator.js";
|
||
import { withCacheFallback } from "../../aggregation/fallback-strategy.js";
|
||
import { CacheKeys } from "../../shared/cache/cache-key.builder.js";
|
||
import { env } from "../../config/env.js";
|
||
import { logger } from "../../shared/observability/logger.js";
|
||
import type {
|
||
ChildDto,
|
||
UserInfoDto,
|
||
ViewportDto,
|
||
} from "../../clients/dtos.js";
|
||
|
||
/**
|
||
* Dashboard Query Resolver(对齐 02-architecture-design.md §1.2 + workline P4.6)。
|
||
*
|
||
* P4.6 增强:
|
||
* - Redis 缓存(15s TTL,对齐 env.DASHBOARD_CACHE_TTL_SECONDS)
|
||
* - Redis 不可用时降级为内存 LRU
|
||
* - 使用 Orchestrator 抽象并行编排 + partial 降级标记
|
||
*
|
||
* P5 增强:
|
||
* - 聚合 msg.listNotifications(unread=true) 获取 unreadNotifications 计数
|
||
*
|
||
* 聚合 4 个并行调用:
|
||
* - iam.getUserInfo → parent
|
||
* - iam.getChildrenByParent → children
|
||
* - iam.getViewports → viewports
|
||
* - msg.listNotifications(unread=true) → unreadNotifications
|
||
*
|
||
* degraded=true 当任一下游调用失败。
|
||
*/
|
||
export function buildDashboardResolver(deps: ResolverDeps) {
|
||
return async (
|
||
_parent: unknown,
|
||
_args: unknown,
|
||
ctx: GraphqlContext,
|
||
): Promise<DashboardDataType> => {
|
||
const parentId = ctx.session.parentId;
|
||
const cacheKey = CacheKeys.dashboard(parentId);
|
||
|
||
const result = await withCacheFallback(
|
||
cacheKey,
|
||
() => fetchDashboard(deps, parentId),
|
||
env.DASHBOARD_CACHE_TTL_SECONDS,
|
||
"dashboard",
|
||
);
|
||
|
||
if (result.data) {
|
||
if (result.fromCache) {
|
||
logger.debug({ parentId }, "Dashboard cache hit");
|
||
}
|
||
return result.data;
|
||
}
|
||
|
||
// 所有降级都失败,返回最小可用数据
|
||
logger.warn({ parentId }, "Dashboard all fallbacks exhausted");
|
||
return {
|
||
parent: null,
|
||
children: [],
|
||
viewports: [],
|
||
unreadNotifications: 0,
|
||
degraded: true,
|
||
};
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 实际聚合下游数据(Orchestrator 并行 + 降级标记)。
|
||
*/
|
||
async function fetchDashboard(
|
||
deps: ResolverDeps,
|
||
parentId: string,
|
||
): Promise<DashboardDataType> {
|
||
const orchResult = await orchestrate({
|
||
parent: deps.iamClient.getUserInfo(parentId),
|
||
children: deps.iamClient.getChildrenByParent(parentId),
|
||
viewports: deps.iamClient.getViewports(parentId),
|
||
unread: deps.msgClient.listNotifications(parentId, true),
|
||
});
|
||
|
||
const parent = orchResult.results.parent as UserInfoDto | null;
|
||
const children = orchResult.results.children as ChildDto[] | null;
|
||
const viewports = orchResult.results.viewports as ViewportDto[] | null;
|
||
const unreadList = orchResult.results.unread as unknown[] | null;
|
||
|
||
return {
|
||
parent: parent ? mapParent(parent) : null,
|
||
children: children ? children.map(mapChild) : [],
|
||
viewports: viewports ? viewports.map(mapViewport) : [],
|
||
unreadNotifications: unreadList ? unreadList.length : 0,
|
||
degraded: orchResult.partial,
|
||
};
|
||
}
|