# node.Node 라우터·레지스트리 이중 참조 제거 ## 이 파일을 읽는 구현 에이전트에게 각 체크리스트 항목을 완료하고 중간 검증 명령을 실행한 뒤 출력을 `CODE_REVIEW-local-G03.md`의 검증 결과 섹션에 붙여 넣으세요. `CODE_REVIEW-local-G03.md`의 "이 파일을 읽는 리뷰 에이전트에게" 섹션에 있는 아카이브·complete.log 생성 지시는 **리뷰 에이전트 전용**이므로 구현 에이전트는 실행하지 마세요. ## 배경 `node.Node`는 현재 `runtime.Router`와 `*adapters.Registry` 둘 다를 필드로 가진다. `OnRunRequest`에서 `router.Resolve()`로 adapter 이름을 결정한 뒤 다시 `registry.Get()`으로 어댑터 인스턴스를 꺼낸다. 라우터가 이름만 반환하기 때문에 불가피하게 이중 참조가 발생하며, `node.Node`가 `adapters.Registry` 구현 세부에 의존하게 된다. `runtime.Router`가 이름 대신 `runtime.Adapter`를 직접 반환하도록 인터페이스를 확장하면 `Node`에서 registry 의존성을 제거할 수 있다. --- ### [REFACTOR-1] `runtime.Router` 인터페이스 확장 — `ResolveAdapter` 추가 **문제** `apps/node/internal/runtime/types.go:128` ```go // Router resolves a RunRequest into a concrete ExecutionSpec. type Router interface { Resolve(ctx context.Context, req RunRequest) (ExecutionSpec, error) } ``` `Resolve`는 어댑터 이름만 담긴 `ExecutionSpec`을 돌려준다. `Node`가 이름을 가지고 다시 `registry.Get()`을 호출해야 한다. **해결 방법** `Router` 인터페이스에 `ResolveAdapter` 메서드를 추가한다. ```go // Router resolves a RunRequest into a concrete ExecutionSpec and the Adapter to execute it. type Router interface { Resolve(ctx context.Context, req RunRequest) (ExecutionSpec, error) ResolveAdapter(ctx context.Context, req RunRequest) (ExecutionSpec, Adapter, error) } ``` 기존 `Resolve`는 하위 호환성 유지를 위해 남겨 두고, `Node`는 `ResolveAdapter`만 사용한다. **수정 파일 및 체크리스트** - [x] `apps/node/internal/runtime/types.go` — `Router` 인터페이스에 `ResolveAdapter` 추가 **테스트 작성**: 스킵 — 인터페이스 추가만이므로 구현 측 테스트로 검증 가능. **중간 검증** ``` go build ./apps/node/internal/runtime/... ``` 기대 결과: 빌드 성공. --- ### [REFACTOR-2] `router.defaultRouter` 에 `ResolveAdapter` 구현 **문제** `apps/node/internal/router/router.go`의 `defaultRouter`가 `runtime.Router`를 구현하는데, 새 메서드가 없으면 인터페이스 불일치 컴파일 오류가 발생한다. **해결 방법** `Resolve` 로직을 내부 헬퍼로 추출하고, `ResolveAdapter`는 같은 헬퍼를 거쳐 registry에서 어댑터를 꺼내 반환한다. ```go func (r *defaultRouter) ResolveAdapter(ctx context.Context, req runtime.RunRequest) (runtime.ExecutionSpec, runtime.Adapter, error) { spec, err := r.Resolve(ctx, req) if err != nil { return runtime.ExecutionSpec{}, nil, err } adapter, ok := r.registry.Get(spec.Adapter) if !ok { return runtime.ExecutionSpec{}, nil, fmt.Errorf("router: adapter %q not found", spec.Adapter) } return spec, adapter, nil } ``` **수정 파일 및 체크리스트** - [x] `apps/node/internal/router/router.go` — `ResolveAdapter` 메서드 추가 **테스트 작성**: `apps/node/internal/router/router_test.go`에 `TestResolveAdapter_Found` / `TestResolveAdapter_NotFound` 추가. - `TestResolveAdapter_Found`: 등록된 어댑터 이름으로 요청 시 올바른 spec과 adapter 인스턴스가 반환되는지 확인. - `TestResolveAdapter_NotFound`: 등록되지 않은 어댑터 이름으로 요청 시 에러 반환 확인. **중간 검증** ``` go test ./apps/node/internal/router/... ``` 기대 결과: 신규 테스트 포함 모두 PASS. --- ### [REFACTOR-3] `node.Node`에서 `registry` 필드 제거, `ResolveAdapter` 사용 **문제** `apps/node/internal/node/node.go:29–33` ```go type Node struct { nodeID string router runtime.Router registry *adapters.Registry // ← 제거 대상 store *store.Store ... } ``` `OnRunRequest:84`에서 `registry.Get(spec.Adapter)`를 직접 호출한다. **해결 방법** `registry` 필드를 제거하고, `OnRunRequest` 내 어댑터 조회를 `router.ResolveAdapter` 한 번의 호출로 대체한다. Before (`node.go:75–88` 근방): ```go spec, err := n.router.Resolve(ctx, rr) if err != nil { return fmt.Errorf("node: resolve: %w", err) } adapter, ok := n.registry.Get(spec.Adapter) if !ok { return fmt.Errorf("node: adapter %q not found after routing", spec.Adapter) } ``` After: ```go spec, adapter, err := n.router.ResolveAdapter(ctx, rr) if err != nil { return fmt.Errorf("node: resolve: %w", err) } ``` `New()` 시그니처에서 `registry *adapters.Registry` 파라미터를 제거한다. **수정 파일 및 체크리스트** - [x] `apps/node/internal/node/node.go` — `registry` 필드 제거, `New()` 파라미터 제거, `OnRunRequest` 수정 - [x] `apps/node/internal/bootstrap/module.go` — `node.New(...)` 호출에서 `reg` 인수 제거 - [x] `apps/node/internal/node/node_test.go` — `makeNode` 시그니처 및 호출 수정 **테스트 작성**: 기존 테스트에서 `makeNode`가 `fixedRouter`를 사용하므로 `fixedRouter`에도 `ResolveAdapter` 구현 추가가 필요하다. **중간 검증** ``` go test ./apps/node/... ``` 기대 결과: 모두 PASS, registry 참조 없음. --- ## 수정 파일 요약 | 파일 | 항목 | |------|------| | `apps/node/internal/runtime/types.go` | REFACTOR-1 | | `apps/node/internal/router/router.go` | REFACTOR-2 | | `apps/node/internal/router/router_test.go` | REFACTOR-2 | | `apps/node/internal/node/node.go` | REFACTOR-3 | | `apps/node/internal/bootstrap/module.go` | REFACTOR-3 | | `apps/node/internal/node/node_test.go` | REFACTOR-3 | ## 의존 관계 및 구현 순서 REFACTOR-1 → REFACTOR-2 → REFACTOR-3 순서로 구현해야 한다. 컴파일 의존성이 있다. ## 최종 검증 ``` go build ./apps/node/... go test ./apps/node/... ``` 기대 결과: 빌드 성공, 모든 테스트 PASS, `node.Node` 구조체에 `registry` 필드 없음.