// 이 파일이 하는 일: "모놀리스 vs 마이크로서비스 — 쪼개는 것의 진짜 비용" 코스 —
// 유행이 아니라 트레이드오프로 아키텍처를 고르는 눈을 기르는 특급 과정 페이지.
// 모놀리스의 강점에서 출발해 분산의 세금, 서비스 경계 긋기(DDD), 통신 설계, 사가 패턴,
// 스키마-퍼-서비스, 그리고 전환 전략(스트랭글러 무화과)까지 7개 섹션으로 안내한다.
// 프레이밍은 "쪼개지 않을 용기" — 실제 운영 중인 traderbox의 6서비스 분할을 살아 있는
// 교재로 삼아, 경계를 왜 거기에 그었는지 근거를 읽는 훈련을 시킨다.
// 학습 포인트: API 호출이 없는 '내용 고정' 페이지다. 스타일은 styles/global.css의
// 가이드 전용 클래스(step-card, code-block, tip 등)를 재사용하고 색은 var(--*)만 쓴다(하드코딩 금지).
// 예제 코드는 JSX 중괄호/백틱 충돌을 피하려고 파일 상단의 백틱 문자열 상수로 정의한다.

import { Link } from 'react-router-dom';

// ── 예제 코드 상수들 ──
// 학습 포인트: 백틱 상수 안에는 백틱과 달러-중괄호 보간이 들어가면 안 된다.
// 그래서 코드 예제도 중괄호 없는 의사코드·SQL·다이어그램 위주로 구성했다.

const CODE_TRADEOFF = `모놀리스 (하나의 배포 단위)          마이크로서비스 (여러 배포 단위)
──────────────────────────────      ──────────────────────────────
배포: jar 하나, 명령 하나            배포: 서비스별로 독립 배포
호출: 로컬 함수 호출 (나노초)         호출: 네트워크 호출 (밀리초 + 실패 가능)
트랜잭션: DB 하나, BEGIN~COMMIT      트랜잭션: 서비스마다 따로 → 사가 필요
확장: 전체를 복제 (통짜 스케일아웃)    확장: 병목인 서비스만 골라 확장
팀: 한 코드베이스를 모두가 공유        팀: 서비스별 오너십, 독립 배포 주기

핵심: 어느 쪽도 '정답'이 아니다.
왼쪽 열이 주는 단순함을 포기하는 대가로 오른쪽 열의 자율성을 사는 것.
그 거래가 남는 장사인지가 유일한 질문이다.`;

const CODE_CONWAY = `콘웨이의 법칙 (Conway's Law, 1968)

"시스템을 설계하는 조직은,
 그 조직의 커뮤니케이션 구조를 닮은 설계를 내놓는다."

3명이 하나의 제품을 만드는 팀
  → 서비스 30개로 쪼개면: 한 사람이 10개 서비스를 오가며 배포·온콜
  → 조직이 감당 못 하는 아키텍처는 종이 위에서만 아름답다

30명이 5개 도메인 팀으로 나뉜 조직
  → 모놀리스 하나면: 배포 열차에 5팀이 줄 서고, 머지 충돌이 일상
  → 팀 경계 = 서비스 경계로 맞추면 각 팀이 자기 속도로 달린다

결론: "마이크로서비스로 갈까?"는 기술 질문이 아니라
      "우리 조직이 몇 개의 독립된 배포 주기를 감당할 수 있나?"라는 조직 질문.`;

const CODE_TAX = `// 같은 한 줄이, 경계를 넘는 순간 얼마나 달라지는가

[모놀리스 안에서]
  주문서비스.재고확인(상품ID)
  → 나노초 단위, 실패 모드는 사실상 '버그'뿐
  → 호출자와 피호출자가 같이 배포되므로 시그니처 변경도 컴파일러가 잡아줌

[마이크로서비스에서 같은 일]
  GET http://inventory-svc/internal/stock/상품ID
  → 걸리는 시간: 수 밀리초 (수만 배 느림)
  → 새로 생기는 실패 모드:
     - 타임아웃 (상대는 살아있는데 응답이 늦음 → 재시도? 몇 번?)
     - 부분 실패 (주문은 됐는데 재고 차감 응답을 못 받음 → 됐나 안 됐나?)
     - 버전 불일치 (재고팀이 응답 필드를 바꿨는데 주문팀은 옛 버전 파싱)
  → 그래서 필요해지는 것들:
     타임아웃 설정, 재시도 정책, 서킷 브레이커, API 버저닝, 분산 추적...

이 목록 전부가 '분산의 세금'이다. 쪼개는 순간 자동으로 청구된다.`;

const CODE_DIST_MONOLITH = `분산 모놀리스 — 최악의 형태 판별법

증상 1: A 서비스를 배포하려면 B, C도 같이 배포해야 한다
        (독립 배포라는 유일한 보상을 못 받으면서 네트워크 세금만 냄)
증상 2: 서비스들이 하나의 DB 테이블을 같이 읽고 쓴다
        (스키마 하나 바꾸면 전 서비스가 멈춤 — 사실상 결합돼 있음)
증상 3: 기능 하나 추가에 서비스 4개의 PR이 동시에 필요하다
        (경계를 도메인이 아니라 기술 계층으로 그었을 때 흔한 증상)

운영 비용도 서비스 수에 비례해 늘어난다:
  서비스 6개 = 배포 파이프라인 6개 + 대시보드 6개 + 로그 스트림 6개
             + 알림 규칙 6벌 + 장애 시 의심 지점 6곳 + 온콜 부담
모놀리스에서 스택트레이스 한 장이면 끝나던 디버깅이,
분산에선 서비스 3개의 로그를 시간순으로 이어 붙이는 고고학이 된다.`;

const CODE_BOUNDED = `바운디드 컨텍스트 찾기 — "같은 단어가 다른 뜻이 되는 지점"

쇼핑몰에서 '상품(Product)'이라는 한 단어:

  카탈로그 컨텍스트의 상품 = 이름, 설명, 사진, 카테고리, 검색 키워드
  재고 컨텍스트의 상품     = SKU, 창고 위치, 수량, 입고 예정일
  주문 컨텍스트의 상품     = 주문 시점의 가격 스냅샷, 수량, 환불 정책
  배송 컨텍스트의 상품     = 무게, 부피, 파손 주의 여부

네 곳의 '상품'은 이름만 같지 관심사가 전혀 다르다.
→ 이 네 덩어리가 각각 바운디드 컨텍스트, 곧 서비스 경계의 유력 후보.

데이터 소유권 원칙: 한 테이블의 주인은 정확히 하나의 서비스다.
  - 다른 서비스가 그 데이터를 원하면? 주인의 API를 부르거나 이벤트를 구독한다.
  - 다른 서비스가 그 테이블에 직접 SELECT/UPDATE? 그 순간 경계는 무너진다.`;

const CODE_TRADERBOX = `traderbox의 실제 분할 — 경계 근거 읽기 (6서비스)

  gateway (8080)  진입점. 라우팅·횡단 관심사 — 도메인 로직 없음
  auth    (8081)  사용자·토큰·워치리스트·포트폴리오 — '사용자의 것'
  market  (8083)  시세·호가 — 유일하게 초단위 실시간, 부분 확장 대상
  content (8084)  공시·아티클·라이브 — 편집/발행 주기가 시세와 전혀 다름
  macro   (8085)  거시 지표 — 하루 몇 번 수집, 배포 주기 독립
  log     (8082)  감사 로그 — 전 서비스의 이벤트를 Kafka로 받아 적재만

경계 근거를 뜯어 보면:
  market을 따로 둔 이유  → 확장 비대칭. 장중 트래픽이 다른 서비스의 수십 배.
                          시세 때문에 auth까지 스케일아웃할 이유가 없다.
  log를 따로 둔 이유     → 쓰기 패턴이 다름(append-only) + 어떤 서비스도
                          감사 적재가 느리다고 같이 느려지면 안 됨(비동기 격리).
  content와 macro 분리   → 데이터 수명주기가 다름. 공시·아티클은 편집 도메인,
                          거시는 수집 배치 도메인. 팀이 나뉘어도 자연스러운 선.

주목: '주문/결제/상품'처럼 명사로 자른 게 아니라,
      트래픽 패턴·배포 주기·데이터 수명주기가 갈라지는 지점에서 잘랐다.`;

const CODE_COMM = `traderbox의 통신 규칙 — 채널 3개, 그리고 한 가지 금기

동기 (지금 답이 필요할 때):
  서비스 → 서비스 직접 호출, 경로는 /internal/** + 공유키 헤더
  게이트웨이를 다시 타지 않는다 (내부 호출이 외부 인증 스택을 또 탈 이유 없음)
  예: content가 auth에 "이 사용자에게 알림 하나 만들어 줘"

비동기 (지금 답이 필요 없을 때):
  Kafka 토픽 audit.events — 각 서비스가 감사 이벤트를 발행,
  log 서비스가 소비해 적재. 발행자는 log가 죽어 있어도 멈추지 않는다.

실시간 팬아웃 (다수에게 즉시 뿌릴 때):
  Valkey Pub/Sub 채널 market.ticks — 시세 틱을 발행하면
  구독 중인 인스턴스들이 SSE로 브라우저에 밀어 준다.

금기: 같은 데이터를 두 채널로 흘리지 않는다.
  시세를 Kafka에도 넣고 Pub/Sub에도 넣으면? 두 경로가 미묘하게 어긋나는 순간
  "어느 쪽이 진실인가"라는 답 없는 질문이 시작된다. 채널은 데이터당 하나.`;

const CODE_SAGA = `사가(Saga) — 로컬 트랜잭션의 연쇄 + 보상 트랜잭션

주문 흐름을 3개 서비스가 나눠 가졌다고 하자:

  단계 1  [주문]  주문 레코드 생성        보상: 주문 취소로 마킹
  단계 2  [재고]  재고 차감               보상: 재고 복원
  단계 3  [결제]  카드 승인 (외부 PG)      보상: 사실상 불가(승인 취소는 별개 절차)
  단계 4  [알림]  확정 이메일 발송          보상: 불가능 (보낸 메일은 못 거둠)

정상 흐름:   1 → 2 → 3 → 4  (각 단계는 자기 DB에서 로컬 커밋)
3에서 실패:  2의 보상(재고 복원) → 1의 보상(주문 취소) 을 역순 실행

설계 원칙: 보상이 불가능하거나 비싼 단계(외부 결제, 이메일)는
           반드시 마지막에 배치한다. 뒤에 실패할 단계가 없으면
           그 단계는 보상할 일도 없다.

왜 2PC(2단계 커밋)가 아닌가:
  2PC는 코디네이터가 전원 합의할 때까지 참여자 전부가 락을 잡고 대기한다.
  참여자 하나가 느려지면 전체가 멈추고, 코디네이터가 죽으면 락이 고아가 된다.
  가용성을 위해 쪼갠 시스템에 전역 락을 다시 끼얹는 셈 — 실무에서 기피되는 이유.`;

const CODE_SCHEMA = `-- 단계적 접근: DB를 물리적으로 나누기 전에, 스키마로 먼저 격리한다
-- (traderbox 방식: 단일 PostgreSQL, 스키마-퍼-서비스)

CREATE SCHEMA auth;     -- auth 서비스만 접근
CREATE SCHEMA market;   -- market 서비스만 접근
CREATE SCHEMA content;  -- content 서비스만 접근

-- 각 서비스의 DB 계정에는 자기 스키마 권한만 부여
GRANT USAGE ON SCHEMA auth TO svc_auth;
REVOKE ALL  ON SCHEMA auth FROM svc_market, svc_content;

-- 이러면 인프라는 DB 한 대(운영 부담 최소)인데,
-- 코드 관점에서는 "남의 테이블에 JOIN 못 함"이 강제된다.
-- 나중에 트래픽이 커지면 스키마를 통째로 별도 DB 인스턴스로
-- 이사시키면 된다 — 코드는 이미 경계를 지키고 있으니까.

-- 서비스 간 JOIN이 필요해지면? (예: "주문 목록에 상품 이름 붙이기")
--   1) API 합성: 주문 API 응답을 받아 상품 API를 이어 부른다 (N+1 주의)
--   2) 데이터 복제: 주문 스키마에 상품 이름을 이벤트로 받아 복사해 둔다
--   3) 읽기 전용 뷰: 조회 전용 서비스가 이벤트를 구독해 조합 테이블을 만든다`;

const CODE_STRANGLER = `스트랭글러 무화과(Strangler Fig) 패턴 — 가장자리부터 갈아끼우기

숙주 나무를 감고 자라다 결국 대체하는 무화과나무처럼:

  1단계   [프록시/게이트웨이] → 모든 요청이 모놀리스로
  2단계   [프록시] → /api/notifications 만 새 서비스로, 나머지는 모놀리스로
  3단계   [프록시] → 떼어낸 경로가 하나씩 늘어남
  N단계   모놀리스에 남은 것이 없으면 조용히 은퇴

핵심: 빅뱅 재작성(한 번에 다시 쓰기)은 "기존 기능 전부를 멈춘 채 재현"하는
      게임이라 거의 항상 진다. 스트랭글러는 매 단계가 배포 가능하고 되돌릴 수 있다.

그 전에: 모듈러 모놀리스로 경계를 코드 안에서 먼저 증명하라.
  한 배포 단위 안에서 패키지·모듈 경계를 긋고 (order / inventory / payment),
  모듈 간에는 공개 인터페이스로만 호출하게 강제한다.
  이 경계가 6개월간 지켜졌다면 → 네트워크로 승격해도 살아남을 경계다.
  이 경계가 계속 뚫린다면 → 쪼갰어도 분산 모놀리스가 됐을 것이다. 미리 안 것에 감사.

쪼개기 결정 체크리스트 (하나도 해당 없으면, 아직 쪼갤 때가 아니다):
  [ ] 배포 충돌: 서로 다른 팀/기능의 배포가 주기적으로 서로를 막는가?
  [ ] 확장 비대칭: 특정 부분만 수십 배 트래픽인데 전체를 복제 중인가?
  [ ] 팀 크기: 한 코드베이스에 사람이 많아 머지·리뷰가 병목인가?`;

// ── 이 페이지 안에서만 쓰는 작은 부품들 ──
// 학습 포인트: 같은 모양이 반복되면 컴포넌트로 뽑는다. props만 바꿔 재사용.
function Section({ n, title, sub, children }) {
  return (
    <section className="step-card" id={`sec-${n}`}>
      <div className="step-head">
        <span className="step-num">{n}</span>
        <div>
          <h3>{title}</h3>
          {sub && <span className="step-sub">{sub}</span>}
        </div>
      </div>
      <div className="step-body">{children}</div>
    </section>
  );
}

function Code({ children }) {
  return <pre className="code-block"><code>{children}</code></pre>;
}

const SECTIONS = [
  { n: 1, label: '모놀리스는 죄가 아니다' },
  { n: 2, label: '분산의 세금' },
  { n: 3, label: '서비스 경계 긋기' },
  { n: 4, label: '서비스 간 통신' },
  { n: 5, label: '사가 패턴' },
  { n: 6, label: '스키마-퍼-서비스' },
  { n: 7, label: '전환 전략' },
];

export default function Lv4MonolithVsMicroservicesPage() {
  return (
    <div>
      {/* 히어로: 이 코스가 어디서 출발해 어디까지 가는지 */}
      <div className="hero">
        <div className="eyebrow">Course · 특급 과정</div>
        <h1>🧩 모놀리스 vs 마이크로서비스<br />— 쪼개는 것의 진짜 비용</h1>
        <p>
          "요즘은 다 마이크로서비스로 가던데요?" — 그 문장이 이 코스의 출발점이자,
          버려야 할 첫 문장입니다. 아키텍처는 유행이 아니라 <strong>트레이드오프</strong>로
          고르는 것. 모놀리스가 공짜로 주던 것들의 가격표를 읽고, 서비스 경계를 긋는
          도구를 손에 쥐고, 트랜잭션이 쪼개질 때의 대가를 계산한 뒤 —{' '}
          <strong>'쪼개지 않을 용기'</strong>까지 갖추는 것이 목표입니다.
        </p>
        <div className="chip-row">
          <span className="chip">예상 소요 80분</span>
          <span className="chip">실습: 쇼핑몰 모놀리스 경계 그리기 발표</span>
          <span className="chip">대상: 대규모 트래픽·분산 설계를 보는 실무 개발자</span>
        </div>
      </div>

      {/* 섹션 바로가기 — 학습 포인트: 정적 앵커(#sec-n)만으로 만드는 목차 */}
      <div className="chip-row">
        {SECTIONS.map((s) => (
          <a key={s.n} className="chip" href={`#sec-${s.n}`}>
            {s.n}. {s.label}
          </a>
        ))}
      </div>

      <Section n={1} title="모놀리스는 죄가 아니다" sub="두 아키텍처의 본질 — 그리고 콘웨이의 법칙">
        <p>
          <strong>모놀리스</strong>는 애플리케이션 전체가 <strong>하나의 배포 단위</strong>인
          구조예요. jar 하나를 빌드해 서버에 올리면 끝. 이 단순함이 공짜로 주는 것이
          셋 있습니다. <strong>① 단순한 배포</strong> — 파이프라인 하나, 버전 하나, 롤백도
          하나. <strong>② 로컬 함수 호출</strong> — 주문 모듈이 재고 모듈을 부르는 건
          메모리 안의 일이라 나노초 단위이고, 네트워크 실패라는 개념 자체가 없어요.{' '}
          <strong>③ 하나의 트랜잭션</strong> — 주문 생성과 재고 차감을{' '}
          <span className="icode">BEGIN</span>과 <span className="icode">COMMIT</span> 사이에
          넣으면 DB가 원자성을 보장해 줍니다. 둘 다 되거나, 둘 다 안 되거나.
        </p>
        <p>
          <strong>마이크로서비스</strong>는 이 셋을 <strong>포기하는 대가로</strong> 다른
          것을 삽니다. <strong>독립 배포</strong> — 시세 서비스만 하루 다섯 번 배포해도
          인증 서비스는 무관. <strong>팀 자율성</strong> — 서비스마다 오너 팀이 있어
          머지 충돌 없이 자기 속도로 달림. <strong>부분 확장</strong> — 트래픽이 몰리는
          서비스만 골라 인스턴스를 늘림. 어느 쪽도 정답이 아니고, 이 <strong>거래</strong>가
          남는 장사인지가 유일한 질문이에요.
        </p>
        <Code>{CODE_TRADEOFF}</Code>
        <p>
          그리고 이 거래의 손익을 결정하는 가장 큰 변수는 기술이 아니라{' '}
          <strong>조직</strong>입니다. 1968년 멜빈 콘웨이가 남긴 관찰 —{' '}
          <strong>시스템의 구조는 그것을 만드는 조직의 소통 구조를 닮는다</strong> — 이
          여기서 실전 도구가 돼요. 세 명이 서비스 서른 개를 운영하면 한 사람이 열 개의
          온콜을 지고, 서른 명이 모놀리스 하나에 매달리면 배포 열차에 다섯 팀이 줄을
          섭니다. 어느 쪽도 아키텍처 다이어그램에는 안 나오는 실패죠.
        </p>
        <Code>{CODE_CONWAY}</Code>
        <div className="tip">
          <b>이 코스의 제1원칙</b> "마이크로서비스로 갈까?"라고 묻지 말고,{' '}
          <strong>"우리 조직이 독립된 배포 주기를 몇 개나 감당할 수 있나?"</strong>라고
          물으세요. 앞의 질문엔 유행이 답하고, 뒤의 질문엔 현실이 답합니다. 성공한 회사
          다수가 모놀리스로 시작해 <strong>아픈 지점이 증명된 뒤에야</strong> 쪼갰다는
          사실은 우연이 아니에요.
        </div>
      </Section>

      <Section n={2} title="분산의 세금" sub="네트워크를 건너는 순간 내는 비용">
        <p>
          모놀리스에서 <span className="icode">재고확인(상품ID)</span>는 그냥 함수
          호출이에요. 마이크로서비스에서 같은 일은 <strong>네트워크 호출</strong>이 되고,
          그 순간 세 가지 세금이 자동으로 청구됩니다. <strong>지연</strong> — 나노초가
          밀리초로, 수만 배 느려져요. <strong>부분 실패</strong> — "주문은 됐는데 재고
          차감 응답을 못 받았다"라는, 모놀리스엔 존재하지 않던 상태가 생깁니다. 재시도해야
          할까요? 이미 차감됐다면 두 번 차감되는데요. <strong>버전 관리</strong> — 호출자와
          피호출자가 따로 배포되니, 응답 필드 하나 바꾸는 것도 이제 '계약 변경'이에요.
        </p>
        <Code>{CODE_TAX}</Code>
        <p>
          이 세금을 다 내고도 보상(독립 배포·자율성·부분 확장)을 못 받는 구조가
          있는데, 그게 바로 <strong>분산 모놀리스</strong>입니다. 쪼개긴 쪼갰는데 A를
          배포하려면 B·C도 같이 배포해야 하고, 여러 서비스가 한 테이블을 같이 쓰고,
          기능 하나에 PR 네 개가 동시에 필요한 상태 — <strong>모놀리스의 결합도에
          마이크로서비스의 운영 비용을 더한</strong>, 두 세계의 나쁜 점만 모은 형태예요.
        </p>
        <Code>{CODE_DIST_MONOLITH}</Code>
        <div className="tip">
          <b>운영 비용은 선형으로 늘지 않는다</b> 서비스가 6개면 파이프라인·대시보드·알림
          규칙이 6벌이지만, <strong>장애 조사 경로는 조합적으로</strong> 늘어요. "느려요"
          한 마디에 의심할 구간이 서비스 수 × 통신 구간만큼 생기니까요. 분산 추적(trace
          ID를 요청에 실어 전 구간을 잇는 것)이 사치가 아니라 생존 도구가 되는 이유입니다.
        </div>
      </Section>

      <Section n={3} title="서비스 경계 긋기" sub="도메인 주도 설계의 도구들">
        <p>
          쪼개기로 했다면, 다음 질문은 <strong>"어디서 자르나"</strong>예요. 여기서 도메인
          주도 설계(DDD)의 <strong>바운디드 컨텍스트(Bounded Context)</strong>가 칼이
          됩니다. 찾는 법은 의외로 언어적이에요 —{' '}
          <strong>같은 단어가 다른 뜻이 되는 지점이 경계입니다.</strong> 쇼핑몰의
          '상품'이라는 한 단어가 카탈로그 팀에겐 사진과 검색 키워드, 재고 팀에겐 SKU와
          창고 위치, 주문 팀에겐 가격 스냅샷, 배송 팀에겐 무게와 부피를 뜻한다면 — 그
          네 덩어리가 각각 하나의 컨텍스트예요.
        </p>
        <Code>{CODE_BOUNDED}</Code>
        <p>
          경계를 그었다면 지키는 규칙은 하나, <strong>데이터 소유권 원칙</strong>입니다.{' '}
          <strong>한 테이블의 주인은 정확히 한 서비스.</strong> 남의 데이터가 필요하면
          주인의 API를 부르거나 이벤트를 구독하지, 절대 그 테이블에 직접{' '}
          <span className="icode">SELECT</span>하지 않아요. 직접 접근을 한 번 허용하는
          순간 스키마 변경이 남의 서비스를 깨뜨리게 되고, 경계는 그림에만 남습니다.
        </p>
        <p>
          이론을 들었으니 <strong>실물</strong>을 봅시다. 우리가 운영하는 트레이딩 플랫폼
          traderbox는 6개 서비스로 나뉘어 있는데, 그 경계 근거를 읽어 보면 명사(주문·상품)가
          아니라 <strong>트래픽 패턴·배포 주기·데이터 수명주기가 갈라지는 지점</strong>에서
          잘랐다는 걸 알 수 있어요.
        </p>
        <Code>{CODE_TRADERBOX}</Code>
        <div className="tip">
          <b>경계의 좋은 냄새 vs 나쁜 냄새</b> 좋은 경계는 "이 서비스는 혼자 배포해도
          아무도 신경 안 쓴다"로 증명돼요. 나쁜 경계의 전형은{' '}
          <strong>기술 계층으로 자르기</strong>(화면 서비스·비즈니스 서비스·DB 서비스) —
          기능 하나를 고치는데 세 서비스를 전부 건드리게 되죠. 자를 땐 세로(도메인)로,
          가로(계층)로 자르지 마세요.
        </div>
      </Section>

      <Section n={4} title="서비스 간 통신 설계" sub="동기 vs 비동기, 그리고 게이트웨이의 자리">
        <p>
          경계를 그었으면 이제 경계를 <strong>건너는 방법</strong>을 정해야 해요. 기준은
          단순합니다. <strong>지금 당장 답이 필요한가?</strong> 필요하면{' '}
          <strong>동기</strong>(REST 등 직접 호출) — 대신 상대가 죽으면 나도 기다리다
          실패하는 결합을 감수합니다. 답이 필요 없고 "이런 일이 있었다"만 알리면 되면{' '}
          <strong>비동기</strong>(이벤트) — 발행자는 소비자가 죽어 있어도 멈추지 않지만,
          "언젠가 처리된다"는 최종 일관성을 감수하죠.
        </p>
        <Code>{CODE_COMM}</Code>
        <p>
          traderbox의 규칙을 다시 읽어 보면 세 채널이 역할별로 갈려 있어요.{' '}
          <strong>동기는 <span className="icode">/internal/**</span> + 공유키</strong> —
          내부 호출이 게이트웨이의 외부 인증 스택을 다시 탈 이유가 없으니 서비스끼리
          직접 부르되, 공유키 헤더로 "내부에서 온 요청"임을 증명합니다.{' '}
          <strong>비동기·감사는 Kafka</strong> — 감사 로그 적재가 느려져도 본 서비스는
          영향받지 않아요. <strong>실시간 팬아웃은 Valkey Pub/Sub</strong> — 시세 틱을
          다수의 구독자에게 즉시 뿌립니다. 그리고 금기 하나:{' '}
          <strong>같은 데이터를 두 채널로 흘리지 않는다.</strong> 두 경로가 어긋나는 순간
          "어느 쪽이 진실인가"라는 답 없는 질문이 시작되니까요.
        </p>
        <p>
          마지막 조각이 <strong>API 게이트웨이</strong>예요. 클라이언트가 서비스 6개의
          주소를 다 알 필요는 없죠. 게이트웨이가 단일 진입점이 되어{' '}
          <strong>라우팅</strong>(경로를 보고 뒤의 서비스로 전달), <strong>인증 위임</strong>
          (토큰 검사를 어디서 할지 정하기 — traderbox는 각 서비스가 자체 검증하는 방식),{' '}
          <strong>횡단 관심사</strong>(레이트리밋·CORS·로깅처럼 모든 요청에 공통인 일)를
          맡습니다. 단, 게이트웨이에 도메인 로직을 넣기 시작하면 그게 새로운 모놀리스가
          돼요 — 게이트웨이는 <strong>얇게</strong>가 철칙입니다.
        </p>
        <div className="tip">
          <b>선택 감각 기르기</b> "회원가입 직후 환영 메일 발송"은 동기일까요 비동기일까요?
          가입 응답이 메일 서버를 기다릴 이유가 없죠 — <strong>비동기</strong>가 자연스러워요.
          반대로 "주문 직전 재고 확인"은 지금 답이 없으면 진행 자체가 불가능하니{' '}
          <strong>동기</strong>. 이렇게 <strong>업무 흐름이 답을 기다리는가</strong>로
          가르면 대부분 자명해집니다.
        </div>
      </Section>

      <Section n={5} title="트랜잭션이 쪼개질 때" sub="사가 패턴 — 보상으로 되돌리는 기술">
        <p>
          섹션 1에서 모놀리스가 공짜로 주던 것 중 가장 비싼 게{' '}
          <strong>하나의 트랜잭션</strong>이었어요. 주문 생성과 재고 차감이 서로 다른
          서비스, 서로 다른 DB로 갈라지는 순간 <span className="icode">BEGIN~COMMIT</span>
          으로 묶을 방법이 사라집니다. 교과서엔 <strong>2단계 커밋(2PC)</strong>이라는
          답이 있지만 — 코디네이터가 참여자 전원의 합의를 받을 때까지{' '}
          <strong>전부가 락을 잡고 대기</strong>하는 구조라, 하나만 느려져도 전체가 멈추고
          코디네이터가 죽으면 락이 고아가 돼요. 가용성을 얻으려고 쪼갠 시스템에 전역 락을
          다시 끼얹는 셈이라 실무에서 기피됩니다.
        </p>
        <p>
          실무의 답은 <strong>사가(Saga) 패턴</strong>이에요. 발상의 전환은 이렇습니다 —
          전체를 한 번에 원자적으로 묶으려 하지 말고, <strong>각 서비스의 로컬 트랜잭션을
          연쇄</strong>시키되, 중간에 실패하면 <strong>이미 커밋된 앞 단계들을 '보상
          트랜잭션'으로 역순으로 되돌린다.</strong> 재고를 차감했다면 복원하고, 주문을
          만들었다면 취소로 마킹하는 식이죠.
        </p>
        <Code>{CODE_SAGA}</Code>
        <p>
          여기서 설계 감각이 드러나는 지점이 <strong>단계의 순서</strong>예요. 보상은
          공짜가 아니고, 어떤 단계는 <strong>보상이 아예 불가능</strong>합니다. 발송된
          이메일은 거둘 수 없고, 외부 PG의 카드 승인 취소는 별개의 복잡한 절차예요.
          그래서 원칙은 — <strong>보상이 불가능하거나 비싼 단계는 사가의 마지막에
          배치한다.</strong> 마지막 단계 뒤에는 실패할 것이 없으니, 보상할 일도 없거든요.
        </p>
        <div className="tip">
          <b>보상은 '삭제'가 아니라 '상쇄'다</b> 보상 트랜잭션은 앞 단계를 없던 일로
          지우는 게 아니라, <strong>반대 방향의 새 기록을 추가해 상쇄</strong>하는 거예요.
          회계 장부에서 잘못된 기입을 지우개로 지우지 않고 반대 분개를 넣는 것과 같죠.
          "주문 취소됨"이라는 기록이 남는 것 — 그게 오히려 장애 조사와 감사에서
          자산이 됩니다.
        </div>
      </Section>

      <Section n={6} title="스키마-퍼-서비스와 데이터 조회 문제" sub="DB를 나누는 단계적 접근, 그리고 JOIN이 그리워질 때">
        <p>
          "서비스마다 DB를 따로 둬야 한다"는 말을 자주 듣지만, 처음부터 DB 인스턴스를
          6대 운영하는 건 부담이 커요. <strong>단계적 접근</strong>이 있습니다 —{' '}
          <strong>단일 DB 안에서 스키마로 먼저 격리</strong>하는 거예요. traderbox가 정확히
          이 방식이죠(단일 PostgreSQL + 스키마-퍼-서비스). 인프라는 한 대라 운영이 가볍고,
          각 서비스 계정에 자기 스키마 권한만 주면 <strong>"남의 테이블에 JOIN 못 함"이
          DB 수준에서 강제</strong>됩니다. 나중에 특정 서비스가 커지면 그 스키마만 별도
          인스턴스로 이사시키면 되고요 — 코드는 이미 경계를 지키고 있으니까요.
        </p>
        <Code>{CODE_SCHEMA}</Code>
        <p>
          격리하고 나면 반드시 그리워지는 게 <strong>JOIN</strong>이에요. "주문 목록에
          상품 이름을 붙여 보여 줘" — 모놀리스에선 JOIN 한 줄이던 게 이제 서비스 두 개에
          걸칩니다. 선택지는 셋이에요. <strong>① API 합성</strong> — 주문 목록을 받아
          상품 API를 이어 부르기. 간단하지만 목록이 100건이면 호출이 100번 튀는 N+1을
          조심해야 해요. <strong>② 데이터 복제</strong> — 상품 이름 정도는 주문 스키마에
          복사해 두고, 상품 변경 이벤트를 구독해 갱신하기. 조회는 빠르지만 복제 지연을
          받아들여야 하죠. <strong>③ 읽기 전용 뷰</strong> — 조회 전용 서비스가 여러
          서비스의 이벤트를 구독해 화면에 맞는 조합 테이블을 미리 만들어 두기(CQRS로
          이어지는 발상).
        </p>
        <p>
          그리고 절대 고르면 안 되는 네 번째 선택지가 <strong>공유 DB 안티패턴</strong>
          입니다. "그냥 옆 서비스 테이블에 직접 JOIN하면 되잖아?" — 되긴 돼요. 대신
          그 순간 무너지는 것들: <strong>스키마 변경의 자유</strong>(주인이 컬럼 하나
          바꾸면 남의 서비스가 깨짐), <strong>독립 배포</strong>(스키마 변경에 두 서비스
          배포를 맞춰야 함), <strong>캡슐화</strong>(비즈니스 규칙을 우회해 데이터를
          읽으니 "이 값이 왜 이래?"의 원인 추적 불가). 쪼갠 이유 전부가 여기서 증발합니다.
        </p>
        <div className="tip">
          <b>참고 — 시세는 왜 Postgres가 아닌가</b> traderbox에서 공시·아티클·거시 지표는
          Postgres에 있지만 <strong>시세는 Valkey(인메모리)</strong>에 있어요. 초당 수십
          번 갱신되고 1초 전 값은 가치가 없는 데이터라, 영속성보다 속도가 우선이기
          때문이죠. 데이터 소유권 원칙은 "서비스마다 자기 데이터"에서 한 발 더 나아가{' '}
          <strong>"데이터 성격에 맞는 저장소"</strong>까지 갑니다.
        </div>
      </Section>

      <Section n={7} title="전환 전략 — 한 번에 다시 쓰지 마라" sub="스트랭글러 무화과, 모듈러 모놀리스, 그리고 체크리스트">
        <p>
          마지막 질문 — 이미 굴러가는 모놀리스가 있다면 <strong>어떻게</strong> 쪼개나요?
          가장 유혹적이고 가장 위험한 답이 <strong>빅뱅 재작성</strong>("전부 멈추고
          마이크로서비스로 새로 짓자")이에요. 기존 기능 전부를 재현하는 동안 제품은
          멈춰 있고, 그 사이 요구사항은 계속 바뀌고, 재현이 끝날 즈음엔 목표 자체가
          이동해 있죠. 거의 항상 집니다.
        </p>
        <p>
          실무의 답은 <strong>스트랭글러 무화과(Strangler Fig) 패턴</strong>이에요. 숙주
          나무를 감고 자라다 결국 대체해 버리는 무화과나무처럼 — 게이트웨이(프록시)를
          앞에 세우고, <strong>가장자리의 기능 하나</strong>(예: 알림)만 새 서비스로 빼서
          그 경로만 새 서비스로 라우팅합니다. 잘 되면 다음 조각, 또 다음 조각.{' '}
          <strong>매 단계가 배포 가능하고, 문제가 생기면 라우팅만 되돌리면</strong> 되죠.
        </p>
        <Code>{CODE_STRANGLER}</Code>
        <p>
          그보다 먼저 할 수 있는 게 <strong>모듈러 모놀리스</strong>입니다. 쪼개기 전에{' '}
          <strong>경계를 코드 안에서 먼저 증명</strong>하는 거예요. 한 배포 단위 안에서
          패키지 경계를 긋고(<span className="icode">order</span>·
          <span className="icode">inventory</span>·<span className="icode">payment</span>),
          모듈 간에는 공개 인터페이스로만 호출하게 강제합니다. 이 경계가 여섯 달을
          버티면 네트워크로 승격해도 살아남을 경계고, 계속 뚫린다면 — 쪼갰어도 분산
          모놀리스가 됐을 거라는 뜻이니, 배포 한 번 없이 그걸 미리 알아낸 겁니다.
        </p>
        <p>
          그리고 쪼개기 결정 자체는 감이 아니라 <strong>체크리스트</strong>로 하세요.{' '}
          <strong>① 배포 충돌 빈도</strong> — 서로 다른 팀의 배포가 주기적으로 서로를
          막는가? <strong>② 확장 비대칭</strong> — 특정 부분만 트래픽이 수십 배인데
          전체를 복제하고 있는가?(traderbox의 market이 정확히 이 근거로 분리된 사례){' '}
          <strong>③ 팀 크기</strong> — 한 코드베이스에 사람이 많아 머지와 리뷰가
          병목인가? <strong>셋 다 아니라면, 아직 쪼갤 때가 아닙니다.</strong> 그때 모놀리스를
          유지하는 건 게으름이 아니라 판단이에요 — 이 코스 제목의 '쪼개지 않을 용기'가
          바로 이것입니다.
        </p>
        <div className="tip">
          <b>실습 안내</b> 가상의 쇼핑몰 모놀리스(회원·카탈로그·주문·재고·결제·배송·리뷰가
          한 덩어리)를 받아 <strong>바운디드 컨텍스트 지도를 그리고</strong>,{' '}
          <strong>"지금 쪼갤 것 1개 / 쪼개지 않을 것 3개"</strong>를 근거와 함께 발표하세요.
          채점 기준은 정답이 아니라 <strong>근거의 품질</strong>입니다 — "요즘 다들
          쪼개니까"는 0점, "이 모듈만 트래픽이 30배라 확장 비대칭이 증명됐다"는 만점.
          쪼개지 않을 것 3개의 근거가 쪼갤 것 1개의 근거만큼 단단해야 통과예요.
        </div>
      </Section>

      {/* 마무리 카드 — 다음 강좌 안내 */}
      <div className="step-card">
        <div className="step-body">
          <h3 style={{ marginBottom: 6 }}>🧩 여기까지 왔다면</h3>
          <p className="muted">
            이제 "마이크로서비스 하시나요?"라는 질문에 기술 이름이 아니라{' '}
            <strong>트레이드오프의 언어</strong>로 답할 수 있게 됐어요. 모놀리스가 공짜로
            주던 세 가지, 네트워크를 건너는 순간 청구되는 세금, 같은 단어가 갈라지는
            지점에 긋는 경계, 채널 셋으로 정리한 통신 규칙, 보상으로 되돌리는 사가,
            스키마로 먼저 격리하는 단계적 접근, 그리고 가장자리부터 갈아끼우는 전환까지 —
            쪼갤 근거와 쪼개지 않을 근거를 모두 손에 쥐었습니다. 그런데 서비스를 아무리
            잘 나눠도, 트래픽이 정말 커지면 결국 <strong>데이터베이스 자체</strong>가
            마지막 병목으로 남아요. 읽기를 복제로 늘릴 것인가, 쓰기를 샤딩으로 쪼갤
            것인가 — 다음{' '}
            <Link to="/learn/lv4-db-replication-sharding"><strong>DB 레플리케이션과 샤딩 —
            데이터베이스를 늘리는 두 가지 축</strong></Link> 코스에서 그 두 축을 이어서
            익혀 봅시다.
          </p>
        </div>
      </div>
    </div>
  );
}
