AI 에이전트는 나쁜 아키텍처를 해결하지 못합니다. 오히려 가속화할 뿐입니다.
요약
AI 에이전트가 코드를 빠르게 생성하더라도 시스템의 의미론적 경계가 불분명하면 오히려 나쁜 아키텍처를 가속화할 수 있습니다. 데이터 소유권과 상태 관리의 명확한 경계 정의가 AI 시대의 소프트웨어 설계에서 더욱 중요해지고 있습니다.
핵심 포인트
- AI 에이전트는 코드 생성 능력보다 시스템의 의미론적 경계 이해가 더 중요함
- 명확한 경계 없는 AI 코드는 유지보수가 불가능한 시스템을 빠르게 구축함
- 데이터 소유권, 상태 일관성, 진실의 원천(Source of Truth) 정의가 필수적임
- 에이전트는 직관적인 경로를 따르므로 설계자의 아키텍처 가이드가 필요함
이전 글들을 통해 저는 하나의 핵심적인 논점으로 계속 돌아왔습니다:
UI 렌더링이 더 이상 전체 데이터 흐름(data flow)을 소유하지 않게 되면, 상태(State), 파생 상태(Derived State), 이펙트(Effects), 그리고 비동기 작업(Async Work)은 더 이상 단일 컴포넌트의 생명주기(lifecycle) 안에 부주의하게 압축될 수 없습니다.
처음에는 이것이 프론트엔드 아키텍처(frontend architecture) 문제처럼 들릴 수 있습니다:
- 컴포넌트가 너무 많은 로컬 상태(local state)를 축적함
- 이펙트(Effects)가 관련 없는 사이드 이펙트(side effects)로 과부하됨
- 비동기 작업(Async work)이 완료될 때
setState를 호출하여, 그 생명주기를 UI에 직접적으로 결합함 - 쿼리(Queries), 뮤테이션(Mutations), 스트림(Streams)이 서로 다른 의미론(semantics)을 가짐에도 불구하고 교체 가능한 작업으로 취급됨
하지만 시스템을 구현, 리팩터링(refactor), 검증하기 위해 AI 에이전트(AI agents)를 더 광범위하게 사용하기 시작하면서, 저는 더 넓은 결론에 도달했습니다:
에이전트가 얼마나 많은 코드를 생성할 수 있는지보다, 시스템의 의미론적 경계(semantic boundaries)를 이해하는지가 훨씬 더 중요합니다.
코드는 더 이상 희소하지 않습니다. 의미론적 경계가 희소할 뿐입니다.
현대의 AI 에이전트는 놀라운 속도로 코드를 생성할 수 있습니다. 디렉토리 구조를 만들고, TypeScript 타입을 완성하며, 테스트를 작성하고, 리듀서(reducers), 훅(hooks), 어댑터(adapters), API 클라이언트(API clients)를 생성하며, 심지어 겉보기에 일관된 구현을 통해 전체 기능을 연결할 수도 있습니다.
표면적으로 이것은 자동화된 소프트웨어 개발과 매우 유사해 보입니다. 그러나 위험은 대개 의미론적 경계를 모호하게 남겨둔 채, 작동은 잘 되는 코드 내부에 숨겨져 있습니다.
코드는 첫 실행에서 잘 작동하지만, 아무도 다음과 같은 근본적인 질문에 명확히 답할 수 없습니다:
실제로 데이터를 소유하는 주체는 누구인가?
소유권이 불분명해지면, 몇 가지 아키텍처 질문들이 보이지 않는 시한폭탄으로 변합니다:
- 상태 일관성(state consistency)을 유지할 책임은 누구에게 있는가?
- 무효화(invalidation)를 트리거할 수 있는 권한은 누구에게 있는가?
- 어떤 값이 진실의 원천(source of truth)이고, 어떤 값이 단순히 파생된 결과인가?
- 비동기 동작(async behavior)이 중단되었을 때, 이를 취소(cancelled)해야 하는가, 재시도(retried)해야 하는가, 아니면 폐기(discarded)해야 하는가?
이러한 경계가 명시적으로 정의되지 않았을 때, 더 생산적인 에이전트가 더 나은 시스템을 만들어내지는 않습니다. 그저 유지보수가 불가능한 시스템을 더 빠르게 구축할 뿐입니다.
경계가 없다면, 에이전트는 혼돈을 가속화할 뿐입니다
개발자 스스로가 시스템의 아키텍처 경계(architectural boundaries)를 명확히 이해하지 못한다면, AI 에이전트가 자동으로 그 경계를 만들어주지는 않습니다.
대신, 에이전트는 대개 가장 직접적이고 직관적인 구현 경로를 따르게 됩니다:
데이터 도착
→ 컴포넌트 상태(component state)에 저장
...
각 단계는 개별적으로 보면 합리적으로 보입니다. 하지만 이들이 모이면 시스템의 구조를 점진적으로 침식시킵니다. 상태(State)는 모든 것을 담는 만능 컨테이너가 되어버립니다. 이펙트(Effects)는 관련 없는 사이드 이펙트(side effects)와 동기화 패치(synchronization patches)를 쏟아붓는 쓰레기통이 됩니다. 비동기 작업(async work)의 라이프사이클(lifecycle)은 UI와 밀접하게 결합(tightly coupled)됩니다.
이것이 현재 AI 코딩 워크플로우(workflows)에서 가장 큰 사각지대 중 하나입니다:
우리는 AI가 무엇을 쓸 수 있는지에 집착하는 반면, 무엇을 함께 써서는 안 되는지는 알고 있는지에 대해서는 무시합니다.
요구사항에서 시맨틱 맵(Semantic Map)으로
많은 이들이 충분히 상세한 프롬프트(prompts)를 제공하면 훌륭한 아키텍처가 만들어질 것이라고 가정합니다. 그것은 절반만 맞는 말입니다. 요구사항(Requirements)은 AI에게 무엇을 할지 알려줍니다. 아키텍처 경계는 무엇이 분리되어 있어야 하는지를 정의합니다.
저는 오픈 소스 프로젝트인 signal-kernel을 구축하면서 이러한 차이를 반복해서 경험했습니다. 시스템의 핵심 시맨틱(semantics)이 명시적일 때, 개선의 효과는 단순한 코드 생성 속도 향상 그 이상으로 나타납니다. 에이전트는 훨씬 더 명확한 결정 공간(decision space) 내에서 작동하며, 아키텍처는 일련의 자연스러운 가드레일(guardrails)을 제공합니다.
signal-kernel에서 저는 의도적으로 UI 프레임워크로부터 데이터 흐름(data flow)에 대한 제어권을 분리했습니다:
- **React 및 Vue를 위한 UI 어댑터 (UI adapters)**는 오직 스냅샷 소비자 (snapshot consumers)로서만 동작합니다.
- **반응형 그래프 (The reactive graph)**는 의존성 관계를 독점적으로 정의하고 소유합니다.
- **비동기 런타임 (The async runtime)**은 UI 생명주기 (lifecycle)와 독립적으로 비동기적 정확성 (async correctness)을 소유합니다.
- **리소스 (Resources)**는 비동기 데이터의 생명주기와 상태를 명시적으로 나타냅니다.
- **뮤테이션 (Mutations)과 스트림 (Streams)**은 의미론적으로 명확히 구분됩니다: 뮤테이션은 무효화 계약 (invalidation contracts)을 수반하며, 스트림은 이벤트 흐름 (event flow)과 안정적인 값 (stable values)을 구분합니다.
이러한 의미론 (semantics)이 인프라에 인코딩되면, AI 에이전트가 모든 책임을 동일한 레이어에 배치할 가능성은 훨씬 낮아집니다. 에이전트는 상태가 어디에 속해야 하는지 추측할 필요가 없습니다. 동기화 격차 (synchronization gaps)를 수리하기 위해 이펙트 (Effects)를 사용할 필요도 없습니다. 캐시 (cache)와 파생 상태 (derived state)를 혼동할 가능성도 줄어듭니다.
시스템의 의미론적 지도 (semantic map)가 이미 유효한 구현이 어떤 모습이어야 하는지를 제약하고 있기 때문입니다.
바이브 코딩 (Vibe Coding)의 보이지 않는 부채
저는 AI가 생성한 코드에 반대하는 것이 아닙니다. 빠른 탐색, 개념 검증, 그리고 반복적인 구현 작업을 줄이는 데 있어 AI의 가치는 부정할 수 없습니다.
소위 _바이브 코딩 (vibe coding)_에 대한 저의 주요 우려는 그것이 버그를 생성할 수도 있다는 점이 아니었습니다. 버그는 종종 테스트를 통해 잡아낼 수 있습니다. 의식적으로 결정되지 않은 아키텍처적 트레이드오프 (architectural trade-offs)는 훨씬 더 감지하기 어렵습니다. 그것들은 시간이 흐르면서 조용히 시스템을 침식시킵니다.
AI가 조립한 시스템은 지속적인 질문 앞에서 종종 버티지 못합니다:
- 왜 이 값이 로컬이 아닌 전역에 저장되어 있는가?
- 왜 이 성공적인 뮤테이션 (Mutation)이 관련 노드를 무효화하는 대신 로컬 캐시를 직접 수정하는가?
- 왜 스트림 (Stream) 이벤트가 현재의 안정적인 값을 덮어쓰도록 허용되는가?
- 취소 (cancellation) 및 재시도 (retry) 동작은 어느 레이어가 소유하는가?
- 두 개의 경계 (boundaries)가 서로 경쟁하는 진실의 원천 (sources of truth)이 되는 것을 무엇이 방지하는가?
이러한 결정 뒤에 숨겨진 추론을 검증하거나 설명할 수 없다면, 그 시스템은 이미 아키텍처 부채 (architectural debt)를 안고 있는 것입니다. 그러한 시스템은 비즈니스 로직이 단순한 동안에는 매우 빠르게 구축될 수 있습니다.
하지만 데이터 흐름 (data flow)이 더 복잡해지거나, 팀 규모가 커지거나, 혹은 비동기 타이밍 (async timing) 및 경계 간 동기화 (cross-boundary synchronization)와 관련된 엣지 케이스 (edge cases)에 직면하게 되면, 아키텍처는 무너지기 시작합니다.
시스템에 코드가 부족한 것이 아닙니다. 경계 (boundaries)가 부족한 것입니다.
AI는 아키텍처 문제를 제거하지 않았습니다. 오히려 드러냈을 뿐입니다.
AI 에이전트 (AI agents)가 소프트웨어 개발 워크플로 (workflows)를 재편했다는 점은 부정할 수 없지만, 그렇다고 아키텍처적 사고 (architectural thinking)를 쓸모없게 만든 것은 아닙니다. 오히려 그 반대로, 아키텍처적 사고를 훨씬 더 중요하게 만들었습니다.
코드를 생성하는 한계 비용 (marginal cost)이 0에 수렴함에 따라, 엔지니어링의 가치는 상위 단계로 이동합니다. 즉, 구현 (implementations)을 만들어내는 것에서, 그 구현들이 따라야 하는 제약 조건 (constraints)을 정의하는 것으로 이동합니다.
이전에는 희소한 역량이 다음과 같았습니다:
누가 이 기능을 구현할 수 있는가?
점점 더 희소해지는 역량은 다음과 같습니다:
누가 데이터 흐름의 경계 (data-flow boundaries)를 정의하고, 무효화 계약 (invalidation contracts)을 설계하며, 인간과 AI 에이전트 모두가 따를 수 있는 시맨틱 맵 (semantic map)을 제공할 수 있는가?
이것이 바로 제 작업이 반응형 라이브러리 (reactive library) 설계로 시작하여, 소유권 (Ownership), 렌더링 경계 (Render Boundaries), 비동기 정확성 (Async Correctness)에 대한 문제로 이어졌고, 결과적으로 AI 에이전트와 연결된 이유입니다.
이 주제들은 겉보기에는 서로 관련이 없어 보일 수 있습니다.
하지만 아키텍처 수준에서 보면, 이 모든 것은 동일한 원칙을 가리키고 있습니다:
AI가 더 많은 코드를 작성하도록 도울 수 있는 시대에, 우리는 어떤 코드들이 결코 함께 작성되어서는 안 되는지에 대해 훨씬 더 명확해져야 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기