개요

이번 포스트에서는 Oxy 프레임워크의 Rust 애플리케이션 진입점과 CLI(Command Line Interface) 시스템을 심층 분석합니다. 이전 프로젝트 개요 분석에 이어, 애플리케이션이 실제로 어떻게 시작되고 초기화되는지 살펴보겠습니다.

1. 애플리케이션 진입점 (main.rs)

1.1 비동기 메인 함수

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
    rustls::crypto::ring::default_provider()
        .install_default()
        .expect("Failed to install rustls crypto provider");

    std::panic::set_hook(Box::new(|panic_info| {
        error!(
            error = %panic_info,
            trace = %std::backtrace::Backtrace::force_capture(),
            "panic occurred"
        );
    }));

    dotenv::from_path(".env").ok();
    tracing_subscriber::fmt::init();

    cli::cli().await?;

    Ok(())
}

주요 특징 분석:

  1. Tokio 런타임: #[tokio::main] 매크로를 사용하여 전체 애플리케이션을 비동기 컨텍스트에서 실행
  2. TLS 초기화: Rustls 암호화 공급자를 기본으로 설정하여 안전한 네트워크 통신 지원
  3. 패닉 핸들러: 구조화된 로깅과 백트레이스를 포함한 고급 패닉 처리
  4. 환경 변수: .env 파일에서 환경 변수 자동 로드
  5. 로깅 시스템: tracing을 통한 구조화된 로깅 초기화

1.2 모듈 구조 (lib.rs)

pub mod adapters;
pub mod agent;
pub mod api;
pub mod cli;
pub mod config;
pub mod db;
pub mod errors;
pub mod execute;
pub mod mcp;
pub mod service;
pub mod theme;
pub mod utils;
pub mod workflow;
pub mod workspace;

14개의 핵심 모듈로 구성된 체계적인 아키텍처:

  • adapters: 데이터베이스 연결 어댑터
  • agent: AI 에이전트 시스템
  • api: REST API 라우터
  • cli: 명령줄 인터페이스
  • config: 설정 관리
  • db: 데이터베이스 추상화
  • execute: 실행 엔진
  • mcp: Model Context Protocol
  • service: 비즈니스 로직
  • theme: 터미널 테마 시스템
  • utils: 유틸리티 함수
  • workflow: 워크플로우 엔진
  • workspace: 작업공간 관리

2. 터미널 테마 시스템 (theme.rs)

2.1 테마 정의

#[derive(Debug, Clone)]
pub struct Theme {
    pub primary: (u8, u8, u8),
    pub secondary: (u8, u8, u8),
    pub tertiary: (u8, u8, u8),
    pub success: (u8, u8, u8),
    pub warning: (u8, u8, u8),
    pub error: (u8, u8, u8),
    pub info: (u8, u8, u8),
    pub text: (u8, u8, u8),
}

다크/라이트 모드 지원:

impl Theme {
    pub fn dark() -> Self {
        Theme {
            primary: (88, 166, 255),     // 밝은 파란색
            secondary: (150, 150, 150),   // 회색
            tertiary: (255, 215, 0),      // 금색
            success: (0, 255, 0),         // 녹색
            warning: (255, 165, 0),       // 주황색  
            error: (255, 69, 58),         // 빨간색
            info: (94, 92, 230),          // 보라색
            text: (255, 255, 255),        // 흰색
        }
    }

    pub fn light() -> Self {
        Theme {
            primary: (0, 102, 204),       // 어두운 파란색
            secondary: (102, 102, 102),   // 어두운 회색
            tertiary: (184, 134, 11),     // 어두운 금색
            success: (0, 128, 0),         // 어두운 녹색
            warning: (255, 140, 0),       // 어두운 주황색
            error: (220, 20, 60),         // 어두운 빨간색
            info: (75, 0, 130),           // 남색
            text: (0, 0, 0),              // 검은색
        }
    }
}

2.2 자동 테마 감지

pub fn get_current_theme_mode() -> ThemeMode {
    match terminal_light::luma() {
        Ok(luma) if luma > 0.6 => ThemeMode::Light,
        _ => ThemeMode::Dark,
    }
}

시스템의 터미널 밝기를 자동 감지하여 적절한 테마를 선택합니다.

2.3 TrueColor 지원

pub fn detect_true_color_support() -> bool {
    std::env::var("COLORTERM")
        .map(|val| val == "truecolor" || val == "24bit")
        .unwrap_or_else(|_| {
            std::env::var("TERM")
                .map(|term| {
                    term.contains("256color") || 
                    term.contains("24bit") || 
                    term.contains("truecolor")
                })
                .unwrap_or(false)
        })
}

3. CLI 시스템 아키텍처

3.1 명령어 구조

Clap을 기반으로 한 계층적 명령어 시스템:

#[derive(Parser, Debug)]
enum SubCommand {
    Init,                          // 프로젝트 초기화
    Run(RunArgs),                  // 워크플로우/SQL 실행
    Test(TestArgs),                // 테스트 실행
    Build(BuildArgs),              // 임베딩 빌드
    VecSearch(VecSearchArgs),      // 벡터 검색
    Sync(SyncArgs),                // 데이터베이스 동기화
    Validate,                      // 설정 검증
    Serve(ServeArgs),              // 웹 서버 시작
    McpSse(McpSseArgs),           // MCP SSE 서버
    McpStdio(McpArgs),            // MCP STDIO 서버
    TestTheme,                     // 테마 테스트
    GenConfigSchema(GenConfigSchemaArgs), // 스키마 생성
    SelfUpdate,                    // 자동 업데이트
    Make(MakeArgs),               // Make 명령
    Ask(AskArgs),                 // AI 질의
}

3.2 파일 타입 기반 실행 시스템

pub async fn handle_run_command(run_args: RunArgs) -> Result<RunResult, OxyError> {
    let extension = file_path.extension().and_then(std::ffi::OsStr::to_str);

    match extension {
        Some("yml") => {
            if file.ends_with(".workflow.yml") {
                handle_workflow_file(&file_path, run_args.retry).await?;
                Ok(RunResult::Workflow)
            } else if file.ends_with(".agent.yml") {
                handle_agent_file(&file_path, run_args.question).await?;
                Ok(RunResult::Agent)
            } else {
                return Err(OxyError::ArgumentError(
                    "Invalid YAML file. Must be either *.workflow.yml or *.agent.yml".into(),
                ));
            }
        }
        Some("sql") => {
            let sql_result = handle_sql_file(/* ... */).await?;
            Ok(RunResult::Sql(sql_result))
        }
        _ => Err(OxyError::ArgumentError(
            "Invalid file extension. Must be .workflow.yml, .agent.yml, or .sql".into(),
        )),
    }
}

파일 확장자에 따라 적절한 핸들러를 선택하는 지능적인 라우팅 시스템입니다.

4. 프로젝트 초기화 시스템

4.1 대화형 설정 생성

pub fn init() -> Result<(), InitError> {
    let config_path = if project_path.as_os_str().is_empty() 
        || !project_path.join("config.yml").exists() {
        std::env::current_dir()?.join("config.yml")
    } else {
        project_path.join("config.yml")
    };

    if !config_path.exists() {
        create_config_file(&config_path)?;
    }

    create_project_structure()?;
    Ok(())
}

4.2 데이터베이스 설정 수집

fn choose_database_type() -> Result<DatabaseType, InitError> {
    println!("\tChoose database type:");
    println!("\t\t1. DuckDB");
    println!("\t\t2. BigQuery");
    println!("\t\t3. Postgres");
    println!("\t\t4. Redshift");
    println!("\t\t5. Mysql");
    println!("\t\t6. ClickHouse");
    // 사용자 입력에 따른 데이터베이스 설정
}

6가지 데이터베이스 타입을 지원하는 대화형 설정 시스템입니다.

4.3 AI 모델 설정

fn collect_models() -> Result<Vec<Model>, InitError> {
    let model = match model_type.as_str() {
        "1" => Model::OpenAI {
            name: prompt_with_default("Name", "openai-4.1", None)?,
            model_ref: prompt_with_default("Model reference", "gpt-4.1", None)?,
            key_var: prompt_with_default("Key variable", "OPENAI_API_KEY", None)?,
            api_url: Some(api_url),
            azure,
        },
        "2" => Model::Ollama {
            name: prompt_with_default("Name", "llama3.2", None)?,
            model_ref: prompt_with_default("Model reference", "llama3.2:latest", None)?,
            api_key: prompt_with_default("API Key", "secret", None)?,
            api_url: prompt_with_default("API URL", "http://localhost:11434/v1", None)?,
        },
        // ...
    };
}

OpenAI와 Ollama를 포함한 다양한 AI 모델 제공자를 지원합니다.

5. 서버 시스템

5.1 웹 애플리케이션 서버

pub async fn start_server_and_web_app(mut web_port: u16) {
    let api_router = router::api_router().await
        .layer(TraceLayer::new_for_http());
    
    let web_app = Router::new()
        .merge(SwaggerUi::new("/apidoc")
            .url("/apidoc/openapi.json", openapi))
        .nest("/api", api_router)
        .fallback_service(serve_with_fallback)
        .layer(TraceLayer::new_for_http());

    axum::serve(listener, web_app)
        .with_graceful_shutdown(shutdown_signal())
        .await
        .unwrap();
}

주요 기능:

  • OpenAPI 문서: Swagger UI를 통한 API 문서화
  • 정적 파일 서빙: SPA 애플리케이션 지원
  • Graceful Shutdown: 신호 기반 우아한 종료
  • HTTP 트레이싱: 요청/응답 로깅

5.2 MCP 서버 지원

pub async fn start_mcp_sse_server(mut port: u16) -> anyhow::Result<CancellationToken> {
    let service = OxyMcpServer::new(project_path.clone()).await?;
    let bind = SocketAddr::from(([0, 0, 0, 0], port));
    let ct = SseServer::serve(bind)
        .await?
        .with_service(move || service.to_owned());

    println!("MCP server running at http://localhost:{}", port);
    anyhow::Ok(ct)
}

Model Context Protocol을 지원하여 외부 AI 도구와의 통합을 제공합니다.

6. 에러 처리 및 로깅

6.1 구조화된 에러 처리

use std::panic;

panic::set_hook(Box::new(move |panic_info| {
    error!(
        error = %panic_info,
        trace = %backtrace::Backtrace::force_capture(),
        "panic occurred"
    );
}));

6.2 컬러 출력 지원

impl StyledText for &str {
    fn primary(self) -> ColoredString { /* ... */ }
    fn success(self) -> ColoredString { /* ... */ }
    fn warning(self) -> ColoredString { /* ... */ }
    fn error(self) -> ColoredString { /* ... */ }
    // ...
}

터미널에서 가독성 높은 컬러 출력을 지원합니다.

7. 아키텍처 특징 분석

7.1 비동기 우선 설계

  • Tokio 런타임 기반 완전 비동기 아키텍처
  • 데이터베이스 I/O부터 웹 서버까지 모든 작업이 비동기로 처리

7.2 모듈화된 구조

  • 각 기능이 독립적인 모듈로 분리
  • 명확한 책임 분리와 재사용성

7.3 사용자 경험 최적화

  • 대화형 초기화 프로세스
  • 자동 테마 감지
  • 컬러 터미널 출력

7.4 확장성 고려

  • 플러그인 아키텍처 (MCP)
  • 다양한 데이터베이스 지원
  • AI 모델 제공자 추상화

결론

Oxy 프레임워크의 진입점과 CLI 시스템은 현대적인 Rust 애플리케이션의 모범 사례를 보여줍니다. 비동기 프로그래밍, 구조화된 로깅, 모듈화된 아키텍처, 그리고 뛰어난 사용자 경험을 제공하는 설계가 돋보입니다.

다음 포스트에서는 Oxy의 핵심인 에이전트 시스템과 워크플로우 엔진을 자세히 분석하겠습니다.


연관 포스트:

참고 자료: