개발자들은 프로젝트 설정 작업에 놀라울 정도로 많은 시간을 소비합니다.
코드 한 줄을 작성하기 전에, 프로젝트가 어떤 런타임을 사용하는지 결정하고, 의존성을 설치하며, 문서를 탐색하고, 언어별 명령어를 기억해야 하는 경우가 많습니다. Node.js, Python, Rust 프로젝트 사이를 전환하는 것은 완전히 다른 생태계 간의 컨텍스트 스위칭처럼 느껴질 수 있습니다.
저는 간단한 질문을 탐구하고 싶었습니다:
프로젝트 실행이 그 뒤에 있는 언어와 상관없이 항상 동일한 명령어로 이루어진다면 어떨까?
그 질문은 제가 Axiom이라는 Rust 기반의 커맨드라인 도구를 만들게 했고, 이 도구는 프로젝트를 자동으로 감지하고, 준비하며, 실행하도록 설계되었습니다.
이 글에서는 이 프로젝트를 구축하는 과정에서 기술적 결정, 아키텍처, 어려움, 그리고 배운 교훈에 초점을 맞춥니다.
문제점
대부분의 개발자는 다음과 같은 경험을 해봤을 것입니다:
Node.js
Python
pip install -r requirements.txt
python main.py
Rust
각 생태계는 자체적인 패키지 관리자, 규칙(convention), 그리고 시작 프로세스를 가지고 있습니다.
개별적으로 이 시스템들은 잘 작동합니다. 하지만 프로젝트와 언어 사이를 자주 이동할 때 마찰이 발생합니다.
저는 다음과 같은 형태의 워크플로우를 원했습니다:
디렉토리 안에 어떤 종류의 프로젝트가 있든 상관없이 말입니다.
왜 Rust인가?
저는 몇 가지 이유로 Rust를 선택했습니다.
프로젝트 감지 및 오케스트레이션은 주로 파일 시스템 작업입니다. Rust의 성능은 이러한 작업을 매우 빠르게 처리하면서도 리소스 사용량을 낮게 유지합니다.
목표 중 하나는 설치를 쉽게 하는 것이었습니다.
Rust는 사용자에게 추가 런타임 설치가 필요하지 않은 정적 바이너리(static binaries)를 허용합니다.
신뢰성(Reliability)
Axiom은 대부분의 시간을 프로젝트 구조를 검사하고 외부 명령을 실행하는 데 사용합니다.
Rust의 타입 시스템은 개발 과정에서 많은 실수를 런타임 버그가 되기 전에 잡아내는 데 도움을 주었습니다.
아키텍처 개요
전반적으로 Axiom은 네 가지 주요 시스템으로 구성됩니다:
- Project Detection (프로젝트 감지)
- Dependency Graph Construction (의존성 그래프 구축)
- Provider Abstraction (공급자 추상화)
- Execution Orchestration (실행 오케스트레이션)
Detection Layer (감지 계층)
첫 번째 과제는 디렉토리 내에 어떤 종류의 프로젝트가 존재하는지 결정하는 것입니다.
Axiom은 일반적인 지표들을 스캔합니다:
| 언어 | 감지 파일 |
|---|
| Node.js | package.json |
| ... | |
로직을 단순화한 버전은 다음과 같습니다:
Directory (디렉토리)
↓
Find Signature Files (서명 파일 찾기)
...
이것은 애플리케이션의 나머지 부분이 언어에 구애받지 않도록(language-agnostic) 만듭니다.
Provider Abstraction (공급자 추상화)
가장 중요한 설계 결정 중 하나는 프로젝트 감지를 프로젝트 실행과 분리한 것입니다.
모든 곳에 특수 케이스 로직을 작성하는 대신, Axiom은 공급자(providers)를 사용합니다.
개념적으로:
Provider (공급자)
├── Node Provider (Node 공급자)
├── Python Provider (Python 공급자)
...
각 공급자는 다음과 같은 동작을 구현합니다:
- 의존성 설치(Installing dependencies)
- 환경 준비(Preparing environments)
- 프로젝트 실행(Running projects)
- 상태 보고(Reporting status)
오케스트레이션 계층은 자신이 Node, Python 또는 Rust와 통신하는지 알 필요가 없습니다.
그것은 단순히 공급자 인터페이스와 통신합니다.
이로 인해 새로운 에코시스템을 추가하는 것이 방대한 조건문(conditional statements) 컬렉션을 수정하는 것보다 훨씬 쉬워졌습니다.
Dependency Graphs (의존성 그래프)
프로젝트가 커지면서, 저는 작업 간의 관계를 표현할 구조화된 방법을 원했습니다.
설정 과정을 선형적인 명령어 목록으로 취급하기보다는, Axiom은 작업을 의존성 그래프(dependency graph)로 모델링합니다.
예를 들어:
Install Runtime (런타임 설치)
↓
Install Dependencies (의존성 설치)
...
이처럼 실행을 표현하는 것은 다음과 같은 미래 개선 사항들을 위한 기반을 제공했습니다:
- 병렬 작업(Parallel operations)
- 더 스마트한 캐싱(Smarter caching)
- 증분 실행(Incremental execution)
- 빌드 최적화(Build optimization)
현재 구현이 비교적 간단함에도 불구하고, 초기에 그래프를 중심으로 설계하는 것은 미래 확장을 더 쉽게 만듭니다.
The Cache System (캐시 시스템)
반복되는 설정 작업은 비용이 많이 들 수 있습니다.
불필요한 작업을 피하기 위해, Axiom은 캐시 디렉토리(cache directory)를 유지합니다.
캐시는 도구가 특정 준비 단계가 이미 완료되었는지 여부를 판단할 수 있도록 메타데이터를 저장합니다.
개념적으로:
장점은 다음과 같습니다:
- 시작 시간 단축
- 반복적인 의존성 작업 감소
- 일관된 실행 동작
캐싱은 이론적으로는 간단하게 들리지만, 무효화(invalidation)가 빠르게 어려워집니다.
캐시된 정보가 여전히 유효한지 판단하는 것이 개발 과정에서 가장 까다로운 부분 중 하나가 되었습니다.
과제 (Challenges)
생태계 차이점 (Ecosystem Differences)
가장 큰 장애물은 Rust 코드를 작성하는 것이 아니었습니다.
언어별 생태계 간의 차이점을 처리하는 것이었습니다.
Node, Python, 그리고 Rust는 각각 다음과 같은 특징을 가지고 있습니다:
- 서로 다른 컨벤션(conventions)
- 서로 다른 의존성 시스템(dependency systems)
- 서로 다른 시작 명령(startup commands)
- 서로 다른 가정(assumptions)
유연성을 잃지 않으면서 공통의 추상화 계층을 만드는 것은 여러 차례의 재설계를 필요로 했습니다.
예외 사례 (Edge Cases)
간단한 데모 프로젝트는 거의 즉시 작동했습니다.
하지만 실제 프로젝트들은 문제점을 노출했습니다.
예를로는 다음과 같습니다:
- 누락된 의존성 파일
- 예상치 못한 디렉터리 레이아웃
- 대안적인 패키지 매니저
- 사용자 정의 시작 명령
많은 버그들은 장난감 같은 예제(toy examples)가 아닌 실제 저장소(repositories)를 대상으로 테스트한 후에야 나타났습니다.
범위 통제 (Scope Control)
개발 과정에서 흔히 하는 유혹은 기능을 계속 추가하는 것입니다.
여러 시점에서 저는 이 프로젝트를 완전한 개발 플랫폼으로 확장할까 고민했습니다.
대신, 저는 더 작은 목표에 집중했습니다:
프로젝트 실행을 더 쉽게 만드는 것.
이러한 초점을 유지한 것이 개발을 관리 가능한 상태로 유지하는 데 도움이 되었습니다.
벤치마킹 (Benchmarking)
제가 답하고 싶었던 질문 중 하나는 자동화가 설정 시간을 의미 있게 줄일 수 있는지 여부였습니다.
정확한 결과는 프로젝트의 크기와 복잡성에 따라 다르지만, 테스트를 통해 반복적인 설정 단계를 제거하는 것이 프로젝트를 실행하는 데 필요한 시간을 상당히 줄일 수 있음을 보여주었습니다.
가장 가치 있는 성과는 순수한 속도(raw speed)가 아니었습니다.
개발자가 작업을 시작하기 전에 내려야 하는 결정의 수를 줄이는 것이었습니다.
제가 배운 것 (What I Learned)
Axiom을 구축하면서 저는 몇 가지 교훈을 얻었습니다.
추상화가 기능보다 중요하다
잘 설계된 아키텍처는 미래의 기능을 위한 여지를 만듭니다.
반면, 잘못 설계된 아키텍처는 기술 부채(technical debt)를 만듭니다.
제공자 추상화(provider abstractions)와 오케스트레이션 계층(orchestration layers)에 시간을 투자한 것이 나중에 결실을 맺었습니다.
실제 프로젝트가 진짜 문제를 드러낸다
많은 설계는 실제로 저장소(repository)를 대상으로 테스트되기 전까지는 완벽해 보입니다.
의미 있는 대부분의 개선 사항은 Axiom을 실제 세계 프로젝트에 적용하면서 나왔습니다.
단순함이 승리한다
가장 유용한 개발자 도구들은 종종 한 가지 일을 예외적으로 잘 수행합니다.
Axiom에 추가된 모든 기능은 간단한 질문을 통해 평가되었습니다:
"이것이 사용자에게서 마찰(friction)을 줄여주는가?"
만약 답이 '아니오'였다면, 아마도 그 자리에 속하지 않았을 것입니다.
향후 작업 (Future Work)
향후 탐색에 흥미로운 몇 가지 영역들이 남아 있습니다:
- 추가 언어 생태계(Additional language ecosystems)
- 더 스마트한 프로젝트 감지(Smarter project detection)
- 개선된 캐싱 전략(Enhanced caching strategies)
- AI 지원 프로젝트 분석(AI-assisted project analysis)
- 자동화된 환경 진단(Automated environment diagnostics)
이 아키텍처는 완전한 재작성 없이도 이러한 기능들을 허용하도록 의도적으로 설계되었습니다.
마지막 생각 (Final Thoughts)
Axiom은 프로젝트 간 전환 시 반복적인 설정 작업을 제거하려는 시도로 시작되었습니다.
단순한 편의 실험으로 시작했던 것이 소프트웨어 아키텍처, 추상화 설계, 그리고 교차 언어 도구링(cross-language tooling)에 대한 더 깊은 탐구로 발전했습니다.
이 프로젝트는 많은 개발자 도구들이 공유하는 하나의 아이디어를 강화했습니다:
마찰을 작게 줄이는 것들은 시간이 지남에 따라 복리처럼 쌓입니다 (compound over time).
개발자들이 명령을 기억하고 환경을 구성하는 데 드는 에너지가 적어질수록, 소프트웨어를 구축하는 데 더 많은 시간을 할애할 수 있습니다.