웹 개발

퍼플심_04_아키텍쳐 정리하기

작성자 : Heehyeon Yoo|2026-01-09
# Architecture# Refactoring# Zustand# ScenarioAsCode

들어가는 말

MVP 기능 구현은 어느 정도 일단락됐다.
이번에는 이 시스템이 어떻게 돌아가는지 정리해보려 한다.
나중에 유지보수할 '미래의 나'를 위한 기술 문서이기도 하다.

1. Core Stack: "No Database"

DB 없는 100% 클라이언트 사이드 앱이다.

  • Framework: Next.js(App Router)
  • Language: TypeScript(Strict Mode)
  • State: Zustand
  • Storage: LocalStorage(진행 상황 저장용, 예정)

교육용 시뮬레이터 특성상 사용자마다 독립된 인스턴스가 필요했다. 또 복잡한 백엔드 로직보다 '인터랙션'이 더 중요하다고 판단했다.
그래서 무거운 백엔드 대신 브라우저 메모리 안에서 거대한 상태 머신(State Machine)이 돌아가는 구조를 택했다.

물론 시나리오가 많아지고 상태가 복잡해지면 이 구조도 문제가 될 수 있다.
이 점은 염두에 두고 있다. 나중에 필요하면 백엔드로 이전할 수 있도록 설계할 것이다.

2. State Management: Zustand Engine

이 앱의 심장은 useScenarioStore다.
단순히 "지금 몇 단계인가?"만 저장하는 게 아니다. 시뮬레이션의 모든 상태를 관리하는 엔진으로 구성했다.

interface ScenarioState {
  // Game State
  currentRole: 'RED' | 'BLUE';
  currentPhaseIndex: number;

  // Blue Team Progress (Complex Object)
  blueProgress: {
    detectedArtifacts: Artifact[];      // 발견한 증거
    executedActions: string[];          // 수행한 조치
    blockedIPs: string[];               // 차단한 IP
    // ...
  };
}

레드팀은 선형적(Linear)이라 currentPhaseIndex만 올리면 되지만
대응을 해야 하는 블루팀은 비선형적이라 blueProgress 객체 안에 수행한 행동들을 누적해서 관리한다.
이 모든 게 Zustand 스토어 하나에서 관리되므로 컴포넌트 간 데이터 동기화 걱정이 없다.
추후 시나리오의 레드팀 작업 방식에 따라 비선형적으로 다시 설계해야할 수 있다.

3. Scenario as Code

시나리오 데이터(react2shell.ts)는 JSON이 아니라 TypeScript 파일로 관리한다.
타입 안정성(Type Safety)을 위해 이런 방식을 많이 쓴다고 해서 한 번 써보기로 했다.
시나리오 흐름이 복잡해질수록 데이터 오타 하나가 치명적인 버그가 되니까.

export const react2shell: Scenario = {
  id: "react2shell",
  title: "React -> Shell",
  phases: [
    {
      role: "RED",
      type: "RECONNAISSANCE",
      // ...
    },
    {
      role: "BLUE",
      // ...
    }
  ]
};

이렇게 코드로 관리하면 컴파일 타임에 에러를 잡을 수 있고 IDE의 자동 완성 지원도 받을 수 있다.
나중에 시나리오가 100개가 되어도 이 구조라면 관리가 쉬울 것이다.

4. Role Separation(One Store, Two Views)

하나의 스토어를 쓰지만 뷰(View)는 철저히 분리했다.

  • RedTeamWorkbench: 공격자용 UI (터미널, 에디터 중심)
  • BlueTeamWorkbench: 방어자용 UI (대시보드, 분석 도구 중심)

currentRole 상태값에 따라 렌더링되는 최상위 컴포넌트 자체가 갈아끼워지는 방식이다.
덕분에 코드가 섞이지 않고 각 역할에 특화된 UI/UX를 마음껏 구현할 수 있었다.

홈 랜딩 페이지도 좀 꾸몄다.