# Go의 Layered 설계 방식

> Clean Markdown view of GeekNews topic #20496. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=20496](https://news.hada.io/topic?id=20496)
- GeekNews Markdown: [https://news.hada.io/topic/20496.md](https://news.hada.io/topic/20496.md)
- Type: GN+
- Author: [xguru](https://news.hada.io/@xguru)
- Published: 2025-04-24T09:32:03+09:00
- Updated: 2025-04-24T09:32:03+09:00
- Original source: [jerf.org](https://jerf.org/iri/post/2025/go_layered_design/)
- Points: 27
- Comments: 4

## Summary

Go 언어는 **패키지 간 순환 참조를 금지**하여 자연스럽게 **계층적 설계**를 유도합니다. 이 구조는 **유향 비순환 그래프(DAG)** 를 형성하며, **언어 레벨에서 강제되는 설계 규칙**입니다. 순환 참조가 발생할 경우, **기능 이동, 공용 기능 분리, 인터페이스 도입** 등의 **리팩터링 전략**을 통해 해결할 수 있습니다. 이러한 설계 방식은 **테스트, 유지보수, 마이크로서비스 분리**에 유리하며, **의도하지 않은 전체 의존성 연결**을 방지합니다.

## Topic Body

- Go 언어는 **패키지 간 순환 참조를 엄격히 금지**하기 때문에, 이는 자연스럽게 **계층적 설계(layered design)** 를 유도함  
- 이 글은 Go 프로젝트를 **의무적으로 갖게 되는 계층 구조**를 설명하고, 그 위에 별도의 아키텍처를 강제하지 않아도 충분히 유효하다고 주장함  
- 순환 의존이 발생할 경우, 이를 해결하기 위한 **구체적이고 실용적인 리팩터링 전략**을 단계별로 제시함  
- 각 패키지는 독립적으로 의미 있는 기능 단위를 가지도록 설계되어, **테스트, 유지보수, 마이크로서비스 분리**에도 유리함  
- 결과적으로 이 방식은 실제 코드 설계에서 흔히 발생하는 **"바나나를 원했는데 정글을 들고 온다"는 문제**를 방지함  
---  
### Go에서의 레이어드 설계 접근법  
  
#### 기본 원칙  
  
- Go는 **패키지 간 순환 참조 금지**  
- 모든 Go 프로그램의 import 관계는 **유향 비순환 그래프(DAG)** 를 구성해야 함  
- 이 구조는 선택이 아닌 **언어 레벨에서 강제되는 설계 규칙**  
  
#### 패키지 레이어링의 자동 형성  
  
- 외부 패키지를 제외한 프로젝트 내부 패키지들은 **참조 깊이에 따라 자동으로 레이어링 가능**  
- 아래 그림처럼 최하단에는 **metrics, logging, 공용 자료구조 등 핵심 유틸리티 패키지**가 위치  
- 이후 상위 패키지들이 점점 **기능을 조합하면서 위로 쌓이는 구조**를 형성  
  
#### 이 설계 방식의 특성  
  
- 레이어는 **계층적 추상화가 아닌 참조 방향성에 기반**  
- 하나의 패키지는 **다수 하위 레벨 패키지를 참조 가능**  
- MVC, 헥사고날 아키텍처 등 기존 설계 방식도 이 구조 위에 **"적용"** 가능함  
  → 단, Go의 구조적 제약을 반드시 고려해야 함  
  
### 순환 참조 해결 전략  
  
순환 참조 발생 시 아래 순서대로 리팩터링을 시도:  
  
##### 1. 기능 이동  
  
- **가장 추천되는 방식**  
- 순환을 유발하는 기능을 **정확히 분석**해, 논리적으로 **적절한 위치로 옮김**  
- 자주 쓰이진 않지만 **개념적 명확성을 가장 많이 향상시킴**  
  
##### 2. 공용 기능을 별도 패키지로 분리  
  
- 양쪽에서 공통으로 쓰는 타입이나 함수(`Username` 등)를 **제3 패키지로 이동**  
- **패키지가 작아 보일지라도 과감히 분리**  
  → 시간이 지나며 해당 패키지가 커질 가능성이 높음  
  
##### 3. 상위 조합 패키지 생성  
  
- 순환하는 두 패키지를 **조합하는 제3 패키지**를 생성  
- 예: `Category`, `BlogPost` 양방향 의존을 상위 패키지로 분리  
  → 하위 패키지는 dumb struct로 유지, 실제 기능은 상위 패키지에서 조합  
  
##### 4. 인터페이스 도입  
  
- 구조체나 함수가 필요한 메서드만 가진 **인터페이스로 의존성 치환**  
- 불필요한 의존성 제거 및 **테스트 편의성** 확보  
- 단, 지나치게 사용하면 오히려 설계가 복잡해질 수 있음  
  
##### 5. 복사(Copy)  
  
- 의존 대상이 매우 작을 경우, **간단히 복사해서 사용**  
- DRY 위반처럼 보일 수 있지만 **실제로는 설계 명확화에 도움**되는 경우 많음  
  
##### 6. 하나의 패키지로 합치기  
  
- 위 방법이 모두 불가능하면 **두 패키지를 병합**  
- 너무 큰 패키지가 되지 않는다면 수용 가능  
  → 단, 무조건적인 병합은 지양하고 신중히 결정  
  
### 이 설계 방식의 실용적 장점  
  
- 각 패키지는 **스스로 의미 있는 기능 단위**를 가지며 독립 테스트 가능  
- 패키지 내 참조가 제한되어, **전체 코드 이해 없이도 개별 패키지 이해가 가능**  
- 의도하지 않은 전체 의존성 연결(=정글 문제)을 피하고, 필요한 것만 사용하는 코드 작성 유도  
- **마이크로서비스 분리 시에도 손쉽게 추출 가능**  
  → 대부분의 종속성이 명확히 정의되어 있음  
  
### 결론  
  
- Go의 패키지 설계 제약은 귀찮은 제약이 아닌 **좋은 설계 유도 장치**  
- 특별한 아키텍처 없이도 **패키지 간 참조 구조만으로도 견고한 설계 구현 가능**  
- 순환 참조에 대한 **정교한 분석과 리팩터링 전략**은 Go뿐만 아니라 타 언어에도 유효

## Comments



### Comment 37775

- Author: bus710
- Created: 2025-04-25T06:19:27+09:00
- Points: 1

처음 막 짜서 돌아갈 땐 재밌지만  
테스트 넣기 시작하면서  
그 때 내가 왜 그랬을까 생각 해보게 됩니다.

### Comment 37753

- Author: bungker
- Created: 2025-04-24T13:09:28+09:00
- Points: 1

"바나나를 원했는데 정글을 들고옴" 이란 말 너무 재밌네요.

### Comment 37737

- Author: iwanhae
- Created: 2025-04-24T12:26:36+09:00
- Points: 1

스프링 개발할때 가장 힘든것들 중 하나가 의존성 순환이었던거 같아요..  
무한하게 서로 초기화하면서 메모리 누수로 뻗어버리는 그 답답함이란...

### Comment 37702

- Author: neo
- Created: 2025-04-24T09:32:05+09:00
- Points: 1

###### [Hacker News 의견](https://news.ycombinator.com/item?id=43740992) 
- 순환 종속성을 허용하지 않는 것은 대규모 프로그램을 구축할 때 훌륭한 설계 선택임
  - 이는 관심사를 적절히 분리하도록 강제함
  - 순환 종속성이 발생하면 설계에 문제가 있는 것이며, 기사는 이를 해결하는 방법을 잘 설명함
  - 가끔 다른 패키지가 재정의하는 함수 포인터를 사용하여 순환 종속성을 해결함
  - Go 컴파일러가 순환 종속성을 만들 때 더 유용한 출력을 제공했으면 좋겠음
  - 현재는 루프에 관련된 모든 패키지 목록을 제공하는데, 이는 꽤 길 수 있으며 일반적으로 문제를 일으킨 것은 마지막으로 변경한 것임

- 훌륭한 블로그 게시물임
  - 이 웹사이트에는 놀라운 게시물이 많으며, 함수형 프로그래밍에 대해 배우는 것을 좋아한다면 확인해보길 권장함
  - [링크](https://jerf.org/iri/blogbooks/functional-programming-lessons-in-imperative-code/)

- "세 번째 패키지로 이동" 조언과 관련된 보너스 기술
  - 많은 모델 구조(SQL, Protobuf, GraphQL 등)를 생성하면 생성된 계층 간의 명확한 방향성을 설정할 수 있음
  - 모든 생성된 코드를 애플리케이션 코드에 "기본 패키지"로 제공하여 모든 것을 함께 구성함
  - 이 기술 도입 전에는 "모델이 모델을 순환적으로 가져오는" 문제가 있었지만, 구조적 추가 계층 도입으로 완전히 사라짐

- Yourdon 구조적 방법에 관한 책을 읽고 있는 것 같음

- 패키지가 서로 순환 참조할 수 없음
  - 실제로 Go에서는 go:linkname을 사용하여 가능함

- 랜덤라이저의 구체 개념을 떠올리게 함

- Golang의 재미있는 특징은 패키지 수준에서 순환 종속성을 가질 수 없지만, go.mod에서는 가질 수 있음
  - 요약하자면, 그것도 하지 말아야 함

- Jerf가 패키지를 어떻게 생각하고 순환 종속성을 어떻게 처리하는지에 대한 멋진 설명임
