refactor: rename examples/ directory to lib/ and update all path references

This commit is contained in:
2026-07-17 19:21:21 +09:00
parent 8f183d597e
commit 8bb11fe205
17 changed files with 49 additions and 49 deletions
+7 -7
View File
@@ -25,7 +25,7 @@
grpccanary/ grpccanary/
├── README.md # 본 프로젝트 종합 소개 및 실행 가이드 (교육용) ├── README.md # 본 프로젝트 종합 소개 및 실행 가이드 (교육용)
├── go.mod / go.sum # Go 모듈 의존성 정의 (Go 1.25.4+) ├── go.mod / go.sum # Go 모듈 의존성 정의 (Go 1.25.4+)
├── examples/ ├── lib/
│ ├── main.go # 학습 예제 통합 실행 진입점 (수동 전환) │ ├── main.go # 학습 예제 통합 실행 진입점 (수동 전환)
│ ├── jsonexample/ # 1단계: JSON 데이터 다루기 실습 예제 │ ├── jsonexample/ # 1단계: JSON 데이터 다루기 실습 예제
│ ├── httpentity/ # 2단계: HTTP & Gin 웹 서버 실습 예제 (WIP) │ ├── httpentity/ # 2단계: HTTP & Gin 웹 서버 실습 예제 (WIP)
@@ -39,13 +39,13 @@ grpccanary/
└── GRPC.md # 3단계: gRPC 통신 구현 상세 가이드 └── GRPC.md # 3단계: gRPC 통신 구현 상세 가이드
``` ```
### 1단계: JSON 데이터 다루기 (`examples/jsonexample`) ### 1단계: JSON 데이터 다루기 (`lib/jsonexample`)
* Go 표준 라이브러리인 `encoding/json`을 활용하여 구조체(Struct)와 JSON 데이터 간의 마샬링(Serialization) 및 언마샬링(Deserialization) 기법을 학습합니다. * Go 표준 라이브러리인 `encoding/json`을 활용하여 구조체(Struct)와 JSON 데이터 간의 마샬링(Serialization) 및 언마샬링(Deserialization) 기법을 학습합니다.
### 2단계: HTTP & Gin 웹 서버 (`examples/httpentity`) ### 2단계: HTTP & Gin 웹 서버 (`lib/httpentity`)
* 대중적인 Go 웹 프레임워크 `gin-gonic`을 활용해 RESTful API 사양을 구축하는 방법을 이해합니다. (현재 주석 해제 후 실습하도록 설계된 Work-in-Progress 단계) * 대중적인 Go 웹 프레임워크 `gin-gonic`을 활용해 RESTful API 사양을 구축하는 방법을 이해합니다. (현재 주석 해제 후 실습하도록 설계된 Work-in-Progress 단계)
### 3단계: gRPC 통신 구현 (`examples/grpcentity`) ### 3단계: gRPC 통신 구현 (`lib/grpcentity`)
* `.proto` 정의를 바탕으로 통신 스키마 계약을 강제하고, Go 언어로 gRPC 서버를 띄워 클라이언트가 날짜/시간, 무작위 비밀번호 및 정수 데이터를 실시간 원격 호출로 송수신하는 분산 통신 기초를 학습합니다. * `.proto` 정의를 바탕으로 통신 스키마 계약을 강제하고, Go 언어로 gRPC 서버를 띄워 클라이언트가 날짜/시간, 무작위 비밀번호 및 정수 데이터를 실시간 원격 호출로 송수신하는 분산 통신 기초를 학습합니다.
* `.proto` 정의를 바탕으로 통신 스키마 계약을 강제하고, Go 언어로 gRPC 서버를 띄워 클라이언트가 날짜/시간, 무작위 비밀번호 및 정수 데이터를 실시간 원격 호출로 송수신하는 분산 통신 기초를 학습합니다. * `.proto` 정의를 바탕으로 통신 스키마 계약을 강제하고, Go 언어로 gRPC 서버를 띄워 클라이언트가 날짜/시간, 무작위 비밀번호 및 정수 데이터를 실시간 원격 호출로 송수신하는 분산 통신 기초를 학습합니다.
@@ -73,13 +73,13 @@ grpccanary/
### 2. 실습 예제 실행 방법 (진입점 전환) ### 2. 실습 예제 실행 방법 (진입점 전환)
이 프로젝트는 교육적 목적을 위해 **하나의 `main.go` 파일 안에서 주석 처리를 통해 학습 단계를 수동 전환**하여 실행하도록 설계되어 있습니다. 이 프로젝트는 교육적 목적을 위해 **하나의 `main.go` 파일 안에서 주석 처리를 통해 학습 단계를 수동 전환**하여 실행하도록 설계되어 있습니다.
1. **[examples/main.go](examples/main.go)** 파일을 엽니다. 1. **[lib/main.go](lib/main.go)** 파일을 엽니다.
2. 아래와 같이 실행하고자 하는 예제의 주석을 해제하고 다른 예제는 주석 처리합니다. 2. 아래와 같이 실행하고자 하는 예제의 주석을 해제하고 다른 예제는 주석 처리합니다.
* *JSON 예제 실행 시*: `jsonexample.JsonParsingExample()` 활성화 * *JSON 예제 실행 시*: `jsonexample.JsonParsingExample()` 활성화
* *gRPC 예제 실행 시*: `grpcSample()` 활성화 (미사용 import 에러를 피하기 위해 `jsonexample` import는 주석 처리 필요) * *gRPC 예제 실행 시*: `grpcSample()` 활성화 (미사용 import 에러를 피하기 위해 `jsonexample` import는 주석 처리 필요)
3. 루트 디렉토리에서 다음 명령어로 실행합니다: 3. 루트 디렉토리에서 다음 명령어로 실행합니다:
```bash ```bash
go run ./examples go run ./lib
``` ```
### 3. gRPC 예제 동작 흐름 및 기대 출력 ### 3. gRPC 예제 동작 흐름 및 기대 출력
@@ -106,4 +106,4 @@ Sensing Update Message: Sensing data updated successfully for device sensor-room
* **[docs/JSON.md](docs/JSON.md)**: 1단계 JSON 데이터 다루기 상세 학습 가이드 * **[docs/JSON.md](docs/JSON.md)**: 1단계 JSON 데이터 다루기 상세 학습 가이드
* **[docs/HTTP.md](docs/HTTP.md)**: 2단계 HTTP & Gin 웹 프레임워크 상세 학습 가이드 * **[docs/HTTP.md](docs/HTTP.md)**: 2단계 HTTP & Gin 웹 프레임워크 상세 학습 가이드
* **[docs/GRPC.md](docs/GRPC.md)**: 3단계 gRPC & Protobuf 상세 학습 가이드 (컴파일 절차 및 소스코드 구현체 분석 포함) * **[docs/GRPC.md](docs/GRPC.md)**: 3단계 gRPC & Protobuf 상세 학습 가이드 (컴파일 절차 및 소스코드 구현체 분석 포함)
* **[examples/grpcentity/README.md](examples/grpcentity/README.md)**: gRPC 실습 디렉토리 안내 및 `docs/GRPC.md` 심화 가이드로의 링크 * **[lib/grpcentity/README.md](lib/grpcentity/README.md)**: gRPC 실습 디렉토리 안내 및 `docs/GRPC.md` 심화 가이드로의 링크
+9 -9
View File
@@ -82,7 +82,7 @@ gRPC는 HTTP/2를 기반으로 구축된 구글의 고성능 오픈소스 원격
### 3.4 명세서 소스코드 및 구체적 분석 ### 3.4 명세서 소스코드 및 구체적 분석
실습 디렉토리 내에 선언된 [protoapi.proto](../examples/grpcentity/protoapi.proto) 명세서 코드는 앞서 기획한 `IoTService` 통신 구조를 수립하기 위해 다음과 같이 사양을 기재해 둡니다. 실습 디렉토리 내에 선언된 [protoapi.proto](../lib/grpcentity/protoapi.proto) 명세서 코드는 앞서 기획한 `IoTService` 통신 구조를 수립하기 위해 다음과 같이 사양을 기재해 둡니다.
```proto ```proto
syntax = "proto3"; syntax = "proto3";
@@ -175,7 +175,7 @@ gRPC 빌드 및 코드 생성 환경을 구축하기 위해 사용되는 세 가
작성된 스키마 명세를 빌드하여 Go 소스코드를 생성하기 위해 다음 명령어를 구동합니다: 작성된 스키마 명세를 빌드하여 Go 소스코드를 생성하기 위해 다음 명령어를 구동합니다:
```bash ```bash
# 해당 실습 디렉터리로 이동 후 컴파일 실행 # 해당 실습 디렉터리로 이동 후 컴파일 실행
cd examples/grpcentity cd lib/grpcentity
protoc --go_out=. --go-grpc_out=. protoapi.proto protoc --go_out=. --go-grpc_out=. protoapi.proto
``` ```
@@ -186,7 +186,7 @@ protoc --go_out=. --go-grpc_out=. protoapi.proto
**Go 패키지 지정 옵션과의 결합 규칙**: **Go 패키지 지정 옵션과의 결합 규칙**:
해당 출력 경로 옵션들은 단독으로 파일의 최종 위치를 고정하지 않습니다. 컴파일러는 지정된 기준 경로(예: `.`)에 `.proto` 스펙 내부의 `option go_package = "./protoapi;protoapi"` 설정값을 조합하여 최종 디렉터리 경로를 생성합니다. 해당 출력 경로 옵션들은 단독으로 파일의 최종 위치를 고정하지 않습니다. 컴파일러는 지정된 기준 경로(예: `.`)에 `.proto` 스펙 내부의 `option go_package = "./protoapi;protoapi"` 설정값을 조합하여 최종 디렉터리 경로를 생성합니다.
이에 따라 **컴파일 대상 디렉터리(`.`)**와 **상세 패키지 주소(`./protoapi`)**가 결합되어 `examples/grpcentity/protoapi/` 경로가 자동 생성되며, 그 하위에 다음 소스코드들이 정상 배치됩니다: 이에 따라 **컴파일 대상 디렉터리(`.`)**와 **상세 패키지 주소(`./protoapi`)**가 결합되어 `lib/grpcentity/protoapi/` 경로가 자동 생성되며, 그 하위에 다음 소스코드들이 정상 배치됩니다:
* `protoapi.pb.go`: 명세에 정의된 메시지(Message) 규격을 Go 구조체로 변환한 파일입니다. * `protoapi.pb.go`: 명세에 정의된 메시지(Message) 규격을 Go 구조체로 변환한 파일입니다.
* `protoapi_grpc.pb.go`: 클라이언트와 서버 통신을 위한 원격 호출(Service) 규격을 구현한 파일입니다. * `protoapi_grpc.pb.go`: 클라이언트와 서버 통신을 위한 원격 호출(Service) 규격을 구현한 파일입니다.
@@ -196,7 +196,7 @@ protoc --go_out=. --go-grpc_out=. protoapi.proto
컴파일러를 통해 통신을 위한 스터브(Stub) 코드가 확보되었으므로, 이를 기반으로 서버와 클라이언트의 비즈니스 로직을 연결하는 상세 코드를 검토합니다. 컴파일러를 통해 통신을 위한 스터브(Stub) 코드가 확보되었으므로, 이를 기반으로 서버와 클라이언트의 비즈니스 로직을 연결하는 상세 코드를 검토합니다.
### 5.1 gRPC 서버 구현 분석 ([server.go](../examples/grpcentity/server.go)) ### 5.1 gRPC 서버 구현 분석 ([server.go](../lib/grpcentity/server.go))
* **서버 서비스 인터페이스 매핑 구조체 (`IoTServer`)**: * **서버 서비스 인터페이스 매핑 구조체 (`IoTServer`)**:
```go ```go
@@ -240,7 +240,7 @@ protoc --go_out=. --go-grpc_out=. protoapi.proto
* **쉬운 설명**: `:8080` 포트로 통하는 소켓(전화선)을 개통하고, 기기들의 전화(접속 및 호출)가 오기를 기다리며 대기 상태로 들어가는 서버 구동 시작점입니다. * **쉬운 설명**: `:8080` 포트로 통하는 소켓(전화선)을 개통하고, 기기들의 전화(접속 및 호출)가 오기를 기다리며 대기 상태로 들어가는 서버 구동 시작점입니다.
* **상세 설명**: 지정된 포트(기본 포트 `:8080`)의 TCP 소켓 포트를 활성화하고, 클라이언트의 접속 및 RPC 서비스 호출에 대해 지속적으로 대기하는 리스너 구동의 진입점입니다. * **상세 설명**: 지정된 포트(기본 포트 `:8080`)의 TCP 소켓 포트를 활성화하고, 클라이언트의 접속 및 RPC 서비스 호출에 대해 지속적으로 대기하는 리스너 구동의 진입점입니다.
### 5.2 gRPC 클라이언트 구현 분석 ([client.go](../examples/grpcentity/client.go)) ### 5.2 gRPC 클라이언트 구현 분석 ([client.go](../lib/grpcentity/client.go))
* **원격 서비스 클라이언트 기동 (`NewIoTServiceClient`)**: * **원격 서비스 클라이언트 기동 (`NewIoTServiceClient`)**:
```go ```go
@@ -276,7 +276,7 @@ protoc --go_out=. --go-grpc_out=. protoapi.proto
### 6.1 `bind: address already in use` (네트워크 소켓 포트 충돌) ### 6.1 `bind: address already in use` (네트워크 소켓 포트 충돌)
* **발생 원인**: gRPC 서버 기동 시 설정한 통신 포트 `:8080`이 이미 다른 네트워크 프로세스나 이전 실습 서버의 비정상 종료 등으로 인해 점유되어 바인딩에 실패한 상태입니다. * **발생 원인**: gRPC 서버 기동 시 설정한 통신 포트 `:8080`이 이미 다른 네트워크 프로세스나 이전 실습 서버의 비정상 종료 등으로 인해 점유되어 바인딩에 실패한 상태입니다.
* **조치 방법**: * **조치 방법**:
- `examples/main.go`의 `port` 변수 및 [server.go](../examples/grpcentity/server.go)의 `port` 전역 변수값을 동시에 다른 유휴 포트(예: `:9090`)로 변경하여 구동을 시도합니다. - `lib/main.go`의 `port` 변수 및 [server.go](../lib/grpcentity/server.go)의 `port` 전역 변수값을 동시에 다른 유휴 포트(예: `:9090`)로 변경하여 구동을 시도합니다.
- **🚨 소스코드 설계 제약**: `server.go`에 기재된 `ServerRun(addr string)` 함수는 메인 환경으로부터 주입받은 `addr` 인자값을 사용하지 않고, 패키지 내부 전역 변수인 `port` (하드코딩된 값 `:8080`)를 소켓 리슨 파라미터로 직접 참조하도록 되어 있습니다. 따라서 바인딩 충돌 회피를 위해서는 `main.go`뿐만 아니라 반드시 `server.go` 파일 내부의 전역 변수인 `port` 역시 함께 갱신해 주어야 실제 바인딩 포트 변경이 유효하게 처리됩니다. - **🚨 소스코드 설계 제약**: `server.go`에 기재된 `ServerRun(addr string)` 함수는 메인 환경으로부터 주입받은 `addr` 인자값을 사용하지 않고, 패키지 내부 전역 변수인 `port` (하드코딩된 값 `:8080`)를 소켓 리슨 파라미터로 직접 참조하도록 되어 있습니다. 따라서 바인딩 충돌 회피를 위해서는 `main.go`뿐만 아니라 반드시 `server.go` 파일 내부의 전역 변수인 `port` 역시 함께 갱신해 주어야 실제 바인딩 포트 변경이 유효하게 처리됩니다.
--- ---
@@ -324,7 +324,7 @@ message DownloadRequest {
``` ```
* **설계 포인트**: `stream` 키워드가 들어간 위치에 주목합니다. `UploadFile`은 입력에 `stream`이 붙어 클라이언트 스트리밍을, `DownloadFile`은 반환(returns)에 `stream`이 붙어 서버 스트리밍 채널을 개설합니다. * **설계 포인트**: `stream` 키워드가 들어간 위치에 주목합니다. `UploadFile`은 입력에 `stream`이 붙어 클라이언트 스트리밍을, `DownloadFile`은 반환(returns)에 `stream`이 붙어 서버 스트리밍 채널을 개설합니다.
### 7.2 서버 저장 및 송수신 구현 ([server.go](../examples/grpcentity/server.go)) ### 7.2 서버 저장 및 송수신 구현 ([server.go](../lib/grpcentity/server.go))
인메모리 파일 저장소(`fileStore`)를 구현하고 목록 조회(`ListFiles`) 및 서버 다운로드 스트리밍(`DownloadFile`) 핸들러를 정의합니다: 인메모리 파일 저장소(`fileStore`)를 구현하고 목록 조회(`ListFiles`) 및 서버 다운로드 스트리밍(`DownloadFile`) 핸들러를 정의합니다:
```go ```go
@@ -392,7 +392,7 @@ func (IoTServer) DownloadFile(r *protoapi.DownloadRequest, stream protoapi.IoTSe
* **목록 조회**: 동시 접근 보호(Race condition 방지)를 위해 읽기 전용 락(`RLock`)을 획득한 후 인메모리 맵을 순회하며 메타데이터 구조체 목록을 집계해 반환합니다. * **목록 조회**: 동시 접근 보호(Race condition 방지)를 위해 읽기 전용 락(`RLock`)을 획득한 후 인메모리 맵을 순회하며 메타데이터 구조체 목록을 집계해 반환합니다.
* **파일 다운로드**: 대상 파일 쿼리 실패 시 gRPC 표준 에러(`codes.NotFound`)를 반환합니다. 검증 통과 시 루프 내에서 가상 윈도우 슬라이싱을 집행해 청크 구조체를 구성하고, `stream.Send()`로 직렬화 패킷을 클라이언트 버퍼 큐에 기입합니다. * **파일 다운로드**: 대상 파일 쿼리 실패 시 gRPC 표준 에러(`codes.NotFound`)를 반환합니다. 검증 통과 시 루프 내에서 가상 윈도우 슬라이싱을 집행해 청크 구조체를 구성하고, `stream.Send()`로 직렬화 패킷을 클라이언트 버퍼 큐에 기입합니다.
### 7.3 클라이언트 송수신 기동 ([client.go](../examples/grpcentity/client.go)) ### 7.3 클라이언트 송수신 기동 ([client.go](../lib/grpcentity/client.go))
클라이언트는 업로드에 성공한 뒤, 서버에 파일 목록 조회를 요구하고, 다운로드 스트림을 개설해 조각 데이터를 재조립하여 무결성을 검사합니다. 클라이언트는 업로드에 성공한 뒤, 서버에 파일 목록 조회를 요구하고, 다운로드 스트림을 개설해 조각 데이터를 재조립하여 무결성을 검사합니다.
```go ```go
@@ -435,7 +435,7 @@ func AskDownloadFile(ctx context.Context, m protoapi.IoTServiceClient, fileName
본 기초 실습을 끝마치셨다면, 아래 과제를 해결해보세요: 본 기초 실습을 끝마치셨다면, 아래 과제를 해결해보세요:
1. **약속 스펙 확장해 보기**: [protoapi.proto](../examples/grpcentity/protoapi.proto) 파일에 새로운 환경 데이터(예: 미세먼지 수치 `double Dust = 4;`)를 슬쩍 얹어본 뒤, 직접 번역기를 새로 돌리고 Go 소스코드를 고치며 확장해 봅니다. 1. **약속 스펙 확장해 보기**: [protoapi.proto](../lib/grpcentity/protoapi.proto) 파일에 새로운 환경 데이터(예: 미세먼지 수치 `double Dust = 4;`)를 슬쩍 얹어본 뒤, 직접 번역기를 새로 돌리고 Go 소스코드를 고치며 확장해 봅니다.
2. **proto 명세서 물리 분할하기 (실전 모듈화)**: 현재 단일 파일로 구성된 `protoapi.proto`를 기능 성격에 맞춰 `iot_service.proto`(Unary 제어 메시지)와 `file_transfer.proto`(스트리밍 전송 메시지) 2개의 파일로 쪼개어 구성해 봅니다. 이때 두 파일의 `option go_package` 공유 방식과 번역기(`protoc`) 명령어를 다중 타겟(또는 `*.proto`)으로 조율하여 정상 빌드해 보세요. 2. **proto 명세서 물리 분할하기 (실전 모듈화)**: 현재 단일 파일로 구성된 `protoapi.proto`를 기능 성격에 맞춰 `iot_service.proto`(Unary 제어 메시지)와 `file_transfer.proto`(스트리밍 전송 메시지) 2개의 파일로 쪼개어 구성해 봅니다. 이때 두 파일의 `option go_package` 공유 방식과 번역기(`protoc`) 명령어를 다중 타겟(또는 `*.proto`)으로 조율하여 정상 빌드해 보세요.
--- ---
+2 -2
View File
@@ -27,9 +27,9 @@ Go 진영에서 대표적으로 사랑받는 웹 프레임워크 중 하나로,
--- ---
## 3. 실습 코드 분석 (`examples/httpentity/server.go`) ## 3. 실습 코드 분석 (`lib/httpentity/server.go`)
저장소의 [server.go](../examples/httpentity/server.go) 파일에는 Gin 라우터를 구성하고 API 서버와 정적 웹 서빙을 혼합하여 라우팅을 우회 처리하는 설계 패턴이 주석 상태로 존재합니다. 저장소의 [server.go](../lib/httpentity/server.go) 파일에는 Gin 라우터를 구성하고 API 서버와 정적 웹 서빙을 혼합하여 라우팅을 우회 처리하는 설계 패턴이 주석 상태로 존재합니다.
### 3.1 라우터 엔진 분리 및 통합 핸들링 ### 3.1 라우터 엔진 분리 및 통합 핸들링
```go ```go
+2 -2
View File
@@ -21,9 +21,9 @@ Go 구조체로 JSON 데이터를 다룰 때 핵심적인 과정입니다.
--- ---
## 2. 실습 코드 분석 (`examples/jsonexample/json_parser.go`) ## 2. 실습 코드 분석 (`lib/jsonexample/json_parser.go`)
저장소의 [json_parser.go](../examples/jsonexample/json_parser.go) 파일은 (1) `map[string]interface{}`와의 직렬화 및 (2) 구조체(`Person`)를 이용한 매핑 방식을 모두 다룹니다. 저장소의 [json_parser.go](../lib/jsonexample/json_parser.go) 파일은 (1) `map[string]interface{}`와의 직렬화 및 (2) 구조체(`Person`)를 이용한 매핑 방식을 모두 다룹니다.
### 2.1 구조체 태그(Struct Tag) 정의 ### 2.1 구조체 태그(Struct Tag) 정의
```go ```go
@@ -1,4 +1,4 @@
# examples/grpcentity 실습 설명서 # lib/grpcentity 실습 설명서
본 디렉토리는 Go 언어를 활용한 gRPC 서버 및 클라이언트 실습 예제를 포함하고 있습니다. 본 디렉토리는 Go 언어를 활용한 gRPC 서버 및 클라이언트 실습 예제를 포함하고 있습니다.
@@ -16,11 +16,11 @@
## 🚀 빠른 실행 방법 ## 🚀 빠른 실행 방법
이 예제는 리포지토리 루트의 `examples/main.go`를 통해 실행됩니다. 이 예제는 리포지토리 루트의 `lib/main.go`를 통해 실행됩니다.
1. 리포지토리 루트의 `examples/main.go`를 엽니다. 1. 리포지토리 루트의 `lib/main.go`를 엽니다.
2. `main()` 함수 내에서 `grpcSample()`의 주석을 해제합니다. 2. `main()` 함수 내에서 `grpcSample()`의 주석을 해제합니다.
3. 리포지토리 루트에서 다음 명령어를 실행합니다: 3. 리포지토리 루트에서 다음 명령어를 실행합니다:
```bash ```bash
go run ./examples go run ./lib
``` ```
@@ -3,7 +3,7 @@ package entity
import ( import (
"context" "context"
"fmt" "fmt"
"grpccanary/examples/grpcentity/protoapi" "grpccanary/lib/grpcentity/protoapi"
"io" "io"
"math/rand" "math/rand"
"time" "time"
@@ -3,7 +3,7 @@ package entity
import ( import (
"context" "context"
"fmt" "fmt"
"grpccanary/examples/grpcentity/protoapi" "grpccanary/lib/grpcentity/protoapi"
"io" "io"
"math/rand" "math/rand"
"net" "net"
@@ -1,4 +1,4 @@
# examples/httpentity 실습 설명서 # lib/httpentity 실습 설명서
본 디렉토리는 Go 언어 웹 프레임워크인 Gin(`gin-gonic`)을 활용한 HTTP 웹 API 서버 실습 예제를 포함하고 있습니다. 본 디렉토리는 Go 언어 웹 프레임워크인 Gin(`gin-gonic`)을 활용한 HTTP 웹 API 서버 실습 예제를 포함하고 있습니다.
@@ -15,11 +15,11 @@
## 🚀 빠른 실행 방법 ## 🚀 빠른 실행 방법
이 예제는 리포지토리 루트의 `examples/main.go`를 통해 실행됩니다. 이 예제는 리포지토리 루트의 `lib/main.go`를 통해 실행됩니다.
1. 리포지토리 루트`examples/main.go`를 엽니다. 1. 리포지토리 루트 of `lib/main.go`를 엽니다.
2. `main()` 함수 내에서 `httpentity` API 호출 주석을 해제합니다. (현재 주석 상태로, 추후 구현 완성을 위한 예제 뼈대 파일입니다.) 2. `main()` 함수 내에서 `httpentity` API 호출 주석을 해제합니다. (현재 주석 상태로, 추후 구현 완성을 위한 예제 뼈대 파일입니다.)
3. 리포지토리 루트에서 다음 명령어를 실행합니다: 3. 리포지토리 루트에서 다음 명령어를 실행합니다:
```bash ```bash
go run ./examples go run ./lib
``` ```
@@ -1,4 +1,4 @@
# examples/jsonexample 실습 설명서 # lib/jsonexample 실습 설명서
본 디렉토리는 Go 언어 표준 라이브러리(`encoding/json`)를 활용한 JSON 데이터 직렬화 및 역직렬화 실습 예제를 포함하고 있습니다. 본 디렉토리는 Go 언어 표준 라이브러리(`encoding/json`)를 활용한 JSON 데이터 직렬화 및 역직렬화 실습 예제를 포함하고 있습니다.
@@ -15,11 +15,11 @@
## 🚀 빠른 실행 방법 ## 🚀 빠른 실행 방법
이 예제는 리포지토리 루트의 `examples/main.go`를 통해 실행됩니다. 이 예제는 리포지토리 루트의 `lib/main.go`를 통해 실행됩니다.
1. 리포지토리 루트의 `examples/main.go`를 엽니다. 1. 리포지토리 루트의 `lib/main.go`를 엽니다.
2. `main()` 함수 내에서 `jsonexample.JsonParsingExample()`의 주석을 해제합니다. 2. `main()` 함수 내에서 `jsonexample.JsonParsingExample()`의 주석을 해제합니다.
3. 리포지토리 루트에서 다음 명령어를 실행합니다: 3. 리포지토리 루트에서 다음 명령어를 실행합니다:
```bash ```bash
go run ./examples go run ./lib
``` ```
+2 -2
View File
@@ -2,8 +2,8 @@ package main
import ( import (
"fmt" "fmt"
entity "grpccanary/examples/grpcentity" entity "grpccanary/lib/grpcentity"
"grpccanary/examples/jsonexample" "grpccanary/lib/jsonexample"
"time" "time"
) )
+13 -13
View File
@@ -22,7 +22,7 @@ grpccanary/
│ ├── protoapi.pb.go # 메시지 타입 (protoc-gen-go) │ ├── protoapi.pb.go # 메시지 타입 (protoc-gen-go)
│ └── protoapi_grpc.pb.go # 서비스/클라이언트 stub (protoc-gen-go-grpc) │ └── protoapi_grpc.pb.go # 서비스/클라이언트 stub (protoc-gen-go-grpc)
├── obj.json # JSON 예제용 샘플 데이터 파일 ├── obj.json # JSON 예제용 샘플 데이터 파일
├── examples/ ├── lib/
│ ├── main.go # 실행 진입점 (현재는 JSON 예제만 호출하도록 설정됨) │ ├── main.go # 실행 진입점 (현재는 JSON 예제만 호출하도록 설정됨)
│ ├── jsonexample/ │ ├── jsonexample/
│ │ └── json_parser.go # encoding/json 마샬링·언마샬링 예제 │ │ └── json_parser.go # encoding/json 마샬링·언마샬링 예제
@@ -50,16 +50,16 @@ grpccanary/
## 3. 핵심 구성 요소 (학습용 Go 코드) ## 3. 핵심 구성 요소 (학습용 Go 코드)
### 3.1 진입점 — `examples/main.go` ### 3.1 진입점 — `lib/main.go`
- 프로젝트의 유일한 `main()` 함수. `var port = ":8080"`을 정의하고 있으며, 기본 상태에서는 `jsonexample.JsonParsingExample()`만 호출합니다. - 프로젝트의 유일한 `main()` 함수. `var port = ":8080"`을 정의하고 있으며, 기본 상태에서는 `jsonexample.JsonParsingExample()`만 호출합니다.
- gRPC 예제를 실행하려면 README.md 안내에 따라 `main()` 내부를 수동으로 편집해 `grpcSample()`을 호출하도록 바꿔야 합니다(미사용 import 오류 방지를 위해 `jsonexample` import를 주석 처리해야 함). 즉 **하나의 코드베이스 안에서 학습 단계별로 진입점을 수동 전환하는 방식**으로 설계되어 있습니다. - gRPC 예제를 실행하려면 README.md 안내에 따라 `main()` 내부를 수동으로 편집해 `grpcSample()`을 호출하도록 바꿔야 합니다(미사용 import 오류 방지를 위해 `jsonexample` import를 주석 처리해야 함). 즉 **하나의 코드베이스 안에서 학습 단계별로 진입점을 수동 전환하는 방식**으로 설계되어 있습니다.
- `grpcSample()` 함수는 `entity.ServerRun(port)`를 고루틴으로 백그라운드 실행한 뒤 1초 대기 후 `entity.ClientRun(...)`을 호출해, 같은 프로세스 안에서 서버·클라이언트가 통신하는 데모를 구성합니다. - `grpcSample()` 함수는 `entity.ServerRun(port)`를 고루틴으로 백그라운드 실행한 뒤 1초 대기 후 `entity.ClientRun(...)`을 호출해, 같은 프로세스 안에서 서버·클라이언트가 통신하는 데모를 구성합니다.
### 3.2 JSON 파싱 예제 — `examples/jsonexample/json_parser.go` ### 3.2 JSON 파싱 예제 — `lib/jsonexample/json_parser.go`
- `encoding/json` 표준 라이브러리를 이용해 (1) `map[string]interface{}` ↔ JSON 문자열 변환, (2) 구조체(`Person`) ↔ JSON 변환의 마샬링/언마샬링을 시연합니다. - `encoding/json` 표준 라이브러리를 이용해 (1) `map[string]interface{}` ↔ JSON 문자열 변환, (2) 구조체(`Person`) ↔ JSON 변환의 마샬링/언마샬링을 시연합니다.
- 외부 의존성 없이 표준 라이브러리만 사용하는 가장 단순한 예제로, 커리큘럼의 1단계 역할을 합니다. - 외부 의존성 없이 표준 라이브러리만 사용하는 가장 단순한 예제로, 커리큘럼의 1단계 역할을 합니다.
### 3.3 HTTP 서버 예제 — `examples/httpentity/` ### 3.3 HTTP 서버 예제 — `lib/httpentity/`
- `server.go``gin-gonic/gin`을 이용한 REST API 서버(정적 파일 서빙 + `/api/randomNumber`, `/api/randomPassword`, `/api/randomDate` 라우트) 초안이 **전체 주석 처리**된 상태로만 존재합니다. 즉 코드는 작성되어 있으나 활성화되지 않은 미완성/보류 상태입니다. - `server.go``gin-gonic/gin`을 이용한 REST API 서버(정적 파일 서빙 + `/api/randomNumber`, `/api/randomPassword`, `/api/randomDate` 라우트) 초안이 **전체 주석 처리**된 상태로만 존재합니다. 즉 코드는 작성되어 있으나 활성화되지 않은 미완성/보류 상태입니다.
- `client.go``package httpentity` 선언 한 줄만 있는 빈 파일입니다. - `client.go``package httpentity` 선언 한 줄만 있는 빈 파일입니다.
- `go.mod`에는 `gin-gonic/gin` 의존성이 여전히 선언되어 있어, 이 파트가 완전히 폐기된 것이 아니라 추후 재개를 염두에 둔 진행 중(work-in-progress) 상태로 보입니다. - `go.mod`에는 `gin-gonic/gin` 의존성이 여전히 선언되어 있어, 이 파트가 완전히 폐기된 것이 아니라 추후 재개를 염두에 둔 진행 중(work-in-progress) 상태로 보입니다.
@@ -72,21 +72,21 @@ grpccanary/
- `option go_package = "./protoapi/;protoapi"`로 지정되어 있어, `protoc` 컴파일 시 `protoapi/` 디렉토리에 Go 패키지 `protoapi`가 생성됩니다. - `option go_package = "./protoapi/;protoapi"`로 지정되어 있어, `protoc` 컴파일 시 `protoapi/` 디렉토리에 Go 패키지 `protoapi`가 생성됩니다.
- 이미 컴파일된 stub(`protoapi/protoapi.pb.go`, `protoapi/protoapi_grpc.pb.go`)이 저장소에 커밋되어 있어 `protoc` 재설치 없이 바로 빌드/실행이 가능합니다. - 이미 컴파일된 stub(`protoapi/protoapi.pb.go`, `protoapi/protoapi_grpc.pb.go`)이 저장소에 커밋되어 있어 `protoc` 재설치 없이 바로 빌드/실행이 가능합니다.
### 3.5 gRPC 서버 구현 — `examples/grpcentity/server.go` ### 3.5 gRPC 서버 구현 — `lib/grpcentity/server.go`
- `RandomServer` 구조체가 `protoapi.UnimplementedRandomServer`를 임베딩하여 3개 RPC 메서드(`GetDate`, `GetRandom`, `GetRandomPass`)를 구현합니다. - `RandomServer` 구조체가 `protoapi.UnimplementedRandomServer`를 임베딩하여 3개 RPC 메서드(`GetDate`, `GetRandom`, `GetRandomPass`)를 구현합니다.
- `getString(len int64)`은 ASCII 코드 `!`(33)부터 94개 범위 내에서 문자를 뽑아 임의 문자열(비밀번호)을 생성하는 헬퍼입니다. - `getString(len int64)`은 ASCII 코드 `!`(33)부터 94개 범위 내에서 문자를 뽑아 임의 문자열(비밀번호)을 생성하는 헬퍼입니다.
- `ServerRun(addr string)``grpc.NewServer()`로 서버를 만들고 `reflection.Register(server)`로 gRPC reflection(예: `grpcurl` 같은 외부 CLI 디버깅 도구 지원)을 활성화한 뒤 TCP 포트를 리슨합니다. - `ServerRun(addr string)``grpc.NewServer()`로 서버를 만들고 `reflection.Register(server)`로 gRPC reflection(예: `grpcurl` 같은 외부 CLI 디버깅 도구 지원)을 활성화한 뒤 TCP 포트를 리슨합니다.
- **주의(코드상 특이점)**: `ServerRun`은 인자로 받은 `addr`을 실제로 사용하지 않고 패키지 전역 변수 `port = ":8080"`으로 리슨합니다(`net.Listen("tcp", port)`). 따라서 현재 코드는 항상 `:8080`에서만 동작하며, 호출부에서 다른 포트를 넘겨도 무시됩니다. - **주의(코드상 특이점)**: `ServerRun`은 인자로 받은 `addr`을 실제로 사용하지 않고 패키지 전역 변수 `port = ":8080"`으로 리슨합니다(`net.Listen("tcp", port)`). 따라서 현재 코드는 항상 `:8080`에서만 동작하며, 호출부에서 다른 포트를 넘겨도 무시됩니다.
- `rand.Seed(r.GetSeed())`(패키지 전역 시드 설정, Go 1.20+에서는 deprecated)와 `rand.NewSource`를 혼용하고 있어 스레드 안전성이나 API 일관성 측면에서 다소 오래된 패턴을 보입니다(학습용 예제이므로 의도된 단순화로 판단됨). - `rand.Seed(r.GetSeed())`(패키지 전역 시드 설정, Go 1.20+에서는 deprecated)와 `rand.NewSource`를 혼용하고 있어 스레드 안전성이나 API 일관성 측면에서 다소 오래된 패턴을 보입니다(학습용 예제이므로 의도된 단순화로 판단됨).
### 3.6 gRPC 클라이언트 구현 — `examples/grpcentity/client.go` ### 3.6 gRPC 클라이언트 구현 — `lib/grpcentity/client.go`
- `grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))`로 평문(TLS 미적용) 채널을 생성합니다(로컬 테스트 목적). - `grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))`로 평문(TLS 미적용) 채널을 생성합니다(로컬 테스트 목적).
- `AskingDateTime`, `AskPass`, `AskRandom` 세 개의 래퍼 함수가 각각 대응하는 RPC를 호출합니다. - `AskingDateTime`, `AskPass`, `AskRandom` 세 개의 래퍼 함수가 각각 대응하는 RPC를 호출합니다.
- `ClientRun(addr)`이 실행 엔트리포인트로, 날짜/시간 조회 1회, 비밀번호 생성 1회, 서로 다른 파라미터로 난수 생성 2회를 순차 호출하며 결과를 표준 출력으로 출력합니다. - `ClientRun(addr)`이 실행 엔트리포인트로, 날짜/시간 조회 1회, 비밀번호 생성 1회, 서로 다른 파라미터로 난수 생성 2회를 순차 호출하며 결과를 표준 출력으로 출력합니다.
### 3.7 문서 자료 ### 3.7 문서 자료
- **`README.md`(루트)**: 이야기체(스타트업 개발자 '민우'의 사례)로 gRPC 도입 배경(REST/JSON의 한계, Protobuf 계약, HTTP/2)을 설명한 뒤, Go 버전 요구사항(1.25.4+), 의존성 설치, `main.go` 수동 편집 방법, 실행 명령(`go run ./examples`), 기대 출력 예시, 트러블슈팅(포트 충돌, 모듈 로드 오류)까지 안내하는 실행 가이드로 구성되어 있습니다. - **`README.md`(루트)**: 이야기체(스타트업 개발자 '민우'의 사례)로 gRPC 도입 배경(REST/JSON의 한계, Protobuf 계약, HTTP/2)을 설명한 뒤, Go 버전 요구사항(1.25.4+), 의존성 설치, `main.go` 수동 편집 방법, 실행 명령(`go run ./examples`), 기대 출력 예시, 트러블슈팅(포트 충돌, 모듈 로드 오류)까지 안내하는 실행 가이드로 구성되어 있습니다.
- **`examples/grpcentity/README.md`**: `.proto` 파일 문법 요소별 상세 해설, `protoc`/`protoc-gen-go`/`protoc-gen-go-grpc` 설치 및 컴파일 절차, 서버·클라이언트 코드의 구현 단계별 설명을 담은 심화 가이드입니다. - **`lib/grpcentity/README.md`**: `.proto` 파일 문법 요소별 상세 해설, `protoc`/`protoc-gen-go`/`protoc-gen-go-grpc` 설치 및 컴파일 절차, 서버·클라이언트 코드의 구현 단계별 설명을 담은 심화 가이드입니다.
- **`docs/Working with JSON/`**: JSON 관련 별도 학습 자료(영/한 병기)가 준비되어 있으나, 루트 README와 직접 링크되어 있지는 않습니다. - **`docs/Working with JSON/`**: JSON 관련 별도 학습 자료(영/한 병기)가 준비되어 있으나, 루트 README와 직접 링크되어 있지는 않습니다.
--- ---
@@ -101,7 +101,7 @@ grpccanary/
- **`.mam/`**: 위 인프라의 런타임 상태 저장소로, `jobs/`(작업 레지스트리 및 브리프 파일), `agent-sessions.yaml`/`.db`(세션 상태), `agent_homes/`(에이전트별 메모리), `delegate_job_logs/`(위임 감사 로그)를 포함합니다. 이번 작업 브리프(`.mam/jobs/702ea1d8/brief.md`) 역시 이 구조를 통해 전달되었습니다. - **`.mam/`**: 위 인프라의 런타임 상태 저장소로, `jobs/`(작업 레지스트리 및 브리프 파일), `agent-sessions.yaml`/`.db`(세션 상태), `agent_homes/`(에이전트별 메모리), `delegate_job_logs/`(위임 감사 로그)를 포함합니다. 이번 작업 브리프(`.mam/jobs/702ea1d8/brief.md`) 역시 이 구조를 통해 전달되었습니다.
- **`scripts/generate-env.sh`, `.env.example`**: 이 인프라가 사용하는 MQTT 브로커 접속 정보, 경로 설정 등을 `.env`로 초기화하는 헬퍼입니다. 실제 비밀 값은 `.env`(git-ignored)에만 두고 `.env.example`에는 플레이스홀더만 커밋하도록 설계되어 있습니다. - **`scripts/generate-env.sh`, `.env.example`**: 이 인프라가 사용하는 MQTT 브로커 접속 정보, 경로 설정 등을 `.env`로 초기화하는 헬퍼입니다. 실제 비밀 값은 `.env`(git-ignored)에만 두고 `.env.example`에는 플레이스홀더만 커밋하도록 설계되어 있습니다.
**참고**: 새로 추가된 `.gitignore``.agents/`, `.mam/`, `.env`, `AGENTS.md` 등을 앞으로 git 추적 대상에서 제외하도록 지정하고 있습니다. 다만 `git log`를 보면 `AGENTS.md`, `examples/grpcentity/README.md` 등은 과거 커밋(`init 251117`, `update 260609`, 이후 docs 커밋들)에서 이미 저장소에 커밋되어 있으므로, 이번 `.gitignore` 추가는 향후 신규/변경 파일이 실수로 다시 커밋되는 것을 막기 위한 조치로 보입니다(기존에 추적 중인 파일을 소급 제외하지는 않음). **참고**: 새로 추가된 `.gitignore``.agents/`, `.mam/`, `.env`, `AGENTS.md` 등을 앞으로 git 추적 대상에서 제외하도록 지정하고 있습니다. 다만 `git log`를 보면 `AGENTS.md`, `lib/grpcentity/README.md` 등은 과거 커밋(`init 251117`, `update 260609`, 이후 docs 커밋들)에서 이미 저장소에 커밋되어 있으므로, 이번 `.gitignore` 추가는 향후 신규/변경 파일이 실수로 다시 커밋되는 것을 막기 위한 조치로 보입니다(기존에 추적 중인 파일을 소급 제외하지는 않음).
--- ---
@@ -117,16 +117,16 @@ go mod download # 의존성 다운로드
```bash ```bash
go run ./examples go run ./examples
``` ```
`examples/main.go`가 기본적으로 `jsonexample.JsonParsingExample()`만 호출하므로, 별도 수정 없이 바로 JSON 마샬링/언마샬링 결과가 콘솔에 출력됩니다. `lib/main.go`가 기본적으로 `jsonexample.JsonParsingExample()`만 호출하므로, 별도 수정 없이 바로 JSON 마샬링/언마샬링 결과가 콘솔에 출력됩니다.
### 5.3 gRPC 예제 실행 (수동 편집 필요) ### 5.3 gRPC 예제 실행 (수동 편집 필요)
1. `examples/main.go`를 열어 `jsonexample` import를 주석 처리하고 `main()` 안에서 `grpcSample()`을 호출하도록 변경. 1. `lib/main.go`를 열어 `jsonexample` import를 주석 처리하고 `main()` 안에서 `grpcSample()`을 호출하도록 변경.
2. 저장 후 실행: 2. 저장 후 실행:
```bash ```bash
go run ./examples go run ./examples
``` ```
3. 내부적으로 `:8080` 포트에서 gRPC 서버(고루틴)가 기동되고, 1초 후 클라이언트가 `GetDate`, `GetRandomPass`, `GetRandom`(2회) 순으로 RPC를 호출하며 결과를 출력합니다. 3. 내부적으로 `:8080` 포트에서 gRPC 서버(고루틴)가 기동되고, 1초 후 클라이언트가 `GetDate`, `GetRandomPass`, `GetRandom`(2회) 순으로 RPC를 호출하며 결과를 출력합니다.
4. 포트 충돌 시(`bind: address already in use`) `examples/grpcentity/server.go`의 `port` 전역 변수를 변경해야 합니다(3.5절에서 언급했듯 `ServerRun`의 `addr` 인자는 무시되므로, 포트 변경은 이 전역 변수 수정으로만 가능). 4. 포트 충돌 시(`bind: address already in use`) `lib/grpcentity/server.go`의 `port` 전역 변수를 변경해야 합니다(3.5절에서 언급했듯 `ServerRun`의 `addr` 인자는 무시되므로, 포트 변경은 이 전역 변수 수정으로만 가능).
### 5.4 HTTP 예제 ### 5.4 HTTP 예제
`httpentity` 패키지는 서버 로직 전체가 주석 처리되어 있고 클라이언트 파일은 비어 있어, 현재 상태로는 실행 가능한 산출물이 없습니다. 향후 주석을 해제하고 `main.go`에 호출부를 추가해야 실습이 가능합니다. `httpentity` 패키지는 서버 로직 전체가 주석 처리되어 있고 클라이언트 파일은 비어 있어, 현재 상태로는 실행 가능한 산출물이 없습니다. 향후 주석을 해제하고 `main.go`에 호출부를 추가해야 실습이 가능합니다.
@@ -137,13 +137,13 @@ go run ./examples
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. \ protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. \
--go-grpc_opt=paths=source_relative protoapi.proto --go-grpc_opt=paths=source_relative protoapi.proto
``` ```
사전에 `protoc`, `protoc-gen-go`, `protoc-gen-go-grpc`가 설치되어 있어야 합니다(설치 방법은 `examples/grpcentity/README.md` 참고). 사전에 `protoc`, `protoc-gen-go`, `protoc-gen-go-grpc`가 설치되어 있어야 합니다(설치 방법은 `lib/grpcentity/README.md` 참고).
--- ---
## 6. 주요 관찰 사항 및 특이점 ## 6. 주요 관찰 사항 및 특이점
1. **단일 진입점, 수동 전환 방식**: `examples/main.go`가 유일한 실행 지점이며, 학습 단계(JSON/HTTP/gRPC)를 전환하려면 코드를 직접 편집해야 합니다. 각 예제를 독립적으로 실행할 수 있는 별도 커맨드나 플래그는 없습니다. 1. **단일 진입점, 수동 전환 방식**: `lib/main.go`가 유일한 실행 지점이며, 학습 단계(JSON/HTTP/gRPC)를 전환하려면 코드를 직접 편집해야 합니다. 각 예제를 독립적으로 실행할 수 있는 별도 커맨드나 플래그는 없습니다.
2. **`ServerRun(addr)`의 `addr` 인자 미사용**: gRPC 서버는 전달받은 인자 대신 패키지 전역 `port` 변수로 리슨하므로, 함수 시그니처와 실제 동작이 일치하지 않는 잠재적 혼동 요소입니다. 2. **`ServerRun(addr)`의 `addr` 인자 미사용**: gRPC 서버는 전달받은 인자 대신 패키지 전역 `port` 변수로 리슨하므로, 함수 시그니처와 실제 동작이 일치하지 않는 잠재적 혼동 요소입니다.
3. **HTTP 파트 미완성**: `gin` 의존성은 `go.mod`에 존재하지만 실제 코드는 비활성 상태로, 커리큘럼상 "다음 실습 예정" 단계로 보입니다. 3. **HTTP 파트 미완성**: `gin` 의존성은 `go.mod`에 존재하지만 실제 코드는 비활성 상태로, 커리큘럼상 "다음 실습 예정" 단계로 보입니다.
4. **테스트 부재**: 저장소 전체에 자동화된 테스트(`*_test.go`)가 없어, 코드 정상 동작 여부는 수동 실행(`go run`)과 콘솔 출력 확인에 의존합니다. `go build ./...`, `go vet ./...`는 통과합니다. 4. **테스트 부재**: 저장소 전체에 자동화된 테스트(`*_test.go`)가 없어, 코드 정상 동작 여부는 수동 실행(`go run`)과 콘솔 출력 확인에 의존합니다. `go build ./...`, `go vet ./...`는 통과합니다.