graph TB
subgraph Main Application
AppModule[App Module]
AppController[App Controller]
AppService[App Service]
end
subgraph Quiz Module
QuizMod[Quiz Module]
QuizController[Quiz Controller]
QuizSetController[QuizSet Controller]
QuizService[Quiz Service]
QuizRepo[Quiz Repository]
QuizSetRepo[QuizSet Repository]
end
subgraph QuizZone Module
QuizZoneMod[QuizZone Module]
QuizZoneController[QuizZone Controller]
QuizZoneService[QuizZone Service]
QuizZoneRepo[QuizZone Repository]
end
subgraph Play Module
PlayMod[Play Module]
PlayGateway[Play Gateway]
PlayService[Play Service]
end
subgraph Chat Module
ChatMod[Chat Module]
ChatController[Chat Controller]
ChatService[Chat Service]
ChatRepo[Chat Memory Repository]
end
subgraph Core
SessionWsAdapter[Session WebSocket Adapter]
Logger[Winston Logger]
TypeORM[TypeORM]
end
%% Module Dependencies
AppModule --> QuizMod
AppModule --> QuizZoneMod
AppModule --> PlayMod
AppModule --> ChatMod
%% Quiz Module Dependencies
QuizMod --> QuizController
QuizMod --> QuizSetController
QuizMod --> QuizService
QuizService --> QuizRepo
QuizService --> QuizSetRepo
%% QuizZone Module Dependencies
QuizZoneMod --> QuizZoneController
QuizZoneMod --> QuizZoneService
QuizZoneService --> QuizZoneRepo
QuizZoneService --> QuizService
QuizZoneService --> ChatService
%% Play Module Dependencies
PlayMod --> PlayGateway
PlayMod --> PlayService
PlayService --> QuizZoneService
PlayService --> ChatService
%% Chat Module Dependencies
ChatMod --> ChatController
ChatMod --> ChatService
ChatService --> ChatRepo
%% Core Dependencies
AppModule --> SessionWsAdapter
AppModule --> Logger
AppModule --> TypeORM
데이터 계층 개선
// 현재: In-memory Repository
export class ChatRepositoryMemory {
constructor(@Inject('ChatStorage') private readonly data: Map<string, ChatMessage[]>) {}
}
// 개선: Repository 인터페이스와 구현 분리
export interface IChatRepository {
save(message: ChatMessage): Promise<void>;
findByRoom(roomId: string): Promise<ChatMessage[]>;
}
// 구현은 Memory, Redis, DB 등으로 유연하게 변경 가능
도메인 로직 분리
// 현재: 서비스에 모든 로직이 집중
export class PlayService {
async submit(quizZoneId: string, clientId: string, submittedQuiz: SubmittedQuiz) {
// 복잡한 제출 로직...
}
}
// 개선: 도메인 객체로 로직 분리
export class Quiz {
submit(answer: string, submittedAt: number): SubmitResult {
// 퀴즈 제출 관련 도메인 로직
}
}
글로벌 예외 처리 추가
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
if (exception instanceof DomainException) {
// 도메인 예외 처리
} else if (exception instanceof InfrastructureException) {
// 인프라 예외 처리
}
}
}
캐싱 전략 도입
// Redis 캐싱 레이어 추가
@Injectable()
export class QuizZoneCacheService {
constructor(
@Inject(CACHE_MANAGER) private cacheManager: Cache,
private quizZoneService: QuizZoneService,
) {}
async getQuizZone(id: string): Promise<QuizZone> {
const cached = await this.cacheManager.get(`quizzone:${id}`);
if (cached) return cached;
const quizZone = await this.quizZoneService.findOne(id);
await this.cacheManager.set(`quizzone:${id}`, quizZone);
return quizZone;
}
}
이벤트 기반 아키텍처 도입
// 이벤트 발행/구독 패턴 도입
export class QuizEventPublisher {
@Inject(EventEmitter2)
private eventEmitter: EventEmitter2;
publishQuizSubmitted(quizId: string, submission: QuizSubmission) {
this.eventEmitter.emit('quiz.submitted', { quizId, submission });
}
}
메트릭스 & 모니터링 추가
// 프로메테우스 메트릭스 추가
@Injectable()
export class QuizMetricsService {
private submitLatency = new Histogram({
name: 'quiz_submit_latency',
help: 'Quiz submission latency in seconds',
});
recordSubmitLatency(duration: number) {
this.submitLatency.observe(duration);
}
}
이러한 개선을 통해: