Files
2nd/10_Wiki/Topic_Programming/Architecture/의존성_주입과_서비스_인터페이스.md
T
Antigravity Agent e2c5471046 wiki: Topic_Blog 신규 문서 일괄 추가 + ASTRA 성장 자산 동기화
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 09:55:38 +09:00

8.2 KiB

id, title, category, status, verification_status, canonical_id, aliases, duplicate_of, source_trust_level, confidence_score, created_at, updated_at, review_reason, merge_history, tags, raw_sources, applied_in, github_commit
id title category status verification_status canonical_id aliases duplicate_of source_trust_level confidence_score created_at updated_at review_reason merge_history tags raw_sources applied_in github_commit
dependency-injection-service-interface 의존성 주입과 서비스 인터페이스 Architecture draft applied
DI
dependency injection
인터페이스
service interface
느슨한 결합
testability
전략 패턴
A 0.93 2026-06-13 2026-06-13
architecture
dependency-injection
interface
design-pattern
astraai
AstraAI/src/core/services.ts
AstraAI/src/extension.ts
AstraAI/src/intelligence/criticAgent.ts
AstraAI/src/agent/multiAgent/workflow.ts
AstraAI

의존성 주입과 서비스 인터페이스

🎯 한 줄 통찰 (One-line insight)

의존성 주입은 "객체가 협력자를 직접 만들지 않고 밖에서 받는" 설계이며, AstraAI 는 인터페이스로 계약을 정의하고, 생성자 옵션 객체·함수 타입으로 구현을 주입해 모듈을 순수하고 교체·테스트 가능하게 만든다 [S1][S3].

🧠 핵심 개념 (Core concepts)

  1. 인터페이스 = 계약: IAIService/IBrainService 는 "무엇을 할 수 있는가"만 정의하고 "어떻게" 는 구현에 맡긴다. 호출부는 인터페이스에만 의존 [S1].
  2. 생성자 주입 (Constructor injection): 협력자를 new X(deps) 처럼 생성 시점에 옵션 객체로 받는다 — 객체가 자기 의존을 숨기지 않고 시그니처에 드러낸다 [S2].
  3. 함수 주입 (Function injection): 무거운 의존(LLM 호출)을 함수 타입 으로 받아, 모듈 자체는 LLM 을 모른 채 순수 함수로 남는다 (CritiqueLlmCall) [S3].
  4. getter 주입 (Lazy): 아직 생성되지 않은 의존은 getProvider: () => provider 처럼 getter 로 — 초기화 순서/순환 의존 회피 [S2].
  5. deps 번들 객체: 여러 협력자를 WorkflowDeps 같은 인터페이스로 묶어 한 번에 전달 [S4].

🧩 추출된 패턴 (Extracted patterns)

  • 인터페이스 선언 → 구현 클래스: interface IAIService { ... } + class AIService implements IAIService { ... }. 정책(엔진 폴백)은 구현에 캡슐화, 호출부는 인터페이스만 본다 [S1].
  • 옵션 객체로 생성자 주입: new AgentExecutor(context, { onStreamLifecycle, lmStudioStreamer, approvalQueue }) — 위치 인자 대신 명명 옵션으로 가독성·확장성 확보 [S2].
  • 함수 타입으로 LLM 추상화: type CritiqueLlmCall = (system, user, maxTokens) => Promise<string> 를 주입받아, criticAgent 는 어떤 엔진이든 무관하게 동작하고 테스트 시 가짜 함수를 끼운다 [S3].
  • deps 인터페이스 + 콜백 게터: WorkflowDeps { getWebview, getAbortSignal, chatHistory, ... } — 동적으로 바뀌는 상태는 게터로 전달해 stale 참조 방지 [S4].
  • 싱글톤 vs 주입 구분: 프로세스 전역 자원(lock/queue)은 싱글톤, 정책·상태를 가진 협력자는 주입 [S2].

📖 세부 내용 (Details)

왜 인터페이스인가

AIService 는 "LM Studio 먼저, 실패 시 Ollama 폴백, 빈 응답은 soft failure" 같은 정책 을 담는다. 호출부(텔레그램 핸들러 등)는 IAIService.chat() 만 알면 되고, 정책이 바뀌어도 호출부는 안 바뀐다. 이것이 "구현이 아니라 추상에 의존하라"(DIP)의 실천 [S1].

함수 주입으로 순수성 유지

criticAgent.ts 헤더 주석은 "모든 LLM 의존은 주입(critique caller) — 모듈 자체는 순수, 테스트 가능" 이라고 명시한다. runCriticReview({ ..., callLlm }) 는 실제 LLM 을 직접 부르지 않고 주입된 callLlm 을 부른다. 덕분에:

  • 프로덕션: agent.tscallNonStreaming 을 주입.
  • 테스트: 고정 문자열을 반환하는 가짜 함수를 주입 → LLM 없이 단위 테스트 [S3].

deps 번들 + getter 의 이유

멀티에이전트 워크플로는 실행 도중 webview/abort signal 이 바뀔 수 있다. 그래서 값이 아니라 게터 를 주입한다:

export interface WorkflowDeps {
    getWebview: () => vscode.Webview | undefined;   // 호출 시점의 최신 webview
    getAbortSignal: () => AbortSignal | undefined;  // 새 controller 의 최신 signal
    chatHistory: ChatMessage[];
}

주석은 "호출자가 stop()+new AbortController() 를 먼저 마쳐야 한다 — getAbortSignal() 은 그 새 controller 의 signal 을 반환해야 함" 이라고 함정을 경고한다 [S4].

⚖️ 비교 및 선택 기준 (Comparison & decision criteria)

항목 (Option) 장점 단점 언제 선택
생성자 주입(옵션 객체) 의존 명시, 교체 쉬움 생성 코드 장황 상태/정책 가진 협력자
함수 주입 모듈 순수, 테스트 최상 콜백 시그니처 관리 LLM·I/O 같은 외부 효과
getter 주입 늦은 바인딩, 순환 회피 호출 시점 의존 동적/초기화 순서 문제
싱글톤 import 간결, 전역 공유 테스트 격리 어려움 프로세스 전역 인프라(lock/queue)

⚖️ 모순 및 업데이트 (Contradictions & updates)

  • DI 컨테이너 없음: AstraAI 는 별도 DI 프레임워크를 쓰지 않고 수동 주입 (activate 에서 직접 조립)한다. 규모가 작고 조립 지점이 한 곳이라 프레임워크 비용이 불필요 — 큰 시스템이면 컨테이너가 유리할 수 있다.
  • 싱글톤의 비용: lockManager/actionQueue 싱글톤은 편하지만 테스트 격리를 어렵게 한다. AstraAI 는 전역성이 본질인 자원에만 한정해 사용한다.

🛠️ 적용 사례 (Applied in summary)

  • AstraAI/src/core/services.ts — IAIService/IBrainService 인터페이스 + 구현 [S1].
  • AstraAI/src/extension.ts — AgentExecutor/SidebarChatProvider 생성자 옵션 주입, getProvider 게터 [S2].
  • AstraAI/src/intelligence/criticAgent.ts — CritiqueLlmCall 함수 주입 [S3].
  • AstraAI/src/agent/multiAgent/workflow.ts — WorkflowDeps 게터 번들 [S4].

💻 코드 패턴 (Code patterns)

// 1) 인터페이스 계약 + 구현 분리 (src/core/services.ts)
export interface IAIService {
    call(prompt: string): Promise<string>;
    chat(req: AIChatRequest): Promise<AIChatResult>;
}
export class AIService implements IAIService { /* 엔진 폴백 정책 캡슐화 */ }

// 2) 생성자 옵션 객체 주입 (src/extension.ts)
const agent = new AgentExecutor(context, {
    onStreamLifecycle: { start: () => lifecycle.onStreamStart(), end: () => lifecycle.onStreamEnd() },
    lmStudioStreamer, approvalQueue,
});

// 3) 함수 주입으로 순수 모듈 (src/intelligence/criticAgent.ts)
export type CritiqueLlmCall = (system: string, user: string, maxTokens: number) => Promise<string>;
export async function runCriticReview(params: { /* ... */ callLlm: CritiqueLlmCall }) {
    const raw = await params.callLlm(system, user, opts.maxTokens);  // 어떤 엔진인지 모름
    return parseCritique(raw);
}

// 4) getter 번들 (src/agent/multiAgent/workflow.ts)
export interface WorkflowDeps {
    getWebview: () => vscode.Webview | undefined;
    getAbortSignal: () => AbortSignal | undefined;
}

검증 상태 및 신뢰도

  • 상태: draft
  • 검증 단계: applied
  • 출처 신뢰도: A
  • 신뢰 점수: 0.93
  • 중복 검사 결과: 신규 생성 (New discovery)

🔗 지식 그래프 (Knowledge Graph)

📚 출처 (Sources)

  • [S1] AstraAI/src/core/services.ts — IAIService/IBrainService + AIService 구현
  • [S2] AstraAI/src/extension.ts — 생성자 옵션 주입, getProvider 게터
  • [S3] AstraAI/src/intelligence/criticAgent.ts — CritiqueLlmCall 함수 주입(순수 모듈)
  • [S4] AstraAI/src/agent/multiAgent/workflow.ts — WorkflowDeps 게터 번들

📝 변경 이력 (Change history)

  • 2026-06-13: AstraAI 코드 분석 기반 초안 생성.