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
165 lines
4.4 KiB
TypeScript
165 lines
4.4 KiB
TypeScript
/**
|
||
* parent-bff 统一错误体系(对齐 02-architecture-design.md §6.2)。
|
||
*
|
||
* 错误码前缀统一 BFF_PARENT_*(C1 仲裁:BFF 在前,不是 PARENT_BFF_)。
|
||
*
|
||
* 错误码清单:
|
||
* - BFF_PARENT_VALIDATION_ERROR (400)
|
||
* - BFF_PARENT_UNAUTHORIZED (401)
|
||
* - BFF_PARENT_CHILD_NOT_BOUND (403) — ChildGuard 拦截
|
||
* - BFF_PARENT_NOT_FOUND (404)
|
||
* - BFF_PARENT_CONFLICT (409)
|
||
* - BFF_PARENT_BUSINESS_ERROR (422)
|
||
* - BFF_PARENT_BAD_GATEWAY (502)
|
||
* - BFF_PARENT_GATEWAY_TIMEOUT (504)
|
||
* - BFF_PARENT_SERVICE_UNAVAILABLE (503) — 熔断器开启(P6)
|
||
* - BFF_PARENT_INTERNAL_ERROR (500)
|
||
*/
|
||
export type ErrorType =
|
||
| "validation"
|
||
| "unauthorized"
|
||
| "child_not_bound"
|
||
| "not_found"
|
||
| "conflict"
|
||
| "business"
|
||
| "bad_gateway"
|
||
| "gateway_timeout"
|
||
| "service_unavailable"
|
||
| "internal";
|
||
|
||
export interface ErrorDetails {
|
||
[key: string]: unknown;
|
||
}
|
||
|
||
export abstract class ApplicationError extends Error {
|
||
abstract readonly type: ErrorType;
|
||
abstract readonly statusCode: number;
|
||
readonly code: string;
|
||
readonly details?: ErrorDetails;
|
||
traceId?: string;
|
||
|
||
constructor(message: string, code: string, details?: ErrorDetails) {
|
||
super(message);
|
||
this.name = this.constructor.name;
|
||
this.code = code;
|
||
this.details = details;
|
||
}
|
||
|
||
toJSON(): Record<string, unknown> {
|
||
return {
|
||
success: false,
|
||
error: {
|
||
code: this.code,
|
||
message: this.message,
|
||
details: this.details,
|
||
traceId: this.traceId,
|
||
},
|
||
};
|
||
}
|
||
}
|
||
|
||
export class ValidationError extends ApplicationError {
|
||
readonly type = "validation" as const;
|
||
readonly statusCode = 400;
|
||
constructor(message: string, details?: ErrorDetails) {
|
||
super(message, "BFF_PARENT_VALIDATION_ERROR", details);
|
||
}
|
||
}
|
||
|
||
export class UnauthorizedError extends ApplicationError {
|
||
readonly type = "unauthorized" as const;
|
||
readonly statusCode = 401;
|
||
constructor(message: string, details?: ErrorDetails) {
|
||
super(message, "BFF_PARENT_UNAUTHORIZED", details);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* ChildGuard 越权拦截:childId 不在家长绑定列表。
|
||
*
|
||
* 对齐 02 §2.3:DataScope=CHILDREN 防御,BFF 层先拦截减少无效下游调用。
|
||
*/
|
||
export class ChildNotBoundError extends ApplicationError {
|
||
readonly type = "child_not_bound" as const;
|
||
readonly statusCode = 403;
|
||
constructor(parentId: string, requestedChildId: string, boundChildren: string[]) {
|
||
super(
|
||
`Child ${requestedChildId} not bound to parent ${parentId}`,
|
||
"BFF_PARENT_CHILD_NOT_BOUND",
|
||
{
|
||
parentId,
|
||
requestedChildId,
|
||
boundChildren,
|
||
},
|
||
);
|
||
}
|
||
}
|
||
|
||
export class NotFoundError extends ApplicationError {
|
||
readonly type = "not_found" as const;
|
||
readonly statusCode = 404;
|
||
constructor(resource: string, id: string) {
|
||
super(`${resource} not found: ${id}`, "BFF_PARENT_NOT_FOUND", {
|
||
resource,
|
||
id,
|
||
});
|
||
}
|
||
}
|
||
|
||
export class ConflictError extends ApplicationError {
|
||
readonly type = "conflict" as const;
|
||
readonly statusCode = 409;
|
||
constructor(message: string, details?: ErrorDetails) {
|
||
super(message, "BFF_PARENT_CONFLICT", details);
|
||
}
|
||
}
|
||
|
||
export class BusinessError extends ApplicationError {
|
||
readonly type = "business" as const;
|
||
readonly statusCode = 422;
|
||
constructor(message: string, details?: ErrorDetails) {
|
||
super(message, "BFF_PARENT_BUSINESS_ERROR", details);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 下游服务返回非 ok 或 gRPC rejected。
|
||
*/
|
||
export class BadGatewayError extends ApplicationError {
|
||
readonly type = "bad_gateway" as const;
|
||
readonly statusCode = 502;
|
||
constructor(message: string, details?: ErrorDetails) {
|
||
super(message, "BFF_PARENT_BAD_GATEWAY", details);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 下游调用超时。
|
||
*/
|
||
export class GatewayTimeoutError extends ApplicationError {
|
||
readonly type = "gateway_timeout" as const;
|
||
readonly statusCode = 504;
|
||
constructor(message: string, details?: ErrorDetails) {
|
||
super(message, "BFF_PARENT_GATEWAY_TIMEOUT", details);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 熔断器开启(P6 opossum 熔断器触发)。
|
||
*/
|
||
export class ServiceUnavailableError extends ApplicationError {
|
||
readonly type = "service_unavailable" as const;
|
||
readonly statusCode = 503;
|
||
constructor(message: string, details?: ErrorDetails) {
|
||
super(message, "BFF_PARENT_SERVICE_UNAVAILABLE", details);
|
||
}
|
||
}
|
||
|
||
export class InternalError extends ApplicationError {
|
||
readonly type = "internal" as const;
|
||
readonly statusCode = 500;
|
||
constructor(message: string, details?: ErrorDetails) {
|
||
super(message, "BFF_PARENT_INTERNAL_ERROR", details);
|
||
}
|
||
}
|