project setup with core files including configuration, package management, and basic structure. Added .gitignore, README, and various TypeScript types for CMS components. Implemented initial components and layouts for the application.
This commit is contained in:
101
middlelayer/plugins/monitoring.ts
Normal file
101
middlelayer/plugins/monitoring.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { ApolloServerPlugin } from '@apollo/server';
|
||||
import { logger, logQuery, logError } from '../monitoring/logger.js';
|
||||
import {
|
||||
queryCounter,
|
||||
queryDuration,
|
||||
errorCounter,
|
||||
queryComplexityGauge,
|
||||
} from '../monitoring/metrics.js';
|
||||
import { createTrace, endTrace } from '../monitoring/tracing.js';
|
||||
|
||||
/**
|
||||
* Monitoring Plugin für Apollo Server
|
||||
* Sammelt Logs, Metrics und Traces für jeden Request
|
||||
*/
|
||||
export const monitoringPlugin = (): ApolloServerPlugin => {
|
||||
return {
|
||||
async requestDidStart() {
|
||||
return {
|
||||
async didResolveOperation({ request, operationName }) {
|
||||
// Erstelle Trace für Request
|
||||
const traceId = createTrace().traceId;
|
||||
(request as any).traceId = traceId;
|
||||
|
||||
logger.info('GraphQL operation started', {
|
||||
operationName: operationName || 'unknown',
|
||||
query: request.query,
|
||||
variables: request.variables,
|
||||
traceId,
|
||||
});
|
||||
},
|
||||
|
||||
async willSendResponse({ request, response }) {
|
||||
const traceId = (request as any).traceId;
|
||||
const operationName = request.operationName || 'unknown';
|
||||
const duration = traceId ? endTrace(traceId) : 0;
|
||||
|
||||
// Log Query
|
||||
logQuery(operationName, request.variables, duration);
|
||||
|
||||
// Metrics
|
||||
const status = response.errors && response.errors.length > 0 ? 'error' : 'success';
|
||||
queryCounter.inc({ operation: operationName, status });
|
||||
queryDuration.observe({ operation: operationName }, duration / 1000);
|
||||
|
||||
// Log Errors
|
||||
if (response.errors && response.errors.length > 0) {
|
||||
response.errors.forEach((error) => {
|
||||
logError(error as Error, {
|
||||
operationName,
|
||||
traceId,
|
||||
});
|
||||
errorCounter.inc({
|
||||
type: error.extensions?.code as string || 'UNKNOWN',
|
||||
operation: operationName,
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async didEncounterErrors({ request, errors }) {
|
||||
const traceId = (request as any).traceId;
|
||||
const operationName = request.operationName || 'unknown';
|
||||
|
||||
errors.forEach((error) => {
|
||||
logError(error as Error, {
|
||||
operationName,
|
||||
traceId,
|
||||
});
|
||||
errorCounter.inc({
|
||||
type: error.extensions?.code as string || 'UNKNOWN',
|
||||
operation: operationName,
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Plugin für Query Complexity Tracking
|
||||
*/
|
||||
export const queryComplexityMonitoringPlugin = (): ApolloServerPlugin => {
|
||||
return {
|
||||
async requestDidStart() {
|
||||
return {
|
||||
async didResolveOperation({ request, operationName }) {
|
||||
// Wird vom queryComplexityPlugin gesetzt
|
||||
const complexity = (request as any).complexity;
|
||||
if (complexity) {
|
||||
queryComplexityGauge.set(
|
||||
{ operation: operationName || 'unknown' },
|
||||
complexity
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
67
middlelayer/plugins/queryComplexity.ts
Normal file
67
middlelayer/plugins/queryComplexity.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import type { ApolloServerPlugin } from "@apollo/server";
|
||||
import { getComplexity, simpleEstimator } from "graphql-query-complexity";
|
||||
import { GraphQLError } from "graphql";
|
||||
|
||||
interface QueryComplexityPluginOptions {
|
||||
maxComplexity?: number;
|
||||
defaultComplexity?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apollo Server Plugin für Query Complexity Limits
|
||||
* Verhindert zu komplexe Queries, die das System überlasten könnten
|
||||
*/
|
||||
export const queryComplexityPlugin = (
|
||||
options: QueryComplexityPluginOptions = {}
|
||||
): ApolloServerPlugin => {
|
||||
const maxComplexity = options.maxComplexity ?? 1000;
|
||||
const defaultComplexity = options.defaultComplexity ?? 1;
|
||||
|
||||
return {
|
||||
async requestDidStart() {
|
||||
return {
|
||||
async didResolveOperation({ request, document, schema }) {
|
||||
if (!schema) return;
|
||||
|
||||
try {
|
||||
const complexity = getComplexity({
|
||||
schema,
|
||||
operationName: request.operationName || undefined,
|
||||
query: document,
|
||||
variables: request.variables || {},
|
||||
estimators: [
|
||||
// Basis-Komplexität für jeden Field
|
||||
simpleEstimator({ defaultComplexity }),
|
||||
],
|
||||
});
|
||||
|
||||
// Speichere Complexity im Request für Monitoring
|
||||
(request as any).complexity = complexity;
|
||||
|
||||
if (complexity > maxComplexity) {
|
||||
throw new GraphQLError(
|
||||
`Query zu komplex (${complexity}). Maximum: ${maxComplexity}`,
|
||||
{
|
||||
extensions: {
|
||||
code: "QUERY_TOO_COMPLEX",
|
||||
complexity,
|
||||
maxComplexity,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Wenn es ein Schema-Realm-Problem gibt, logge es aber blockiere nicht
|
||||
if (error.message?.includes("another module or realm")) {
|
||||
console.warn(
|
||||
"[Query Complexity] Schema-Realm-Konflikt, Complexity-Check übersprungen"
|
||||
);
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
32
middlelayer/plugins/responseCache.ts
Normal file
32
middlelayer/plugins/responseCache.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import ApolloServerPluginResponseCache from "@apollo/server-plugin-response-cache";
|
||||
import type { GraphQLRequestContext } from "@apollo/server";
|
||||
|
||||
/**
|
||||
* Response Caching Plugin für Apollo Server
|
||||
* Cached GraphQL Responses basierend auf Query und Variablen
|
||||
*/
|
||||
export const createResponseCachePlugin = () => {
|
||||
return ApolloServerPluginResponseCache({
|
||||
// Session-ID für User-spezifisches Caching
|
||||
sessionId: async (
|
||||
requestContext: GraphQLRequestContext<any>
|
||||
): Promise<string | null> => {
|
||||
// Optional: User-ID aus Headers oder Context
|
||||
const userId = requestContext.request.http?.headers.get("x-user-id");
|
||||
return userId || null;
|
||||
},
|
||||
|
||||
// Cache nur bei erfolgreichen Queries
|
||||
shouldWriteToCache: async (
|
||||
requestContext: GraphQLRequestContext<any>
|
||||
): Promise<boolean> => {
|
||||
const query = requestContext.request.query;
|
||||
|
||||
if (!query) return false;
|
||||
|
||||
// Cache nur bestimmte Queries
|
||||
const cacheableQueries = ["products", "pageSeo", "navigation", "page"];
|
||||
return cacheableQueries.some((q) => query.includes(q));
|
||||
},
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user