[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-22-rust-uijonseong-juib-servicelocator-arc-treuit":3},{"article":4,"author":51},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":31,"related_articles":32},"d5000000-0000-0000-0000-000000000122","a0000000-0000-0000-0000-000000000056","Deep EVM #22: Rust에서의 의존성 주입 — ServiceLocator, Arc, 트레잇 객체","deep-evm-22-rust-uijonseong-juib-servicelocator-arc-treuit","프레임워크 없이 Rust에서 의존성 주입 구현. 컴포지션 루트 패턴, Arc\u003Cdyn Trait> vs 제네릭, 테스트를 위한 모의 구현, ServiceLocator 패턴을 다룹니다.","## Rust에서의 DI 문제\n\n의존성 주입은 기본적인 설계 원칙입니다: 컴포넌트가 내부에서 의존성을 생성하는 대신 외부에서 의존성을 받아야 합니다. Java나 C#에서는 Spring이나 Autofac과 같은 DI 프레임워크가 이를 자동으로 처리합니다. Rust에는 표준 DI 프레임워크가 없으며, 소유권 모델이 순진한 DI 패턴을 어색하게 만듭니다.\n\n데이터베이스 연결, 캐시, 로거가 필요한 서비스를 생각해봅시다:\n\n```rust\n\u002F\u002F 나쁨: 하드코딩된 의존성\nstruct UserService {\n    db: PgPool,          \u002F\u002F 구체적 타입, 모킹 불가\n    cache: RedisClient,  \u002F\u002F 구체적 타입\n}\n\nimpl UserService {\n    fn new() -> Self {\n        \u002F\u002F 최악의 패턴: 자체 의존성 생성\n        Self {\n            db: PgPool::connect(\"postgres:\u002F\u002F...\").await.unwrap(),\n            cache: RedisClient::new(\"redis:\u002F\u002F...\").unwrap(),\n        }\n    }\n}\n```\n\n이것은 테스트 불가능합니다. 실제 PostgreSQL과 Redis 인스턴스 없이는 유닛 테스트를 실행할 수 없습니다. Rust 관용적 DI로 이를 수정합시다.\n\n## 패턴 1: Arc를 사용한 트레잇 객체\n\n동작을 트레잇으로 정의한 다음, 런타임 다형성을 위해 `Arc\u003Cdyn Trait>`를 받습니다:\n\n```rust\nuse std::sync::Arc;\nuse async_trait::async_trait;\n\n#[async_trait]\ntrait UserRepository: Send + Sync {\n    async fn find_by_id(&self, id: i64) -> anyhow::Result\u003COption\u003CUser>>;\n    async fn save(&self, user: &User) -> anyhow::Result\u003C()>;\n}\n\n#[async_trait]\ntrait CacheService: Send + Sync {\n    async fn get(&self, key: &str) -> anyhow::Result\u003COption\u003CString>>;\n    async fn set(&self, key: &str, value: &str, ttl: u64) -> anyhow::Result\u003C()>;\n}\n\nstruct UserService {\n    repo: Arc\u003Cdyn UserRepository>,\n    cache: Arc\u003Cdyn CacheService>,\n}\n\nimpl UserService {\n    fn new(\n        repo: Arc\u003Cdyn UserRepository>,\n        cache: Arc\u003Cdyn CacheService>,\n    ) -> Self {\n        Self { repo, cache }\n    }\n\n    async fn get_user(&self, id: i64) -> anyhow::Result\u003COption\u003CUser>> {\n        \u002F\u002F 먼저 캐시 확인\n        let cache_key = format!(\"user:{}\", id);\n        if let Some(cached) = self.cache.get(&cache_key).await? {\n            return Ok(Some(serde_json::from_str(&cached)?));\n        }\n        \u002F\u002F 캐시 미스 — 데이터베이스 조회\n        if let Some(user) = self.repo.find_by_id(id).await? {\n            let json = serde_json::to_string(&user)?;\n            self.cache.set(&cache_key, &json, 300).await?;\n            return Ok(Some(user));\n        }\n        Ok(None)\n    }\n}\n```\n\n이제 테스트에서 구현을 교체할 수 있습니다:\n\n```rust\nstruct MockUserRepository {\n    users: Vec\u003CUser>,\n}\n\n#[async_trait]\nimpl UserRepository for MockUserRepository {\n    async fn find_by_id(&self, id: i64) -> anyhow::Result\u003COption\u003CUser>> {\n        Ok(self.users.iter().find(|u| u.id == id).cloned())\n    }\n    async fn save(&self, _user: &User) -> anyhow::Result\u003C()> {\n        Ok(())\n    }\n}\n\nstruct MockCache;\n\n#[async_trait]\nimpl CacheService for MockCache {\n    async fn get(&self, _key: &str) -> anyhow::Result\u003COption\u003CString>> {\n        Ok(None) \u002F\u002F 항상 캐시 미스\n    }\n    async fn set(&self, _key: &str, _value: &str, _ttl: u64) -> anyhow::Result\u003C()> {\n        Ok(())\n    }\n}\n\n#[tokio::test]\nasync fn test_get_user() {\n    let repo = Arc::new(MockUserRepository {\n        users: vec![User { id: 1, name: \"Alice\".into() }],\n    });\n    let cache = Arc::new(MockCache);\n    let service = UserService::new(repo, cache);\n    let user = service.get_user(1).await.unwrap();\n    assert_eq!(user.unwrap().name, \"Alice\");\n}\n```\n\n## 패턴 2: 제네릭을 사용한 컴파일 타임 디스패치\n\n`Arc\u003Cdyn Trait>`는 런타임 비용(vtable 간접 참조, 힙 할당)이 있습니다. 성능이 중요한 코드에서는 제네릭을 사용하여 비용을 제거합니다:\n\n```rust\nstruct UserService\u003CR: UserRepository, C: CacheService> {\n    repo: R,\n    cache: C,\n}\n\nimpl\u003CR: UserRepository, C: CacheService> UserService\u003CR, C> {\n    fn new(repo: R, cache: C) -> Self {\n        Self { repo, cache }\n    }\n}\n```\n\n트레이드오프:\n- **Arc\u003Cdyn Trait>**: 런타임 다형성, 유연하지만 약간의 오버헤드\n- **제네릭**: 컴파일 타임 다형성, 제로 비용이지만 더 긴 컴파일 시간과 단형화\n\n경험 법칙: 핫 경로(초당 수백만 호출)에서는 제네릭, 나머지에서는 `Arc\u003Cdyn Trait>`.\n\n## 패턴 3: ServiceLocator 패턴\n\n의존성이 많은 대규모 애플리케이션에서는 모든 것을 개별 매개변수로 전달하는 것이 지루해집니다. ServiceLocator 패턴이 이를 단순화합니다:\n\n```rust\nuse std::any::{Any, TypeId};\nuse std::collections::HashMap;\n\n#[derive(Default)]\nstruct ServiceLocator {\n    services: HashMap\u003CTypeId, Box\u003Cdyn Any + Send + Sync>>,\n}\n\nimpl ServiceLocator {\n    fn register\u003CT: Send + Sync + 'static>(&mut self, service: T) {\n        self.services.insert(TypeId::of::\u003CT>(), Box::new(service));\n    }\n\n    fn resolve\u003CT: Send + Sync + 'static>(&self) -> Option\u003C&T> {\n        self.services\n            .get(&TypeId::of::\u003CT>())\n            .and_then(|s| s.downcast_ref::\u003CT>())\n    }\n}\n```\n\n사용 예:\n\n```rust\nlet mut locator = ServiceLocator::default();\nlocator.register(Arc::new(PostgresUserRepo::new(pool.clone())) as Arc\u003Cdyn UserRepository>);\nlocator.register(Arc::new(RedisCache::new(redis_client)) as Arc\u003Cdyn CacheService>);\n\n\u002F\u002F 해결\nlet repo = locator.resolve::\u003CArc\u003Cdyn UserRepository>>().unwrap();\nlet cache = locator.resolve::\u003CArc\u003Cdyn CacheService>>().unwrap();\nlet service = UserService::new(Arc::clone(repo), Arc::clone(cache));\n```\n\n## 컴포지션 루트 패턴\n\n의존성 트리를 하나의 장소에서 조립합니다 — 보통 `main()` 함수에서:\n\n```rust\n#[tokio::main]\nasync fn main() -> anyhow::Result\u003C()> {\n    \u002F\u002F 인프라 계층\n    let db_pool = PgPool::connect(&env::var(\"DATABASE_URL\")?).await?;\n    let redis = RedisClient::open(env::var(\"REDIS_URL\")?)?;\n\n    \u002F\u002F 리포지토리 계층\n    let user_repo: Arc\u003Cdyn UserRepository> = Arc::new(\n        PostgresUserRepo::new(db_pool.clone())\n    );\n    let order_repo: Arc\u003Cdyn OrderRepository> = Arc::new(\n        PostgresOrderRepo::new(db_pool.clone())\n    );\n\n    \u002F\u002F 서비스 계층\n    let cache: Arc\u003Cdyn CacheService> = Arc::new(\n        RedisCache::new(redis)\n    );\n    let user_service = Arc::new(\n        UserService::new(Arc::clone(&user_repo), Arc::clone(&cache))\n    );\n    let order_service = Arc::new(\n        OrderService::new(Arc::clone(&order_repo), Arc::clone(&user_service))\n    );\n\n    \u002F\u002F HTTP 계층\n    let app = Router::new()\n        .route(\"\u002Fusers\u002F:id\", get(get_user))\n        .route(\"\u002Forders\", post(create_order))\n        .with_state(AppState {\n            user_service,\n            order_service,\n        });\n\n    axum::serve(listener, app).await?;\n    Ok(())\n}\n```\n\n이 패턴은 의존성 구성을 단일 장소에 집중시키므로 전체 애플리케이션의 배선을 한눈에 이해할 수 있습니다.\n\n## Axum의 State 추출자와의 통합\n\nAxum은 `State` 추출자를 통해 자연스러운 DI 메커니즘을 제공합니다:\n\n```rust\n#[derive(Clone)]\nstruct AppState {\n    user_service: Arc\u003CUserService>,\n    order_service: Arc\u003COrderService>,\n}\n\nasync fn get_user(\n    State(state): State\u003CAppState>,\n    Path(id): Path\u003Ci64>,\n) -> Result\u003CJson\u003CUser>, StatusCode> {\n    state.user_service\n        .get_user(id)\n        .await\n        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?\n        .map(Json)\n        .ok_or(StatusCode::NOT_FOUND)\n}\n```\n\n## 테스트에서의 DI 활용\n\nDI의 진정한 가치는 테스트에서 드러납니다. 모의 구현을 주입하면 데이터베이스 없이도 비즈니스 로직을 격리하여 테스트할 수 있습니다:\n\n```rust\n#[tokio::test]\nasync fn test_order_creation_validates_user() {\n    let user_repo = Arc::new(MockUserRepository::empty());\n    let cache = Arc::new(MockCache);\n    let user_service = Arc::new(UserService::new(user_repo, cache));\n\n    let order_repo = Arc::new(MockOrderRepository::new());\n    let order_service = OrderService::new(order_repo, user_service);\n\n    \u002F\u002F 존재하지 않는 사용자로 주문 생성 시도\n    let result = order_service.create_order(999, items).await;\n    assert!(result.is_err());\n    assert_eq!(result.unwrap_err().to_string(), \"User not found\");\n}\n```\n\n이 테스트는 데이터베이스나 Redis가 필요하지 않습니다. 밀리초 내에 실행되며, CI에서 수백 개를 동시에 실행할 수 있습니다.\n\n## 결론\n\nRust에서의 의존성 주입은 프레임워크가 필요하지 않습니다. 트레잇이 인터페이스 역할을 하고, `Arc\u003Cdyn Trait>`가 런타임 다형성을 제공하며, 컴포지션 루트가 배선을 중앙화합니다. 성능이 중요한 경로에서는 제네릭이 제로 비용 추상화를 제공합니다. ServiceLocator 패턴은 대규모 애플리케이션에서 보일러플레이트를 줄여줍니다. 핵심 원칙은 동일합니다: 컴포넌트는 자신의 의존성을 생성하지 않으며, 이를 통해 테스트 가능하고 유연하며 유지 보수가 가능한 코드를 만듭니다.","\u003Ch2 id=\"rust-di\">Rust에서의 DI 문제\u003C\u002Fh2>\n\u003Cp>의존성 주입은 기본적인 설계 원칙입니다: 컴포넌트가 내부에서 의존성을 생성하는 대신 외부에서 의존성을 받아야 합니다. Java나 C#에서는 Spring이나 Autofac과 같은 DI 프레임워크가 이를 자동으로 처리합니다. Rust에는 표준 DI 프레임워크가 없으며, 소유권 모델이 순진한 DI 패턴을 어색하게 만듭니다.\u003C\u002Fp>\n\u003Cp>데이터베이스 연결, 캐시, 로거가 필요한 서비스를 생각해봅시다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">\u002F\u002F 나쁨: 하드코딩된 의존성\nstruct UserService {\n    db: PgPool,          \u002F\u002F 구체적 타입, 모킹 불가\n    cache: RedisClient,  \u002F\u002F 구체적 타입\n}\n\nimpl UserService {\n    fn new() -&gt; Self {\n        \u002F\u002F 최악의 패턴: 자체 의존성 생성\n        Self {\n            db: PgPool::connect(\"postgres:\u002F\u002F...\").await.unwrap(),\n            cache: RedisClient::new(\"redis:\u002F\u002F...\").unwrap(),\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>이것은 테스트 불가능합니다. 실제 PostgreSQL과 Redis 인스턴스 없이는 유닛 테스트를 실행할 수 없습니다. Rust 관용적 DI로 이를 수정합시다.\u003C\u002Fp>\n\u003Ch2 id=\"1-arc\">패턴 1: Arc를 사용한 트레잇 객체\u003C\u002Fh2>\n\u003Cp>동작을 트레잇으로 정의한 다음, 런타임 다형성을 위해 \u003Ccode>Arc&lt;dyn Trait&gt;\u003C\u002Fcode>를 받습니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use std::sync::Arc;\nuse async_trait::async_trait;\n\n#[async_trait]\ntrait UserRepository: Send + Sync {\n    async fn find_by_id(&amp;self, id: i64) -&gt; anyhow::Result&lt;Option&lt;User&gt;&gt;;\n    async fn save(&amp;self, user: &amp;User) -&gt; anyhow::Result&lt;()&gt;;\n}\n\n#[async_trait]\ntrait CacheService: Send + Sync {\n    async fn get(&amp;self, key: &amp;str) -&gt; anyhow::Result&lt;Option&lt;String&gt;&gt;;\n    async fn set(&amp;self, key: &amp;str, value: &amp;str, ttl: u64) -&gt; anyhow::Result&lt;()&gt;;\n}\n\nstruct UserService {\n    repo: Arc&lt;dyn UserRepository&gt;,\n    cache: Arc&lt;dyn CacheService&gt;,\n}\n\nimpl UserService {\n    fn new(\n        repo: Arc&lt;dyn UserRepository&gt;,\n        cache: Arc&lt;dyn CacheService&gt;,\n    ) -&gt; Self {\n        Self { repo, cache }\n    }\n\n    async fn get_user(&amp;self, id: i64) -&gt; anyhow::Result&lt;Option&lt;User&gt;&gt; {\n        \u002F\u002F 먼저 캐시 확인\n        let cache_key = format!(\"user:{}\", id);\n        if let Some(cached) = self.cache.get(&amp;cache_key).await? {\n            return Ok(Some(serde_json::from_str(&amp;cached)?));\n        }\n        \u002F\u002F 캐시 미스 — 데이터베이스 조회\n        if let Some(user) = self.repo.find_by_id(id).await? {\n            let json = serde_json::to_string(&amp;user)?;\n            self.cache.set(&amp;cache_key, &amp;json, 300).await?;\n            return Ok(Some(user));\n        }\n        Ok(None)\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>이제 테스트에서 구현을 교체할 수 있습니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">struct MockUserRepository {\n    users: Vec&lt;User&gt;,\n}\n\n#[async_trait]\nimpl UserRepository for MockUserRepository {\n    async fn find_by_id(&amp;self, id: i64) -&gt; anyhow::Result&lt;Option&lt;User&gt;&gt; {\n        Ok(self.users.iter().find(|u| u.id == id).cloned())\n    }\n    async fn save(&amp;self, _user: &amp;User) -&gt; anyhow::Result&lt;()&gt; {\n        Ok(())\n    }\n}\n\nstruct MockCache;\n\n#[async_trait]\nimpl CacheService for MockCache {\n    async fn get(&amp;self, _key: &amp;str) -&gt; anyhow::Result&lt;Option&lt;String&gt;&gt; {\n        Ok(None) \u002F\u002F 항상 캐시 미스\n    }\n    async fn set(&amp;self, _key: &amp;str, _value: &amp;str, _ttl: u64) -&gt; anyhow::Result&lt;()&gt; {\n        Ok(())\n    }\n}\n\n#[tokio::test]\nasync fn test_get_user() {\n    let repo = Arc::new(MockUserRepository {\n        users: vec![User { id: 1, name: \"Alice\".into() }],\n    });\n    let cache = Arc::new(MockCache);\n    let service = UserService::new(repo, cache);\n    let user = service.get_user(1).await.unwrap();\n    assert_eq!(user.unwrap().name, \"Alice\");\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"2\">패턴 2: 제네릭을 사용한 컴파일 타임 디스패치\u003C\u002Fh2>\n\u003Cp>\u003Ccode>Arc&lt;dyn Trait&gt;\u003C\u002Fcode>는 런타임 비용(vtable 간접 참조, 힙 할당)이 있습니다. 성능이 중요한 코드에서는 제네릭을 사용하여 비용을 제거합니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">struct UserService&lt;R: UserRepository, C: CacheService&gt; {\n    repo: R,\n    cache: C,\n}\n\nimpl&lt;R: UserRepository, C: CacheService&gt; UserService&lt;R, C&gt; {\n    fn new(repo: R, cache: C) -&gt; Self {\n        Self { repo, cache }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>트레이드오프:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cstrong>Arc\u003Cdyn Trait>\u003C\u002Fstrong>: 런타임 다형성, 유연하지만 약간의 오버헤드\u003C\u002Fli>\n\u003Cli>\u003Cstrong>제네릭\u003C\u002Fstrong>: 컴파일 타임 다형성, 제로 비용이지만 더 긴 컴파일 시간과 단형화\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>경험 법칙: 핫 경로(초당 수백만 호출)에서는 제네릭, 나머지에서는 \u003Ccode>Arc&lt;dyn Trait&gt;\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"3-servicelocator\">패턴 3: ServiceLocator 패턴\u003C\u002Fh2>\n\u003Cp>의존성이 많은 대규모 애플리케이션에서는 모든 것을 개별 매개변수로 전달하는 것이 지루해집니다. ServiceLocator 패턴이 이를 단순화합니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use std::any::{Any, TypeId};\nuse std::collections::HashMap;\n\n#[derive(Default)]\nstruct ServiceLocator {\n    services: HashMap&lt;TypeId, Box&lt;dyn Any + Send + Sync&gt;&gt;,\n}\n\nimpl ServiceLocator {\n    fn register&lt;T: Send + Sync + 'static&gt;(&amp;mut self, service: T) {\n        self.services.insert(TypeId::of::&lt;T&gt;(), Box::new(service));\n    }\n\n    fn resolve&lt;T: Send + Sync + 'static&gt;(&amp;self) -&gt; Option&lt;&amp;T&gt; {\n        self.services\n            .get(&amp;TypeId::of::&lt;T&gt;())\n            .and_then(|s| s.downcast_ref::&lt;T&gt;())\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>사용 예:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">let mut locator = ServiceLocator::default();\nlocator.register(Arc::new(PostgresUserRepo::new(pool.clone())) as Arc&lt;dyn UserRepository&gt;);\nlocator.register(Arc::new(RedisCache::new(redis_client)) as Arc&lt;dyn CacheService&gt;);\n\n\u002F\u002F 해결\nlet repo = locator.resolve::&lt;Arc&lt;dyn UserRepository&gt;&gt;().unwrap();\nlet cache = locator.resolve::&lt;Arc&lt;dyn CacheService&gt;&gt;().unwrap();\nlet service = UserService::new(Arc::clone(repo), Arc::clone(cache));\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">컴포지션 루트 패턴\u003C\u002Fh2>\n\u003Cp>의존성 트리를 하나의 장소에서 조립합니다 — 보통 \u003Ccode>main()\u003C\u002Fcode> 함수에서:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">#[tokio::main]\nasync fn main() -&gt; anyhow::Result&lt;()&gt; {\n    \u002F\u002F 인프라 계층\n    let db_pool = PgPool::connect(&amp;env::var(\"DATABASE_URL\")?).await?;\n    let redis = RedisClient::open(env::var(\"REDIS_URL\")?)?;\n\n    \u002F\u002F 리포지토리 계층\n    let user_repo: Arc&lt;dyn UserRepository&gt; = Arc::new(\n        PostgresUserRepo::new(db_pool.clone())\n    );\n    let order_repo: Arc&lt;dyn OrderRepository&gt; = Arc::new(\n        PostgresOrderRepo::new(db_pool.clone())\n    );\n\n    \u002F\u002F 서비스 계층\n    let cache: Arc&lt;dyn CacheService&gt; = Arc::new(\n        RedisCache::new(redis)\n    );\n    let user_service = Arc::new(\n        UserService::new(Arc::clone(&amp;user_repo), Arc::clone(&amp;cache))\n    );\n    let order_service = Arc::new(\n        OrderService::new(Arc::clone(&amp;order_repo), Arc::clone(&amp;user_service))\n    );\n\n    \u002F\u002F HTTP 계층\n    let app = Router::new()\n        .route(\"\u002Fusers\u002F:id\", get(get_user))\n        .route(\"\u002Forders\", post(create_order))\n        .with_state(AppState {\n            user_service,\n            order_service,\n        });\n\n    axum::serve(listener, app).await?;\n    Ok(())\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>이 패턴은 의존성 구성을 단일 장소에 집중시키므로 전체 애플리케이션의 배선을 한눈에 이해할 수 있습니다.\u003C\u002Fp>\n\u003Ch2 id=\"axum-state\">Axum의 State 추출자와의 통합\u003C\u002Fh2>\n\u003Cp>Axum은 \u003Ccode>State\u003C\u002Fcode> 추출자를 통해 자연스러운 DI 메커니즘을 제공합니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">#[derive(Clone)]\nstruct AppState {\n    user_service: Arc&lt;UserService&gt;,\n    order_service: Arc&lt;OrderService&gt;,\n}\n\nasync fn get_user(\n    State(state): State&lt;AppState&gt;,\n    Path(id): Path&lt;i64&gt;,\n) -&gt; Result&lt;Json&lt;User&gt;, StatusCode&gt; {\n    state.user_service\n        .get_user(id)\n        .await\n        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?\n        .map(Json)\n        .ok_or(StatusCode::NOT_FOUND)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"di\">테스트에서의 DI 활용\u003C\u002Fh2>\n\u003Cp>DI의 진정한 가치는 테스트에서 드러납니다. 모의 구현을 주입하면 데이터베이스 없이도 비즈니스 로직을 격리하여 테스트할 수 있습니다:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">#[tokio::test]\nasync fn test_order_creation_validates_user() {\n    let user_repo = Arc::new(MockUserRepository::empty());\n    let cache = Arc::new(MockCache);\n    let user_service = Arc::new(UserService::new(user_repo, cache));\n\n    let order_repo = Arc::new(MockOrderRepository::new());\n    let order_service = OrderService::new(order_repo, user_service);\n\n    \u002F\u002F 존재하지 않는 사용자로 주문 생성 시도\n    let result = order_service.create_order(999, items).await;\n    assert!(result.is_err());\n    assert_eq!(result.unwrap_err().to_string(), \"User not found\");\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>이 테스트는 데이터베이스나 Redis가 필요하지 않습니다. 밀리초 내에 실행되며, CI에서 수백 개를 동시에 실행할 수 있습니다.\u003C\u002Fp>\n\u003Ch2 id=\"\">결론\u003C\u002Fh2>\n\u003Cp>Rust에서의 의존성 주입은 프레임워크가 필요하지 않습니다. 트레잇이 인터페이스 역할을 하고, \u003Ccode>Arc&lt;dyn Trait&gt;\u003C\u002Fcode>가 런타임 다형성을 제공하며, 컴포지션 루트가 배선을 중앙화합니다. 성능이 중요한 경로에서는 제네릭이 제로 비용 추상화를 제공합니다. ServiceLocator 패턴은 대규모 애플리케이션에서 보일러플레이트를 줄여줍니다. 핵심 원칙은 동일합니다: 컴포넌트는 자신의 의존성을 생성하지 않으며, 이를 통해 테스트 가능하고 유연하며 유지 보수가 가능한 코드를 만듭니다.\u003C\u002Fp>\n","ko","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:28.282223Z","Rust에서의 의존성 주입 — ServiceLocator, Arc, 트레잇 객체","프레임워크 없이 Arc, 트레잇 객체, 컴포지션 루트 패턴, ServiceLocator를 사용한 Rust 의존성 주입 구현.","Rust 의존성 주입",null,"index, follow",[22,27],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000022","Performance","performance","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000001","Rust","rust","엔지니어링",[33,39,45],{"id":34,"title":35,"slug":36,"excerpt":37,"locale":12,"category_name":31,"published_at":38},"d0000000-0000-0000-0000-000000000674","2026년, Bali가 동남아시아의 임팩트 테크 허브가 되고 있는 이유","bali-2026-dongnamasia-impaekteu-tekeu-heobeu-iyu","Bali는 동남아시아 스타트업 생태계에서 16위를 차지하고 있습니다. Web3 빌더, AI 지속가능성 스타트업, 에코 여행 테크 기업이 집중되면서, 이 섬은 지역 임팩트 테크의 수도로 자리매김하고 있습니다.","2026-03-28T10:44:49.294484Z",{"id":40,"title":41,"slug":42,"excerpt":43,"locale":12,"category_name":31,"published_at":44},"d0000000-0000-0000-0000-000000000673","ASEAN 데이터 보호 패치워크: 개발자를 위한 컴플라이언스 체크리스트","asean-deiteo-boho-paechiwokeu-gaebaljaleul-wihan-keompeullaieonseuchekeuriseuteu","7개 ASEAN 국가가 포괄적인 데이터 보호법을 시행하고 있으며, 각각 다른 동의 모델, 현지화 요건, 벌칙 구조를 가지고 있습니다. 다중 국가 애플리케이션을 구축하는 개발자를 위한 실용적인 컴플라이언스 체크리스트입니다.","2026-03-28T10:44:49.286400Z",{"id":46,"title":47,"slug":48,"excerpt":49,"locale":12,"category_name":31,"published_at":50},"d0000000-0000-0000-0000-000000000672","Indonesia 290억 달러 디지털 전환: 소프트웨어 기업을 위한 기회","indonesia-290eok-dallleo-dijiteol-jeonhwan-sopeuteuweo-gieopui-gihoe","Indonesia IT 서비스 시장은 2026년 290.3억 달러에 달할 것으로 예상되며, 이는 2025년 243.7억 달러에서 증가한 수치입니다. 클라우드 인프라, AI, 전자상거래, 데이터센터가 동남아시아에서 가장 빠른 성장을 주도하고 있습니다.","2026-03-28T10:44:49.265609Z",{"id":13,"name":52,"slug":53,"bio":54,"photo_url":19,"linkedin":19,"role":55,"created_at":56,"updated_at":56},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]