이 글이 다루는 문제 — 가장 단순해 보였던 기능이 왜 위험했는가

도메인 구조를 한 번 정리하고 나면, 사람은 자연스럽게 안심하게 된다. 복잡하게 얽혀 있던 개념들이 역할별로 나뉘고, 각 객체가 무엇을 책임지는지가 명확해지면, 이제 남은 건 그 위에 기능을 얹는 일이라고 생각하게 된다. 실제로도 그렇게 보인다. ReadingRecord는 상태를 관리하고, ReadingSession은 시간과 수치를 담당하며, TimelineEvent는 사실을 기록한다. 역할은 분리되었고, 경계도 선명하다. 이 정도면 설계는 어느 정도 안정된 것처럼 느껴진다. 하지만 이 시점에서 놓치기 쉬운 것이 하나 있다. 구조는 정리되었지만, 아직 그 구조 위에서 “무엇이 일어나는지”는 검증되지 않았다는 점이다.

이 글이 다루는 문제는 바로 그 지점에서 시작된다. 구조는 맞아 보였지만, 실제 동작을 얹는 순간 예상하지 못한 복잡성이 드러났다. 그 첫 번째 충돌 지점이 바로 toggleSession이었다. 겉으로 보면 이건 기능이라고 부르기도 애매한 수준이다. 버튼 하나, 상태 하나, 분기 하나. 읽기 시작과 종료를 번갈아 수행하는 아주 단순한 인터랙션이다. 대부분의 경우 이런 기능은 설계의 중심이 아니라 주변부에 위치한다. 그래서 더 위험하다. 중요하지 않다고 판단된 순간, 우리는 그 안에 숨어 있는 복잡성을 검증하지 않는다.

문제는 toggleSession이 단순한 UI 동작이 아니라는 데 있었다. 이 버튼은 단순히 상태를 바꾸는 역할을 하는 것이 아니라, 도메인 전체를 통과하는 진입점이었다. 사용자는 버튼 하나만 누르지만, 그 뒤에서는 세션의 생성과 종료, 진행률 업데이트, 이벤트 기록, 상태 변경이 모두 연결되어 실행된다. 즉, 이 버튼은 시스템의 “입구”에 가까운 역할을 한다. 이 입구에서 어떤 정책이 실행되는지에 따라, 전체 데이터의 정합성이 결정된다. 이 지점에서 처음으로 하나의 의문이 생긴다. 이걸 정말 단순하게 처리해도 되는가.

이 질문은 단순한 의심이 아니라, 이후 모든 설계 수정의 출발점이 된다. 지금까지는 구조를 정리하는 단계였다면, 이제부터는 그 구조가 실제로 어떻게 동작하는지를 검증하는 단계로 넘어간다. 그리고 그 과정에서 드러나는 것은, 우리가 “단순하다”고 믿었던 가정이 얼마나 쉽게 무너질 수 있는지에 대한 이야기다.

처음 가정 — 토글은 if-else 하나면 충분하다

처음 toggleSession을 떠올렸을 때의 사고는 놀랄 만큼 단순했다. activeSession이 존재하면 종료하고, 없으면 시작하면 된다. 이보다 더 간단할 수는 없다고 느껴졌다. 실제로 이 구조는 대부분의 타이머나 세션 기반 기능에서 흔히 사용되는 패턴이기도 하다. 그래서 이 가정은 의심 없이 받아들여졌다. 복잡한 도메인 설계를 거친 직후였기 때문에, 오히려 이런 단순한 기능 하나쯤은 쉽게 처리할 수 있어야 한다는 생각이 더 강하게 작용했다.

그때 머릿속에 있던 모델은 거의 이 코드 한 줄로 설명될 수 있었다.

if (activeSession exists)
    endSession()
else
    startSession()

이 구조는 너무나 자연스럽다. 상태가 있으면 종료, 없으면 시작. 조건도 명확하고, 분기도 단순하다. 무엇보다도 이 코드에는 고민할 여지가 없어 보인다. 이 시점에서는 이 단순함이 설계가 잘 되었기 때문이라고 생각했다. 복잡한 도메인을 잘게 나누었기 때문에, 각 기능은 이렇게 간결하게 표현될 수 있다고 믿었다. 즉, 이 코드는 설계가 잘 되었다는 증거처럼 보였다.

하지만 실제로는 그 반대였다. 이 코드는 문제를 해결한 결과가 아니라, 문제를 숨기고 있는 상태였다. activeSession이라는 단일 조건으로 모든 상황을 설명하려는 시도는, 그 안에 존재하는 다양한 상태와 예외를 의도적으로 무시하는 접근이었다. 우리는 종종 단순한 인터페이스를 보면, 내부도 단순할 것이라고 착각한다. 하지만 이 경우에는 인터페이스의 단순함이 오히려 내부 복잡성을 가리는 역할을 하고 있었다.

이 가정이 위험한 이유는, 너무 쉽게 받아들여진다는 점이다. 코드가 짧고 명확할수록, 우리는 그것을 더 이상 의심하지 않는다. 하지만 도메인이 복잡할수록, 그런 단순한 조건 하나로 모든 상황을 처리하는 것은 거의 불가능에 가깝다. 이 시점에서는 아직 그 사실을 체감하지 못했지만, 실제 구현을 시작하는 순간 이 가정은 빠르게 무너지기 시작한다. 그리고 그 붕괴는 한 번에 일어나지 않고, 작은 질문들이 쌓이면서 점진적으로 진행된다.

붕괴의 시작 — 질문이 붙기 시작하는 순간

코드를 실제로 작성하려고 한 순간, 예상하지 못했던 질문들이 하나씩 붙기 시작했다. 처음에는 사소해 보였다. 세션을 종료할 때 진행률은 어디서 입력받을 것인가. 입력이 없다면 기본값을 어떻게 처리할 것인가. 사용자가 버튼을 연속으로 눌렀을 때 중복 처리는 어떻게 할 것인가. 이런 질문들은 각각 보면 작고 독립적인 문제처럼 보인다. 그래서 처음에는 “이건 나중에 처리하면 되겠지” 정도로 넘기기 쉽다. 하지만 문제는 이 질문들이 서로 연결된다는 데 있다.

하나의 질문을 해결하려고 하면, 그 해결 방식이 다른 질문을 만들어낸다. 예를 들어 진행률을 선택적으로 입력받도록 하면, null 상태를 처리해야 한다. null을 허용하면, 완료 상태를 어떻게 판단할지 불명확해진다. 완료 상태를 자동으로 판단하려고 하면, 이벤트 생성 시점이 바뀐다. 이벤트 생성 시점이 바뀌면, 중복 이벤트 처리 문제가 생긴다. 이처럼 하나의 선택이 다른 문제를 강제하는 구조가 만들어진다. 이 시점에서 더 이상 toggleSession은 단순한 분기문으로 유지될 수 없다.

이 과정에서 중요한 변화가 하나 일어난다. 문제의 성격이 바뀐다. 처음에는 이걸 “버튼 동작 구현”이라고 생각했다. 하지만 질문이 쌓이면서, 이건 UI 레벨의 문제가 아니라 도메인 정책의 문제라는 사실이 드러난다. 버튼은 단순히 트리거일 뿐이고, 실제로 중요한 것은 그 버튼이 어떤 규칙을 실행시키느냐이다. 즉, toggleSession은 기능이 아니라 정책 실행 지점이 된다. 이 인식 전환이 일어나는 순간, 기존의 if-else 구조는 더 이상 유효하지 않게 된다.

이 단계에서 이미 붕괴는 시작된 상태다. 아직 코드는 완전히 깨지지 않았지만, 그 코드를 유지하기 위한 가정이 더 이상 성립하지 않는다. 중요한 것은 이 붕괴가 “버그” 때문이 아니라, “가정의 부족” 때문에 발생했다는 점이다. 우리는 단순한 문제라고 믿었지만, 실제로는 복잡한 정책을 포함하고 있었다. 그리고 그 사실은 구현을 시작하고 나서야 드러난다. 이 지점에서 다음 단계로 넘어간다. 이제는 이 복잡성을 억지로 숨기는 것이 아니라, 구조적으로 받아들이고 다시 정의해야 한다.

문제의 본질 — 토글은 상태 변경이 아니라 정책 실행이다

질문이 쌓이기 시작한 순간, 더 이상 기존의 관점으로 이 문제를 바라볼 수 없게 된다. 처음에는 단순히 상태를 전환하는 기능이라고 생각했지만, 실제로는 그 안에 훨씬 더 많은 의미가 들어 있었다. activeSession의 존재 여부는 단순한 조건이 아니라, 시스템의 현재 상태를 압축한 표현이었다. 그리고 그 상태에서 어떤 행동을 수행할 것인지는 단순한 분기가 아니라, 이미 정의된 여러 정책을 실행하는 과정이었다. 즉, toggleSession은 상태를 바꾸는 버튼이 아니라, 도메인 규칙을 통과시키는 진입점이었다.

이 차이를 이해하는 것이 중요하다. 상태 변경은 결과에 집중한다. 어떤 값이 바뀌었는가, 어떤 필드가 업데이트되었는가가 핵심이다. 하지만 정책 실행은 과정에 집중한다. 어떤 조건을 검증했는지, 어떤 순서로 처리했는지, 그리고 어떤 규칙이 적용되었는지가 더 중요하다. 이 차이는 코드의 형태에도 그대로 드러난다. 상태 변경 중심의 코드는 짧고 단순하다. 하지만 정책 실행 중심의 코드는 길어지고, 단계가 생기며, 각 단계가 의미를 가지게 된다.

이 시점에서 toggleSession을 다시 정의할 필요가 생긴다. 이건 더 이상 “시작/종료를 토글하는 기능”이 아니다. 이건 “현재 상태를 기반으로, 적절한 도메인 절차를 실행하는 흐름”이다. 이 정의는 단순히 표현의 변화가 아니라, 설계 방향 자체를 바꾼다. 이제 우리는 버튼이 무엇을 하는지 설명하는 대신, 이 버튼이 어떤 정책을 실행하는지를 설명해야 한다. 그리고 그 정책은 단일 조건으로 표현될 수 없는 복잡성을 가진다.

이렇게 관점을 바꾸고 나면, 이전까지 보이지 않던 문제들이 자연스럽게 설명되기 시작한다. 왜 null 처리가 필요했는지, 왜 중복 이벤트가 발생할 수 있었는지, 왜 종료 로직이 복잡해지는지 모두 하나의 맥락으로 연결된다. 결국 문제는 코드가 아니라, 우리가 그 코드를 어떤 관점에서 바라보고 있었는가였다. 그리고 이 지점에서 다음 단계로 넘어간다. 이제는 이 정책이 실제로 어떻게 실행되어야 하는지를 구체적으로 드러내야 한다.

세션 종료의 폭발 — 단순 동작이 절차로 바뀌는 순간

이 변화는 특히 세션 종료 로직에서 극명하게 드러난다. 세션 시작은 비교적 단순하다. 새로운 세션을 생성하고, 시작 시간을 기록하고, 필요한 이벤트를 남기면 된다. 이 과정은 거의 직선적인 흐름으로 표현할 수 있다. 하지만 종료는 완전히 다르다. 종료는 단순히 endedAt 값을 채우는 행위가 아니다. 종료는 여러 상태를 동시에 정리하고, 그 결과에 따라 다른 상태를 만들어내는 분기점이다.

실제로 종료 로직을 구성하려고 하면, 생각보다 많은 단계가 필요하다. 먼저 입력값을 받아야 하고, 그 값이 유효한지 검증해야 한다. 이후 세션을 업데이트하고, 이벤트를 생성하며, 필요하다면 기록의 상태를 변경해야 한다. 그리고 이 모든 과정은 하나의 트랜잭션 안에서 처리되어야 한다. 이 중 어느 하나라도 빠지거나 순서가 바뀌면, 데이터의 정합성이 깨질 수 있다. 즉, 종료는 단순한 동작이 아니라, 여러 정책이 결합된 절차다.

이 절차의 복잡성은 특히 idempotency 문제에서 극단적으로 드러난다. 사용자가 버튼을 두 번 누르는 상황은 매우 흔하다. 네트워크 지연이나 UI 반응 속도 문제로 인해 동일한 요청이 여러 번 발생할 수 있다. 이때 종료 로직이 두 번 실행되면 어떤 일이 벌어질까. 동일한 이벤트가 중복 생성될 수 있고, 상태가 두 번 변경될 수 있으며, 통계 데이터가 왜곡될 수 있다. 이 문제는 단순히 “중복 방지” 조건 하나로 해결되지 않는다. 전체 절차가 중복 실행되더라도 결과가 동일하게 유지되도록 설계되어야 한다.

이 지점에서 하나의 사실이 명확해진다. 우리가 처음 생각했던 “토글”은 실제로 존재하지 않는다. 존재하는 것은 시작과 종료라는 두 개의 서로 다른 절차이며, 이 둘은 완전히 다른 복잡도를 가진다. 토글이라는 표현은 이 둘을 하나로 묶기 위해 사용된 단순화된 개념일 뿐이다. 그리고 이 단순화가 문제를 숨기고 있었다. 이제는 이 단순화를 버리고, 각 절차를 있는 그대로 드러내야 한다.

해결이 아니라 재정의 — 토글을 ‘flow’로 바꾸다

이 단계에서 필요한 것은 문제를 해결하는 것이 아니라, 문제를 다시 정의하는 것이다. 기존의 if-else 구조를 유지한 채로 조건을 추가하는 방식은 더 이상 유효하지 않다. 그 방식은 문제를 해결하는 것이 아니라, 문제를 더 깊게 숨기는 결과를 만든다. 대신 우리는 이 구조 자체를 버려야 한다. 토글이라는 개념을 유지한 채로 코드를 수정하는 것이 아니라, 토글이라는 개념 자체를 재해석해야 한다.

그래서 등장한 것이 flow 기반 구조다. 이제 toggleSession은 단일 함수가 아니라, 두 개의 명확한 흐름으로 나뉜다. 하나는 시작 흐름이고, 다른 하나는 종료 흐름이다. 이 흐름은 단순한 분기가 아니라, 각각 독립된 절차를 가진다. activeSession의 존재 여부는 더 이상 “무엇을 할지”를 결정하는 조건이 아니라, “어떤 흐름을 선택할지”를 결정하는 기준이 된다. 이 변화는 코드 구조뿐 아니라, 사고 방식 자체를 바꾼다.

check activeSession
→ start flow OR end flow
→ validate inputs
→ execute domain policies
→ create events
→ update record

이 구조에서 중요한 것은 각 단계가 명확한 의미를 가진다는 점이다. validate는 단순한 입력 체크가 아니라 정책의 전제 조건을 보장하는 단계이고, 이벤트 생성은 단순한 로그가 아니라 시스템의 상태 변화를 기록하는 행위다. 그리고 이 모든 단계는 독립적으로 존재하는 것이 아니라, 하나의 흐름 안에서 연결된다. 이 연결이 바로 시스템의 안정성을 만든다.

이제 toggleSession은 더 이상 단순한 버튼 동작이 아니다. 이건 도메인 정책을 실행하는 하나의 입구이며, 그 안에서 여러 흐름이 선택되고 실행되는 구조다. 이 재정의를 통해서만, 앞에서 드러났던 모든 문제를 일관된 방식으로 다룰 수 있게 된다. 그리고 이 시점에서 비로소, 이전 글에서 정의했던 도메인 분리가 실제로 어떤 의미를 가지는지가 드러나기 시작한다.

도메인 분리가 실제로 작동하는 순간

토글을 flow로 재정의하고 나서야 비로소 하나의 사실이 드러난다. 이전 단계에서 수행했던 도메인 분리가 단순한 구조 정리가 아니라는 점이다. 그때는 역할을 나누는 것이 주된 목적처럼 보였다. ReadingRecord는 상태를 관리하고, ReadingSession은 시간과 수치를 담당하며, TimelineEvent는 사실을 기록한다는 식의 구분은 일종의 설계 원칙처럼 느껴졌다. 하지만 그 설계가 실제로 의미를 가지는지는, 그 위에 동작을 올려보지 않으면 알 수 없다. toggleSession을 flow로 재구성하는 과정은 바로 그 검증 단계였다.

이 과정에서 가장 먼저 드러난 것은 책임의 경계였다. 종료 로직을 구성하면서, 각 데이터가 어디에 있어야 하는지가 명확하게 구분되기 시작한다. 진행률은 세션에만 존재해야 하고, 완료 상태는 레코드에서만 관리되어야 하며, 이벤트는 이벤트 레이어에서만 생성되어야 한다. 이 구분이 명확해질수록, 각 단계에서 무엇을 해야 하는지도 자연스럽게 정리된다. 반대로 이 경계가 흐려지면, 동일한 데이터가 여러 곳에서 관리되거나, 서로 다른 레이어가 같은 책임을 공유하게 된다. 그 순간부터 정합성은 깨지기 시작한다.

여기서 중요한 점은, 이 구조가 “깔끔함”을 위해 존재하는 것이 아니라는 것이다. 우리는 흔히 레이어를 나누거나 객체를 분리하는 이유를 가독성이나 유지보수성에서 찾는다. 하지만 이 경우에는 그보다 더 근본적인 이유가 있다. 바로 정합성을 유지하기 위한 구조라는 점이다. 각 책임이 명확하게 나뉘어 있을 때만, 복잡한 정책이 여러 단계에 걸쳐 실행되더라도 결과가 일관되게 유지된다. 만약 진행률이 Record에도 있고 Session에도 있었다면, 어느 값을 기준으로 완료를 판단해야 할지 불명확해졌을 것이다. 이런 모호함은 결국 버그로 이어진다.

이 지점에서 도메인 분리는 더 이상 추상적인 설계 원칙이 아니다. 실제 동작을 안정적으로 유지하기 위한 필수 조건이 된다. 그리고 이 구조 덕분에, 우리는 복잡한 flow를 구성하면서도 각 단계의 책임을 혼동하지 않을 수 있게 된다. 이전까지는 구조가 이론이었다면, 이제는 그 구조가 실제 문제를 해결하는 도구로 작동하기 시작한다.

코드가 아니라 정책이다 — 의사코드로 드러나는 구조

flow 기반으로 구조를 재정의한 이후, 이를 코드로 옮기는 과정에서 하나의 흥미로운 변화가 나타난다. 코드가 더 이상 단순한 로직이 아니라, 정책을 표현하는 수단으로 보이기 시작한다. 이전에는 코드가 짧을수록 좋다고 생각했다. 간결한 조건문과 최소한의 분기만으로 기능을 구현하는 것이 좋은 코드라고 여겼다. 하지만 이 단계에서는 그 기준이 더 이상 유효하지 않다. 오히려 각 단계가 명확하게 드러나는 코드가 더 좋은 코드가 된다.

예를 들어 종료 로직을 의사코드로 표현하면, 다음과 같은 형태를 가지게 된다.

function endSession(recordId, inputProgress):
    record = findRecord(recordId)

    if (record.activeSessionId == null)
        return

    session = findSession(record.activeSessionId)

    if (session.endedAtUtc != null)
        return

    validate(inputProgress)

    session.endedAtUtc = nowUtc()
    session.progressPct = inputProgress
    save(session)

    createEvent(SESSION_ENDED)

    if (inputProgress == 100):
        record.status = COMPLETED
        createEvent(RECORD_COMPLETED)

    record.activeSessionId = null
    save(record)

이 코드는 표면적으로 보면 특별히 복잡해 보이지 않는다. 하지만 중요한 것은 이 안에 담겨 있는 의미다. 각 단계는 단순한 처리 과정이 아니라, 하나의 정책을 나타낸다. validate는 입력값을 검사하는 것이 아니라, 도메인 규칙을 만족하는지를 확인하는 과정이다. createEvent는 로그를 남기는 것이 아니라, 시스템의 상태 변화를 외부에 드러내는 행위다. 그리고 record를 업데이트하는 부분은 단순한 필드 변경이 아니라, 시스템의 현재 상태를 재정의하는 과정이다.

이렇게 보면 코드의 길이는 더 이상 중요하지 않다. 중요한 것은 코드가 얼마나 많은 정책을 명확하게 표현하고 있는가이다. 짧은 코드가 항상 좋은 것은 아니다. 오히려 정책이 숨겨진 코드가 더 위험하다. 이 단계에서 우리는 코드의 역할을 다시 정의하게 된다. 코드는 기능을 구현하는 도구가 아니라, 도메인 정책을 명시적으로 드러내는 문서에 가깝다.

이 관점 변화는 이후의 모든 구현 방식에 영향을 준다. 우리는 더 이상 코드를 줄이려고 하지 않고, 오히려 정책이 잘 드러나도록 구조를 나눈다. 그리고 그 결과, 시스템 전체의 동작을 코드만으로도 추적할 수 있게 된다.

설계가 아니라 사고가 바뀐 순간

이 모든 과정을 거치고 나면, 겉으로 보이는 변화보다 더 중요한 변화가 하나 남는다. 그것은 코드의 구조가 아니라, 사고 방식의 변화다. 처음에는 toggleSession을 단순한 기능으로 보았고, 그 기능을 어떻게 구현할지를 고민했다. 하지만 지금은 완전히 다른 질문을 하게 된다. 이 동작은 어떤 정책을 실행하는가, 이 정책은 어떤 순서로 적용되어야 하는가, 그리고 이 흐름이 깨지면 어떤 문제가 발생하는가를 먼저 생각하게 된다.

이 변화는 단순히 더 나은 코드를 작성하게 만드는 수준이 아니다. 문제를 바라보는 방식 자체를 바꾼다. 이전에는 기능 단위로 사고했다면, 이제는 흐름과 정책 단위로 사고하게 된다. 그래서 동일한 문제를 보더라도, 접근 방식이 완전히 달라진다. 예를 들어 중복 실행 문제를 해결할 때도, 단순히 조건문을 추가하는 것이 아니라, 전체 흐름이 반복 실행되더라도 결과가 변하지 않도록 설계하려고 한다. 이 차이는 결과적으로 시스템의 안정성에 큰 영향을 준다.

이 과정에서 여러 선택이 바뀌었다. nullable로 두었던 값을 제거하고, 이벤트 생성을 UI 레벨에서 도메인 레벨로 이동시켰으며, activeSession을 암묵적으로 찾는 방식에서 명시적으로 관리하는 방식으로 전환했다. 이 변화들은 각각 보면 작은 수정처럼 보인다. 하지만 이 모든 변화는 하나의 공통된 방향을 가진다. 정책을 명확하게 드러내고, 흐름을 통제 가능한 형태로 만드는 것이다.

이 시점에서 도메인은 더 이상 불안정한 상태가 아니다. 아직 완벽하다고 말할 수는 없지만, 적어도 어떤 문제가 발생했을 때 어디를 봐야 하는지는 명확해진다. 그리고 이 명확함이 바로 설계의 안정성을 만든다. 이 글은 단순히 토글 로직을 구현한 기록이 아니다. 하나의 기능을 통해, 사고 방식이 어떻게 바뀌었는지를 보여주는 과정이다. 그리고 이 변화는 다음 단계로 이어진다. 이제 우리는 이 구조 위에서, 이벤트라는 또 다른 축을 어떻게 정의할 것인지 고민하게 된다.

이 글의 위치 — 첫 번째 ‘동작 붕괴’ 기록

여기까지의 흐름을 따라오면 하나의 분명한 전환점이 보인다. 이전 글에서는 도메인 구조를 정리하는 데 집중했다. 각 객체의 책임을 나누고, 상태와 기록을 분리하며, 전체 구조를 안정된 형태로 만들려고 했다. 그 과정은 설계의 문제였고, 비교적 정적인 영역이었다. 하지만 이 글에서 다룬 내용은 그와 성격이 다르다. 여기서는 구조 위에 실제 동작을 올리는 순간 어떤 일이 발생하는지를 다루었다. 그리고 그 결과는 예상과 달랐다. 구조는 맞았지만, 그 구조가 동작하는 방식은 전혀 단순하지 않았다.

이 글의 위치는 바로 그 지점에 있다. 이건 구조가 아니라 동작이 무너지는 순간을 기록한 첫 번째 사례다. toggleSession은 겉으로 보면 가장 단순한 기능 중 하나였지만, 실제로는 도메인 전체를 관통하는 흐름이었다. 그래서 이 기능을 구현하는 과정에서, 이전에 정의했던 모든 설계가 시험대에 올랐다. 그리고 그 과정에서 드러난 것은, 설계가 맞느냐 틀리냐의 문제가 아니라, 그 설계를 어떻게 실행하느냐의 문제였다. 즉, 구조만으로는 충분하지 않았고, 그 구조 위에서 동작이 어떻게 흘러야 하는지를 정의해야 했다.

이 지점에서 중요한 변화가 하나 더 생긴다. 이전까지의 붕괴는 개념적인 것이었다. 세션 모델이 맞지 않는다거나, 상태 중심 접근이 한계를 가진다는 식의 문제였다. 하지만 이번 붕괴는 훨씬 더 구체적이다. 실제 코드가 작성되려는 순간, 그 코드가 유지될 수 없는 구조라는 것이 드러난다. 이 차이는 중요하다. 개념적 붕괴는 방향을 수정하면 해결되지만, 동작 붕괴는 구조 자체를 다시 정의하지 않으면 해결되지 않는다. 그래서 이 글은 단순한 구현 기록이 아니라, 설계가 실행되는 과정에서 어떻게 다시 무너지는지를 보여주는 사례가 된다.

이 글이 시리즈에서 가지는 의미는 명확하다. #2가 구조를 재정렬한 글이었다면, 이 글은 그 구조가 실제로 동작할 때 어떤 문제가 발생하는지를 드러낸 글이다. 그리고 이 과정에서 하나의 새로운 기준이 만들어진다. 설계는 구조로만 평가할 수 없고, 반드시 동작을 통해 검증되어야 한다는 점이다. 이 기준은 이후의 모든 글에 영향을 미친다. 우리는 더 이상 “이 구조가 맞는가”를 묻지 않고, “이 구조가 실제로 어떻게 동작하는가”를 먼저 보게 된다.

이 흐름은 자연스럽게 다음 단계로 이어진다. toggleSession을 통해 드러난 문제는 결국 이벤트 처리 방식과 연결된다. 어떤 시점에 어떤 이벤트를 생성해야 하는지, 그리고 그 이벤트가 어떤 정보를 가져야 하는지에 따라 전체 흐름이 달라지기 때문이다. 그래서 다음 글에서는 TimelineEvent를 어떻게 정의했는지, 그리고 왜 이벤트를 가능한 한 얇게 유지하려 했는지를 다루게 된다. 지금까지가 동작이 무너지는 과정이었다면, 다음 단계는 그 동작을 다시 안정화시키기 위한 새로운 기준을 만드는 과정이 된다.