내가 경험하고 고민했던 프로젝트 구조에 대해서 정리한다. 어떤 고민들이 어떻게 흘러갔고 변해갔는지를 남기기 위함이다. 이는 오로지 내 경험과 생각/고민에 의한 것이므로 다른 팀 혹은 프로젝트에서는 다를 수 있다.

들어가기 앞서

신규 프로젝트를 시작할 때 프로젝트 구조에 대해서 고민을 한 적이 있을까?

경험이 부족한 시기에서 나는 별다른 생각이 없었다. 이미 팀에서 정해진 구조가 있고, 여기에 맞게 코드만 넣으면 되기 때문이었다. 그래서 새로운 레파지토리repository를 만들더라도 관습대로 동일한 구조로 만들고 코드를 작성해왔다.

신규 프로젝트 또는 레파지토리의 경우 유지 보수할 것 뿐만 아니라 버그, 코드 악취Code small도, 중복 코드가 없다. 그래서 이 시점을 아래 그림과 같이 생선성Productivity을 100으로 본다. 오로지 생산만 하는 단계이니 당연히 그럴 수 밖에 없다.

Productivity vs. time

프로젝트의 생산성은 떨어지는 건 당연한 것이겠지만, 프로젝트 구조 관점에서 어떻게 하면 보다 높은 생산성을 유지하면서 이어갈 수 있을까?

이러한 고민을 프로젝트 진행 과정을 빗대어 설명/정리해본다. 소스코드는 'Spring Boot + Kotlin' 기반(코프링?)으로 설명한다.

Note
프로젝트 구조를 정할 때 필요한 사전 요소

나중에 다시 말하겠지만, 프로젝트 구조는 만들때 아래와 같은 고민이 필요하다고 생각한다.

  • 몇 명의 팀원(개발자)이 참여하는지

  • 어떤 도메인들을 아우르는지

  • 어느 규모의 서비스를 하는지

  • 어떤 어플리케이션들로 구성할 것인지

  • 언제 서비스 오픈을 목표로 하는지

아키텍처에 대해서 팀원들이 모두 같은 목표를 가지고 있다면 문제 없겠지만, 항상 팀은 다양한 경험, 관점을 가진 팀원들이 모여있으므로 프로젝트 초반에 목표를 통일화하기 어렵다.

처음부터 "'포트와 어댑터Ports and Adapters', '육각형 아키텍처Hexagonal Architecture', '클린 아키텍처Clean Architecture'로 가야한다!"고 말할 수도 있겠지만, 이는 서비스를 만들어가는데 있어서 어느정도는 오버엔지니어링이 될 수도, 오버커뮤니케이션을 불러 일으킬 수도, 공감하지 않을 수도 있다.

“Later equals never”

— Leblanc’s Law

“Always leave the campground cleaner than you found it.”

— The Boy Scout Rule

그래서 다양한 상황에서 아키텍처가 어떻게 흘러가는지를 알고 있고, 현재 상황에 맞게 만들지만 언제든지 변경하기 쉬운 구조를 가져야 한다고 생각한다. 여기서 가장 중요한 것은 "나중은 오지 않는다.", "보이스카웃 규칙"을 마음에 새기고 적재적소의 시기가 오면 미루지 않고 구조를 변경하는 것이다. 이러한 행위가 시간이 지나도 소프트웨어 생산성을 유지 시켜준다고 생각한다.

프로젝트 시작

자. 시작해볼까!?

요즘엔 스캐폴딩Scaffolding을 지원하는 프레임워크나 라이브러리가 많아 초기 서버 코드를 작성하는게 매우 쉽다.

프로젝트를 만들고, @Controller, @Service, @Repository 만 있으면 간단한 API 서버는 완성이다. 프로젝트도 나름 계층(layer)으로 구성해봤다.

repository
├── com.example.controller
│   └── PaymentController.kt
├── com.example.service
│   └── PaymentService.kt
└── com.example.repository
    └── PaymentRepository.kt

이러한 계층형 구조Layered Architecture는 전통적인 웹 어플리케이션 구조로 전공 과목 또는 튜토리얼, 각종 사례 등에서 우리에게 주입돼 왔다.

@startuml
hide empty methods
hide empty members
hide circle

class "   웹   " as web
class " 도메인 " as domain
class " 영속성 " as persistence

web -down-> domain
domain -down-> persistence
@enduml
Note
영속성

영속성Persistence는 '지속됨’을 의미하는 단어로, 데이터가 영구적으로 유지되는 데이터 특성을 의미한다.

잠깐. 위 글을 다시 보면 계층형 구조보다 패키지 구조(코드)를 먼저 작성했다. 왜 머리를 먼저 쓰지 않고 단순 CURD 서버만을 생각하며 키보드에 손을 먼저 갖다 댔을까?

코딩하는 내 모습이 멋있어서, 언릉 코딩하고 싶다.

대학생때도, 막 졸업했을 때도 코딩이 재미있었다. 빨리 무엇이든 코딩하고 싶었다. 그리고 그런 모습에 조금…​

이와 같은 흐름으로 프로젝트가 만들어지는 것은 좋지 않다. '어떤 프로젝트를 만드느냐’가 아니라 단순히 '데이터를 보여주는 역할’만 생각해서 만들어진 구조다. 이러한 접근법은 무슨 글을 쓸지 생각하고 펜을 드는 것이 아니라 낙서를 하는 김에 글을 쓰는 격인데, 이게 애초부터 말이 안된다.

programmer

개발자로써 살면서 느끼는건 개발은 실제 코딩보다 생각하는 시간이 더 많다는 걸 깨닫는다.
여기서 중요한 포인트는 개발은 코딩이 전부가 아니라는 것이다. 모든 코딩은 비지니스에 대한 설계와 사용자 혹은 데이터 흐름이 있다. TDDTest Driven Development도 마찬가지다. 테스트에 비지니스, 메서드의 입출력, 예외처리를 미리 작성하고 그에 맞는 코드를 작성해나가는 것이다. 테스트를 짜는 행위지만 이는 설계 혹은 실제 사용자 요청/응답 인터페이스를 작성하는 것과 다를 바 없다.

계층형 아키텍처

아무튼 여차저차 계층형 아키텍처로 만들어진 프로젝트가 있다. 서비스를 운영하면서 코드들은 각각의 패키지에 쌓여간다.

TODO: 작성중

처음엔 한 레포에서 컨트롤러 서비스 레파지토리 추가한다면 엔티티정도? 보통 모델??

repository
├── com.example.controller
│   └── PaymentController.kt
├── com.example.service
│   ├── PaymentService.kt
│   └── OtcService.kt
└── com.example.repository
    └── PaymentRepository.kt

계층으로 구성

기능으로 구성

서비스단위의 패키지

그러다 혼잡하게됨

모델

repository
├── com.example.controller
│   └── PaymentController.kt
├── com.example.service
│   ├── PaymentService.kt
│   ├── RefundService.kt
│   └── OtcService.kt
├── com.example.repository
│   └── PaymentRepository.kt
└── com.example.model
    └── Payment.kt

프로젝트 구조 진척도

다른 프로젝트 어드민을 만들고싶음 Api나 모듈을 분리함 코어가 생김 패키지는 그대로 동일한 패키지 구조가 그저 하나 더 생김

은탄환른 없음 다만 갈아치우기(리팩토링? 리스트럭처링)하기에 보다 나은 구조여여함. 지금 상황 인원 비지니스에서는 이게 최적일수 있음. 구조가 주는 것은 현재 구조를 해치지 않기 워함 새로운 사람도 그대로 구조를 ㅋ다르기 위함. 혼란을 주지 않아야함

컨트롤러가 서비스를 호츌함 근데 결국 이러한

클린아키텍처에서 유즈케이스는 무엇일짜? 서비스? 도메인? 일반적인 모델은 입출력모델 엔티티 모두 나누는것 같고.

코드가 커질수록 결국 서로간의 의존성을 끊기위한 작업을 하는듯 여기서 모델이나 서비스가 많이 생기니 이것을 빠르고 쉽게 이해할수 있는 정리기법?이 아닐까. 소리치는 구조라든지.

헥사고날 아키텍처

유스케이스

키워드: 서비스

웹 어댑터

키워드: 컨트롤러, spring-boot-web-mvc

책임

  1. HTTP 요청을 객체로 매핑

  2. 권한 검사

  3. 입력 유효성 검증

  4. 입력을 유스케이스 입력 모델로 매핑

  5. 유스케이스 호출

  6. 유스케이스 출력을 HTTP로 매핑

  7. HTTP 응답을 반환

영속성 어댑터

정리하기

'클린 아키텍처’는 패턴이 아니다. 현재 나의 프로젝트가 속한 시점이 어느 지점인지 파악하고 현재로써 클린 아키텍처인가를 생각해봐야 하지 않을까.

모노레포, 폴리레포 이러한 소스코드 구조가 나눠진 것도 결국 같은 이유지 않을까. 모듈로 분리하는게 결국 내가 코드를 패키지 단위로 분니할 것인지, 저장소 단위로 분리할 것인지 그 차이일뿐. 관심사의 분리는 동일하다.

우리팀의 고민

  • 기능으로 구성과 계층으로 구성의 혼재

  • 코드가 많아져서 계층 구성을 기능 구성으로 변경함 ⇒ 근본적인 해결 안됨. 코드의 양은 동일하게 방대함. 코드 찾기 어려움

  • 한번 더 분리하기 위함

    • 하지만 코드 접근성이 높았으면 함. 이는 이전 사용성에 대한 관습을 유지하기 위함이 아닐까?

      • 즉, multi repo 보다 monorepo의 장점에 중점을 둔 것

참고 자료

  • 만들면서 배우는 클린 아키텍처 - 톰 홈버그 지음, 박소은 옮김

  • 클린 아키텍처 - 로버트 마틴 지음

참고 링크