# IMPLEMENTATION.md — 구현 가이드 > 본 문서는 `CLAUDE.md`(연구 방향·정책)에서 분리된 **구현 세부 사항**을 다룬다. > 디렉터리 구조, 네이밍 규칙, 개발 워크플로우, 코드 패턴, 멀티 에이전트 작업 분담 등이 포함된다. > > 연구 동기·목표·평가 지표는 `CLAUDE.md`를 참조한다. --- ## 1. 프로젝트 구조 ``` . ├── proto/ # IDL 정의 │ └── aiot/ │ ├── inference/inference.proto # AI 추론 요청/응답 │ ├── device/device.proto # 디바이스 등록·상태 보고 │ └── gateway/gateway.proto # 게이트웨이 데이터 전달 서비스 │ ├── gen/ # protoc 자동 생성 파일 (수동 수정 금지) │ └── aiot/{inference,device,gateway}/ │ ├── internal/ # 프로젝트 내부 패키지 (외부 import 불가) │ ├── transport/ │ │ ├── transport.go # Listener/Dialer 인터페이스 정의 │ │ ├── quic_listener.go # QUIC 리스너 (quic-go 래핑) │ │ ├── quic_dialer.go # QUIC 다이얼러 (클라이언트 연결) │ │ └── h2_listener.go # HTTP/2 리스너 (비교군) │ ├── gateway/ │ │ ├── gateway.go # 게이트웨이 코어 (라우팅·변환 엔진) │ │ ├── protocol_adapter.go # IoT 프로토콜 어댑터 인터페이스 │ │ ├── mqtt_adapter.go # MQTT → Protobuf │ │ ├── coap_adapter.go # CoAP → Protobuf │ │ └── route_table.go # 서비스 디스커버리·라우팅 테이블 │ ├── server/ │ │ ├── inference_server.go # InferenceService 구현 │ │ ├── device_server.go # DeviceRegistry 구현 │ │ └── gateway_server.go # GatewayService 구현 │ ├── client/ │ │ ├── inference_client.go # gRPC 클라이언트 (재시도·타임아웃 포함) │ │ └── rest_client.go # REST 비교군 클라이언트 │ ├── middleware/ │ │ ├── logging.go # 요청/응답 로깅 인터셉터 │ │ └── metrics.go # latency 측정 인터셉터 │ └── router/ │ └── task_router.go # 엣지 라우팅 로직 (ROI 결과 기반) │ ├── cmd/ │ ├── server/main.go # gRPC 서버 진입점 (--transport=quic|h2 플래그) │ ├── gateway/main.go # gRPC 게이트웨이 진입점 │ ├── rest-server/main.go # REST 비교군 서버 진입점 │ └── benchmark-runner/main.go # 벤치마크 실행 CLI │ ├── benchmarks/ │ ├── scenarios/ │ │ ├── unary_test.go # Unary RPC 벤치마크 │ │ ├── streaming_test.go # Streaming RPC 벤치마크 │ │ └── rest_compare_test.go # REST vs gRPC 비교 벤치마크 │ └── results/ # 날짜별 결과 (YYYY-MM-DD/) │ └── .gitkeep │ ├── docs/ │ ├── decisions/ # ADR (Architecture Decision Records) │ │ └── 001-go-grpc-baseline.md │ └── open-questions.md # 미해결 질문 및 탐색 주제 │ ├── tests/ │ ├── unit/ # 단위 테스트 │ └── integration/ # 통합 테스트 (실제 서버 기동) │ ├── scripts/ │ ├── proto-gen.sh # protoc 컴파일 스크립트 │ ├── tc-setup.sh # tc 네트워크 지연 설정 │ └── tc-reset.sh # tc 설정 초기화 │ ├── docker/ │ ├── Dockerfile.server │ ├── Dockerfile.rest-server │ └── docker-compose.yml # 전체 실험 환경 구성 │ ├── CLAUDE.md # 연구 방향·정책 ├── IMPLEMENTATION.md # (본 파일) 구현 세부 사항 ├── BACKGROUND.md # 연구 수행 배경 ├── go.mod ├── go.sum └── Makefile ``` --- ## 2. 네이밍 규칙 ### 2.1. Proto 정의 | 대상 | 규칙 | 예시 | |------|------|------| | 패키지 | `aiot.{module}` | `aiot.inference` | | 서비스 | `{명사}{역할}Service` | `InferenceService`, `DeviceRegistry` | | RPC 메서드 | `{동사}{명사}` | `PredictFrame`, `RegisterDevice`, `StreamSensorData` | | 메시지 | `{명사}{동사}Request/Response` | `FramePredictRequest`, `DeviceRegisterResponse` | | 열거형 | `UPPER_SNAKE_CASE` | `PROCESSING_STATUS_PENDING` | ### 2.2. Go 코드 | 대상 | 규칙 | |------|------| | 파일 | `snake_case.go` | | 패키지 | 단수 소문자 (`server`, `client`, `router`) | | 인터페이스 | `{명사}` 또는 `{명사}er` (`InferenceService`, `Router`) | | 구조체 | `PascalCase` | | 함수/메서드 | `camelCase` (내부), `PascalCase` (공개) | | 에러 변수 | `Err{명사}` (`ErrConnectionTimeout`) | | 컨텍스트 | 첫 번째 인수, 변수명 `ctx` | --- ## 3. 개발 워크플로우 ### 3.1. 초기 셋업 ```bash # Go 버전 확인 (1.22 이상 권장) go version # protoc 및 플러그인 설치 (최초 1회) go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest # 의존성 설치 go mod tidy ``` ### 3.2. Proto 컴파일 ```bash make proto # 또는 직접 실행: # scripts/proto-gen.sh ``` **`gen/` 디렉터리 파일은 절대 직접 수정하지 않는다.** `.proto` 파일을 수정 후 재생성한다. ### 3.3. 서버/클라이언트 실행 ```bash # gRPC 서버 (전송 계층 선택) make run-server # 기본 go run ./cmd/server --transport=quic --port=50051 go run ./cmd/server --transport=h2 --port=50052 # REST 비교군 서버 make run-rest-server # 벤치마크 make benchmark go run ./cmd/benchmark-runner --scenario unary --requests 10000 --concurrency 10 ``` ### 3.4. 네트워크 조건 시뮬레이션 ```bash # 지연 50ms sudo ./scripts/tc-setup.sh --delay 50ms --interface eth0 # 지연 200ms + 패킷 손실 1% sudo ./scripts/tc-setup.sh --delay 200ms --loss 1% --interface eth0 # 초기화 sudo ./scripts/tc-reset.sh --interface eth0 ``` ### 3.5. 테스트 ```bash # 단위 테스트 go test ./tests/unit/... -v # 통합 테스트 (서버 자동 기동) go test ./tests/integration/... -v -timeout 60s # 벤치마크 (Go 표준) go test ./benchmarks/... -bench=. -benchmem -count=5 ``` --- ## 4. 코드 패턴 ### 4.1. Transport 인터페이스 설계 QUIC↔TCP를 동일한 코드 경로에서 교체할 수 있도록, 전송 계층은 인터페이스로 추상화한다. ```go // internal/transport/transport.go type Listener interface { Accept(ctx context.Context) (net.Conn, error) Addr() net.Addr Close() error } type Dialer interface { Dial(ctx context.Context, addr string) (net.Conn, error) Close() error } ``` - gRPC 서버/클라이언트 생성 시 `Listener`/`Dialer`를 주입받음 - 벤치마크는 동일 코드 경로로 전송 계층만 변경하여 공정 비교 ### 4.2. 컨텍스트와 타임아웃 타임아웃은 **클라이언트에서 설정**한다. 서버는 컨텍스트 취소를 존중한다. ```go func (c *InferenceClient) PredictFrame(ctx context.Context, req *pb.FramePredictRequest) (*pb.FramePredictResponse, error) { ctx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() return c.stub.PredictFrame(ctx, req) } ``` ### 4.3. gRPC 에러 처리 ```go import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // 서버 측 return nil, status.Errorf(codes.InvalidArgument, "frame data is empty") // 클라이언트 측 if st, ok := status.FromError(err); ok { switch st.Code() { case codes.DeadlineExceeded: // 타임아웃 처리 case codes.Unavailable: // 재연결 처리 } } ``` ### 4.4. Latency 측정 인터셉터 `internal/middleware/metrics.go`의 인터셉터를 RPC 등록 시 함께 등록한다. 직접 측정이 필요한 경우 구간을 명시적으로 기록한다. ```go // 등록 예시 grpc.NewServer( grpc.UnaryInterceptor(middleware.MetricsUnaryInterceptor), grpc.StreamInterceptor(middleware.MetricsStreamInterceptor), ) ``` --- ## 5. Makefile 타겟 ```makefile make proto # .proto → gen/ Go 코드 생성 make build # 모든 cmd/ 바이너리 빌드 make run-server # gRPC 서버 실행 (기본 포트 :50051) make run-rest # REST 서버 실행 (기본 포트 :8080) make test # 단위 + 통합 테스트 make benchmark # 전체 벤치마크 시나리오 실행 make lint # golangci-lint 실행 make docker-up # docker-compose로 전체 환경 기동 make docker-down # 환경 종료 make clean # 빌드 아티팩트 및 gen/ 삭제 ``` --- ## 6. 멀티 에이전트 작업 분담 가이드 여러 에이전트가 동시에 작업할 경우 아래 경계를 지킨다. ### 6.1. 독립 작업 도메인 | 도메인 | 담당 파일/디렉터리 | 주의사항 | |--------|------------------|----------| | **Proto 설계** | `proto/` | 필드 번호 변경은 전체 팀에 공지 후 진행 | | **QUIC 전송 계층** | `internal/transport/` | 인터페이스(`Listener`/`Dialer`) 확정 후 구현, quic-go 의존 | | **gRPC 게이트웨이** | `internal/gateway/`, `cmd/gateway/` | transport 인터페이스 의존, 프로토콜 어댑터는 독립 개발 가능 | | **gRPC 서버 구현** | `internal/server/`, `cmd/server/` | `gen/` + transport 인터페이스 의존 | | **REST 비교군** | `internal/client/rest_client.go`, `cmd/rest-server/` | gRPC 서버와 동일한 시나리오 구현 필수 | | **벤치마크 스크립트** | `benchmarks/`, `cmd/benchmark-runner/` | 서버/클라이언트 인터페이스 확정 후 구현 | | **라우터 로직** | `internal/router/` | 인터페이스만 맞추면 독립 개발 가능 | | **문서/분석** | `docs/`, `benchmarks/results/` | 코드 변경 없이 병행 가능 | ### 6.2. 의존 순서 ``` Proto 정의 완료 └─→ gen/ 코드 생성 │ Transport 인터페이스 확정 (internal/transport/transport.go) │ ├─→ QUIC Listener/Dialer 구현 (internal/transport/quic_*.go) ├─→ H2 Listener/Dialer 구현 (internal/transport/h2_*.go) │ ├─→ gRPC 서버 구현 (transport 주입) ├─→ gRPC 게이트웨이 구현 │ ├─→ 프로토콜 어댑터 (MQTT, CoAP — 독립 병행 가능) │ └─→ 라우팅 테이블 ├─→ gRPC 클라이언트 구현 ├─→ REST 비교군 구현 │ └─→ 벤치마크 구현 └─→ 실험 실행 → 결과 기록 ``` ### 6.3. 충돌 방지 - `go.mod` / `go.sum` 변경 시 다른 에이전트에 즉시 알린다 - `proto/` 파일에 새 필드 추가 시 **필드 번호를 기존과 겹치지 않게** 유보 번호를 확인한다 - 실험 결과 파일은 날짜/시나리오명으로 고유하게 명명하여 덮어쓰기를 방지한다 --- ## 7. 자주 수행하는 작업 시나리오 ### 7.1. 새 RPC 추가 1. `proto/aiot/{module}/{service}.proto`에 메시지·서비스 정의 2. `make proto` — `gen/` 코드 생성 3. `internal/server/`에 핸들러 구현 4. `internal/middleware/metrics.go` 인터셉터로 측정 자동화 5. `tests/unit/`에 단위 테스트 추가 6. `benchmarks/scenarios/`에 벤치마크 시나리오 추가 ### 7.2. 새 전송 계층 추가 1. `internal/transport/transport.go`의 `Listener`/`Dialer` 인터페이스 충족 확인 2. `internal/transport/{name}_listener.go`, `{name}_dialer.go` 구현 3. `cmd/server/main.go`의 `--transport` 플래그 분기에 추가 4. 벤치마크 실행 시 `--transport={name}` 옵션으로 비교군 등록 ### 7.3. 새 IoT 프로토콜 어댑터 추가 (게이트웨이) 1. `internal/gateway/protocol_adapter.go`의 어댑터 인터페이스 확인 2. `internal/gateway/{protocol}_adapter.go` 구현 3. 게이트웨이 부트스트랩(`cmd/gateway/main.go`)에서 어댑터 등록 4. `tests/integration/`에 end-to-end 테스트 추가