
DSL은 LLM의 신뢰할 수 있는 사용을 가능하게 한다
요약
LLM을 활용한 소프트웨어 개발 시 도메인 특화 언어(DSL)를 도입하여 시스템의 신뢰성을 높이는 방법을 다룹니다. DSL은 LLM의 코드 생성 과정을 가이드하는 강력한 하네스 역할을 하며, 설계와 구현 사이의 간극을 메우는 신뢰할 수 있는 단일 출처(Source of Truth)가 됩니다.
핵심 포인트
- DSL은 LLM이 의도에 맞는 코드를 생성하도록 돕는 명확한 경계를 제공함
- 사전 명세의 한계를 극복하기 위해 구현을 통한 반복적 설계가 필요함
- DSL은 LLM과 협업하여 구축할 수 있는 강력한 도메인 추상화 도구임
- Tickloom 사례를 통해 분산 시스템을 위한 시맨틱 모델 구축의 중요성 제시
DSL은 LLM의 신뢰할 수 있는 사용을 가능하게 한다
LLM은 믿을 수 없을 정도로 빠르게 코드를 생성하지만, 의도한 바를 정확하게 생성하도록 보장하기 위해서는 명확한 경계가 필요합니다. 추상화(Abstractions)와 도메인 특화 언어(Domain-Specific Languages, DSLs)는 시작 단계부터 LLM을 안내하는 강력한 하네스(harness)를 제공합니다. 분산 시스템의 동작을 설명하기 위한 도메인 모델이자 DSL인 Tickloom의 사례는, 우리가 어떻게 LLM을 DSL을 반복적으로 구축하기 위한 파트너이자 이를 사용하기 위한 자연어 인터페이스로 활용할 수 있는지를 보여줍니다. 이러한 DSL은 LLM의 세계에서 소프트웨어 시스템을 위한 핵심적인 신뢰할 수 있는 단일 출처(Source of Truth) 역할을 할 수 있습니다.
2026년 7월 14일
목차
- 사전 명세(Upfront Specification)의 한계
- 설계는 구현을 통해 발견된다
- 도메인 추상화(Domain Abstractions)와 DSLs
- 왜 DSL은 LLM과 매우 잘 작동하는가
- 사례: 다이어그램이 풍부한 파워포인트 프레젠테이션을 생성하기 위해 LLM 사용하기
- 시맨틱 모델(Semantic Model) 구축하기
- 사례: Tickloom — 분산 시스템을 위한 시맨틱 모델
- DSL 없이도 좋은 추상화는 도움이 된다
- 사례: 분산 시스템 시나리오 테스트를 위한 DSL 구축하기
- LLM과 함께 작업하는 두 가지 단계
- 신뢰할 수 있는 단일 출처(Source of Truth)로서의 DSL
현대의 LLM은 놀라운 능력을 갖추고 있습니다. 이들은 단지 높은 수준의 자연어 설명만으로도 방대한 양의 코드, 때로는 전체 시스템을 생성할 수 있습니다. 여기서 중요한 가정은 구축해야 할 것의 '의도(intent)'가 LLM이 코딩 빌딩 블록(coding building blocks)으로 매핑할 수 있는 정확한 단어를 사용하여 잘 표현되어 있다는 점입니다. 그러나 주목할 만한 두 가지 중요한 사항이 있습니다. 바로 사전 명세(upfront specification)의 한계와 구현을 통해 설계가 어떻게 발견되는가 하는 점입니다.
사전 명세(Upfront Specification)의 한계
대규모 시스템을 구축하는 과정에는 수많은 작은 설계 결정(design decisions)이 포함되며, 이 모든 것을 사전에 알거나 고수준 명세(high-level spec)만으로 완전히 유도할 수는 없습니다. 명세는 기껏해야 시작 단계의 가설일 뿐입니다. 실제 제약 조건(constraints), 트레이드오프(trade-offs), 그리고 엣지 케이스(edge cases)는 구현을 진행함에 따라 반복적으로 발견됩니다. 우리는 이전 기사에서 이를 '사전 명세 불가능성(Upfront Specification Impossibility)'이라 부르며 자세히 논의한 바 있습니다. 핵심은 명세가 가치 없다는 것이 아니라, 첫 번째 명세는 수정되어야 할 가설일 뿐 결코 완성된 청사진이 아니라는 점입니다.
이에 대한 자연스러운 대응은 반복(iterate)하는 것입니다. 명세를 다듬고, 코드를 생성하고, 결과물을 검토한 뒤, 그 과정에서 배운 점을 다음 라운드에 반영하는 것입니다. 각 라운드가 검토 가능한 작은 변화를 만들어낼 때 이러한 루프는 잘 작동합니다.
구현을 통해 발견되는 설계
코드를 검토하는 것, 특히 우리가 아직 설계를 발견해 나가는 과정 중에 검토하는 것은 코드를 직접 작성하는 것과 같지 않습니다. 생성된 코드를 검토할 때, 우리는 청크(chunks) 단위로 검토하며 그것이 우리의 의도에 부합하는지 확인하고 발생 가능한 함정(pitfalls)을 찾습니다. 하지만 검토만으로는 설계 결정과 씨름하게 되는 경우가 드뭅니다. 반면, 코드를 작성하는 것은 책임(responsibility)이 어디에 속해야 하는지, 혹은 설계를 더 확장할 수 있도록 어떤 경계(boundaries)를 노출해야 하는지와 같은 구체적인 결정 사항들을 깊이 고민하게 만듭니다. 설계는 바로 이러한 결정들을 내리는 과정에서 가장 온전하게 드러납니다.
우리가 코딩하는 프로그래밍 언어와 패러다임은 우리가 얻는 설계 통찰(design insight)의 형태를 결정합니다. 함수형 설계(functional design) 접근 방식이나 객체 지향 설계(object-oriented design) 접근 방식은 각 패러다임에 자연스러운 관용구(idioms) 및 패턴과 함께 설계의 서로 다른 측면들을 드러냅니다.
그렇다면 LLM은 어디에 위치할까요? 저는 LLM이 두 가지 역할을 수행한다고 봅니다. 첫째, 우리가 설계와 그 어휘(vocabulary)를 형성하는 동안 브레인스토밍 파트너로서 설계 공간을 탐색하고 적절한 추상화(abstractions)를 발견하도록 돕는 훌륭한 조력자 역할을 합니다. 일단 어휘가 확립되면, LLM은 그것에 대한 탁월한 자연어 인터페이스(natural language interface)로서 작동합니다.
도메인 추상화와 DSL
이를 프레임화하는 유용한 방법은 도메인 주도 설계 (Domain Driven Design, DDD)를 통하는 것입니다. DDD의 핵심 통찰은 코드 내에 도메인의 공유된 개념 모델 (conceptual model)을 구축하고, DDD에서 유비쿼터스 언어 (Ubiquitous Language)라고 부르는 그 모델을 사용하여 코드베이스를 발전시키는 동시에 팀이 사고하고 소통할 수 있는 어휘를 제공하는 것입니다. 종종 해당 모델 위에 도메인 특화 언어 (Domain Specific Language, DSL)를 구축하는 것이 매우 효과적입니다. 즉, 도메인의 개념과 연산을 표현하기 위한 제약된 구문 (constrained syntax)을 만드는 것입니다. 이런 관점에서 보면, 대부분의 개발은 도메인 모델을 구축하고 이를 사용하여 시스템을 발전시키는 과정입니다. LLM은 도메인 모델의 존재 여부에 따라 두 가지 뚜렷한 역할을 수행합니다. 이 글에서는 도메인 특화 언어 (DSL)가 LLM과 어떻게 작동하는지에 초점을 맞출 것입니다.
DSL이 LLM과 매우 잘 작동하는 이유
DSL이 LLM과 잘 작동한다는 것은 흔한 경험입니다. PlantUML, Mermaid, Graphviz는 시각적 모델링을 위한 도메인 특화 언어 (DSL)이며, SQL은 데이터베이스 쿼리를 위한 DSL이고, Kubernetes YAML은 클라우드 인프라를 기술하기 위한 DSL입니다. 이것들은 범용 프로그래밍 언어 (general-purpose programming languages)가 아닙니다. 이들은 의도적으로 제약되어 있으며, 하나의 도메인 내에서 좁은 범위의 개념들을 표현하도록 설계되었습니다. 따라서 LLM이 평이한 영어 설명을 바탕으로 Mermaid 다이어그램, SQL 쿼리, 또는 Kubernetes 매니페스트 (manifests)를 생성하는 데 놀라울 정도로 능숙한 것은 놀라운 일이 아닙니다.
제가 관찰한 바에 따르면, DSL은 몇 가지 인컨텍스트 예시 (in-context examples)에 매우 잘 반응하기 때문에 LLM을 더 신뢰할 수 있게 만듭니다. Java와 같은 범용 언어는 동일한 의도를 표현하는 수많은 유효한 방법들을 제공합니다. 반면 DSL은 그러한 변동성을 제거합니다. 모델에 몇 가지 예시를 제공하는 것만으로도 정확한 구문을 안정적으로 생성하기에 충분합니다. 최전선 모델 (frontline models)들은 이미 학습 과정에서 PlantUML이나 Java의 플루언트 인터페이스 (fluent interfaces)에 많이 노출되어 있으므로, 아무것도 없는 상태에서 시작하는 것은 아니라는 점을 주목할 만합니다. 더 작고 더 제약된 모델들이 진정으로 새로운 DSL 작업을 맡았을 때 어떻게 작동할지 지켜보는 것도 흥미로울 것입니다.
에이전트(Agent) — 즉, 단일 샷 생성(single shot generation)이 아닌 자율적인 생성 및 확인 루프(generate-and-check loop) 내에서 실행되는 LLM — 에게는 한 가지 이점이 더 있습니다. DSL은 거의 항상 결정론적 검증기(deterministic validator), 즉 파서(parser), JSON 스키마(JSON schema), 타입 체커(type checker) 또는 컴파일러(compiler)를 동반합니다. 에이전트는 후보(candidate)를 생성하고, 이를 검증기에 통과시킨 뒤, 오류로부터 이를 수정할 수 있으며, 이 모든 과정은 인간의 개입(human in the loop) 없이 이루어집니다. 결정적으로, 오류가 생성된 코드 깊숙이 묻혀 있는 스택 트레이스(stack trace) 형태가 아니라, “클라이언트를 선택하기 전에 액션을 선택할 수 없습니다”와 같이 도메인 수준에서 표현됩니다. DSL의 도구 세트 자체가 훌륭한 하네스(harness) 역할을 합니다. 우리는 아래의 Tickloom 예시에서 이를 구체적으로 확인할 수 있는데, 여기서 DSL의 문법은 호스트 언어의 컴파일러에 의해 강제되며 결과적인 실행은 자동으로 확인됩니다.
이것이 모든 상황에 적용되는 만능 해결책(one-size-fits-all solution)은 아니라는 점에 유의해야 합니다. 이 장점은 DSL이 작고 충분히 제약되어 있어, 몇 가지 인컨텍스트 예시(in-context examples)만으로도 그 사용법을 전달할 수 있을 때 유지됩니다. 또한 언어와 그 의미론적 모델(semantic model)을 설계하고 유지 관리하는 데에는 실제적인 초기 비용이 발생합니다. 따라서 그 보상은 검증기로 뒷받침되는, 잘 분리되고 진정으로 제약된 DSL에 집중됩니다.
예시: 다이어그램이 풍부한 파워포인트 프레젠테이션 생성을 위한 LLM 활용
LLM (Large Language Models)은 커스텀 도구를 구축하는 것을 매우 쉽게 만들어 줍니다. 분산 시스템 (Distributed Systems)을 가르칠 때, 저는 클러스터 내의 분산 동작을 설명하는 다이어그램이 주로 포함된 프레젠테이션을 자주 만들어야 했습니다. UML 시퀀스 다이어그램 (Sequence diagrams)이 그동안 큰 도움이 되었지만, 클러스터를 통한 메시지 흐름을 설명하면서 전체 시퀀스 다이어그램을 한꺼번에 보여주는 것은 그리 유용하지 않았습니다. 저는 파워포인트 (PowerPoint) 프레젠테이션에서 시퀀스 다이어그램을 단계별로 보여줄 수 있는 도구가 필요했습니다. LLM의 도움을 받아, 저는 PlantUML 다이어그램에 대한 참조를 포함하여 프레젠테이션 구조를 기술하는 YAML을 처리하고 파워포인트 프레젠테이션을 생성하는 도구를 구축할 수 있었습니다. PlantUML 다이어그램에는 단계 (steps)가 표시되어 있으며, 도구는 각 단계마다 별도의 슬라이드를 생성합니다. 덕분에 다이어그램이 풍부한 프레젠테이션을 매우 쉽게 만들 수 있었습니다.
이 프롬프트는 다음과 같이 단계 마커가 포함된 PlantUML 코드를 생성합니다:
@startuml actor Alice box "Cluster" #lightblue participant athens participant byzantium participant cyrene end box '[step] Alice -> athens: "title", "After Dawn" '[step] athens -> athens: save() note right of athens state: title: After Dawn end note '[step] athens -[#red]x byzantium: "title", "After Dawn" '[step] athens -> cyrene: "title", "After Dawn" note right of cyrene state: title: After Dawn end note '[step] athens -> athens: isQuorumReached() '[step] athens --> Alice: Success @enduml
저는 이를 사용하여 파워포인트 프레젠테이션의 일련의 슬라이드를 만들었습니다. 이를 위해 프레젠테이션 구조와 각 슬라이드에서 사용할 다이어그램을 기술하는 작은 YAML 명세 (specification)를 개발했습니다. 이를 통해 슬라이드에 애니메이션을 수동으로 만들 필요 없이, LLM을 사용하여 복잡한 분산 시스템 개념을 설명하는 프레젠테이션을 제작할 수 있었습니다. 슬라이드 YAML 명세를 생성하기 위한 예시 프롬프트는 다음과 같이 간단합니다.
이것은 다음과 같은 슬라이드 명세 YAML을 생성합니다:
- slide: title: "Quorum Write Example" diagram: "quorum-write"
프롬프트가 슬라이드 YAML을 생성하라고 말하더라도, 그것이 아무런 무작위적인 YAML 명세(spec)가 아니라는 점에 주목하는 것이 중요합니다. 파워포인트 프레젠테이션을 생성하는 도구와 해당 도구가 이해하는 YAML 명세가 프롬프트의 컨텍스트(context)로 사용되기 때문에, LLM은 도구가 파워포인트 프레젠테이션을 생성하는 데 직접 사용할 수 있는 정확한 YAML 명세를 생성할 수 있습니다.
전체 YAML 명세는 이 Github 리포지토리에서 확인할 수 있습니다.
이 단일 예시에서 LLM이 두 가지 서로 다른 역할을 수행했다는 점에 주목하십시오. 첫째, LLM은 공동 설계자(co-designer)로서 기존 PlantUML 도구 위에 단계별로 표시된 PlantUML 확장 기능과 슬라이드 YAML의 형태를 잡는 것을 도왔습니다. 그 다음, 해당 작은 DSL이 존재하게 되자, LLM은 영어 요청을 유효한 명세로 변환하는 자연어 인터페이스(natural-language interface)가 되었습니다. 이 역할 분담에 대해서는 기사 마지막 부분에서 다시 다루겠습니다.
시맨틱 모델 (Semantic Model) 구축
이전 섹션에서 다룬 예시는 간단했습니다. YAML은 전달 구문(carrier syntax)으로 사용되었고, 저는 파싱된 구문 트리(syntax tree)를 직접 처리하여 구문 트리 자체를 시맨틱 모델 (Semantic Model)로 효과적으로 사용했습니다 (비록 이것이 구문을 실행 시맨틱스 (execution semantics)와 결합시키기는 하지만 말입니다). 하지만 분산 시스템(distributed systems)과 같이 더 복잡한 도메인에서는 도메인의 개념과 코드베이스에서 내린 설계 결정을 표현하기 위해 더 복잡한 시맨틱 모델이 필요합니다. 제가 분산 시스템을 빠르게 구축하고 테스트하기 위해 만든 작은 프레임워크를 기반으로 한 예시를 살펴보겠습니다.
예시: Tickloom — 분산 시스템을 위한 시맨틱 모델
쿼럼 기반 (quorum-based) 키-값 저장소 (key-value stores)나 Raft 및 Paxos와 같은 합의 프로토콜 (consensus protocols)과 같은 분산 시스템을 구현하는 것은 매우 어려운 작업입니다. 프롬프트, 명세서(specifications), 또는 정교하게 구성된 .md 스킬 파일(skill files)을 통해 점진적으로 구현을 가이드하더라도, 비동기 런타임 (asynchronous runtimes)은 여전히 압도적으로 방대한 구현 결정 공간을 노출합니다. 스레딩 모델 (threading models), 네트워킹 패턴 (networking patterns), 스토리지 조정 (storage coordination), 재시도 동작 (retry behavior), 그리고 타이밍 시맨틱스 (timing semantics)가 모두 생성된 코드 내에 뒤엉킨 상태로 남게 됩니다. 문제는 단순히 코드 생성의 복잡성이 아니라, 검증 (verification)의 복잡성입니다. 스레드 스케줄링 (thread scheduling), 네트워크 지연 (network delays), 프로세스 일시 중지 (process pauses), 그리고 클록 스큐 (clock skew) 전반에 걸쳐 발생할 수 있는 모든 가능한 인터리빙 (interleavings)에 의해 생성되는 결과적인 상태 공간 (state space)은 너무 커져서, 상호작용하는 모든 동작에 대해 체계적으로 검토하고 정확성을 검증하는 것이 거의 불가능해집니다. 이것이 바로 가장 검증된 분산 시스템에서도 Jepsen 테스트가 버그를 찾아내는 이유입니다.
이것이 바로 시맨틱 모델 (semantic model)이 유익한 지점입니다. Tickloom은 분산 알고리즘을 구축하고 테스트하기 위해 제가 만든 작은 프레임워크입니다. 이 프레임워크의 추상화는 범용 런타임이 아닙니다. 그것은 분산 프로세스가 어떻게 동작하는지에 대한 일련의 설계 결정 (design decisions)입니다. 모든 노드는 단일 스레드 틱 루프 (single-threaded tick loop) 내에서 실행됩니다. tick() 호출마다 논리적 시계 (logical clock)가 1씩 증가하며, 정해진 결정론적 순서(네트워크, 메시지 버스, 프로세스, 스토리지 순)로 대기 중인 작업을 처리합니다. 시간은 밀리초 (milliseconds)가 아닌 틱 (ticks) 단위로 측정됩니다. 메시지는 일반적인 Java 레코드 (records)입니다. 복제본 (replicas) 간의 조정은 피어 (peers), 브로드캐스트 (broadcasts), 쿼럼 (quorums)을 이미 알고 있는 Replica 기본 클래스를 통해 표현됩니다.
스레딩, 타이밍, 네트워크 전달은 더 이상 매 프롬프트마다 다시 결정해야 하는 미결정 문제(open questions)가 아닙니다. 알고리즘 작성자에게 남는 것은 실제 프로토콜 로직 (protocol logic)뿐입니다. 예를 들어, 쿼럼 복제본 (quorum replica)은 프레임워크의 어휘 (vocabulary)로 표현된 메시지 핸들러 (message handlers)의 집합일 뿐입니다.
프레임워크가 어휘를 제공하기 때문에 — Replica
, quorumRequest
,
countResponseIf
,
MessageType
,
Handler
— 프롬프트가 복잡한 내부 구현(plumbing) 대신 프로토콜(protocol) 수준에 머무를 수 있습니다:
이러한 고수준의 설명은 다음과 같은 코드를 생성합니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 HN AI Posts의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기