Basic Architecture of Sport Web
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import { Controller, Get, HttpCode, HttpStatus } from '@nestjs/common';
|
||||
import { ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { Public } from '@/common/decorators/public.decorator';
|
||||
|
||||
import { HealthService, type HealthCheckResult } from './health.service';
|
||||
|
||||
@ApiTags('health')
|
||||
@Controller('health')
|
||||
export class HealthController {
|
||||
constructor(private readonly healthService: HealthService) {}
|
||||
|
||||
/**
|
||||
* Liveness. Answers "is the process running?" and nothing else — it must not
|
||||
* touch the database, or a brief DB blip would make the orchestrator kill
|
||||
* otherwise-healthy containers.
|
||||
*/
|
||||
@Public()
|
||||
@Get('live')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Liveness probe' })
|
||||
live(): { status: 'ok' } {
|
||||
return { status: 'ok' };
|
||||
}
|
||||
|
||||
/** Readiness / deep check: should this instance receive traffic? */
|
||||
@Public()
|
||||
@Get()
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiOperation({ summary: 'Readiness probe with dependency status' })
|
||||
check(): Promise<HealthCheckResult> {
|
||||
return this.healthService.check();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { HealthController } from './health.controller';
|
||||
import { HealthService } from './health.service';
|
||||
|
||||
@Module({
|
||||
controllers: [HealthController],
|
||||
providers: [HealthService],
|
||||
})
|
||||
export class HealthModule {}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
|
||||
import { APP_CONFIG } from '@/config/app-config.module';
|
||||
import type { AppConfig } from '@/config/configuration';
|
||||
import { PrismaService } from '@/infrastructure/prisma/prisma.service';
|
||||
import { RedisService } from '@/infrastructure/redis/redis.service';
|
||||
|
||||
export interface DependencyStatus {
|
||||
status: 'up' | 'down';
|
||||
latencyMs: number | null;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface HealthCheckResult {
|
||||
status: 'ok' | 'degraded';
|
||||
uptimeSeconds: number;
|
||||
version: string;
|
||||
environment: string;
|
||||
dependencies: {
|
||||
database: DependencyStatus;
|
||||
redis: DependencyStatus;
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class HealthService {
|
||||
constructor(
|
||||
@Inject(APP_CONFIG) private readonly config: AppConfig,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly redis: RedisService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Deep check — used by dashboards and by `docker compose` dependency gates.
|
||||
* Dependencies are probed in parallel so a slow one cannot mask another.
|
||||
*/
|
||||
async check(): Promise<HealthCheckResult> {
|
||||
const [database, redis] = await Promise.all([
|
||||
probe(() => this.prisma.ping()),
|
||||
probe(() => this.redis.ping()),
|
||||
]);
|
||||
|
||||
const healthy = database.status === 'up' && redis.status === 'up';
|
||||
|
||||
return {
|
||||
status: healthy ? 'ok' : 'degraded',
|
||||
uptimeSeconds: Math.round(process.uptime()),
|
||||
version: this.config.app.version,
|
||||
environment: this.config.app.env,
|
||||
dependencies: { database, redis },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async function probe(fn: () => Promise<unknown>): Promise<DependencyStatus> {
|
||||
const startedAt = performance.now();
|
||||
try {
|
||||
await fn();
|
||||
return { status: 'up', latencyMs: Math.round(performance.now() - startedAt) };
|
||||
} catch (error) {
|
||||
return {
|
||||
status: 'down',
|
||||
latencyMs: null,
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user