2025.11.03 - [개발 노트/Experience] - 사이드 프로젝트를 정리하며
사이드 프로젝트를 정리하며
개요올해 2월 1일부터 11월 1일까지 사이드 프로젝트를 진행했다. 본업에서 할 수 있는 시도에는 여러 제약이 있었고, 그 한계를 본업 밖에서 풀어보고자 하는 목적이 있었다. 본업에서는 쉽게 경
jakpentest.tistory.com
FastAPI로 품앗이를 만들면서 시도했던 부분 중 하나는, 파이썬에서도 객체 기반으로 코드를 잘게 나눠보자는 시도였다.
개인적인 생각이지만, 파이썬의 특성이 빠른 생산성과 결과 중심에 맞춰져 있어서인지, 구글링을 해보면 대부분 함수 형태 중심의 코드가 작성된 것을 볼 수 있었다. “왜 그럴까?”라는 의문이 생겼고, 그 시작에는 Spring MVC와의 비교가 있었다.
FastAPI 문서에도 권장하는 아키텍처 구조가 있지만, 그동안 책 등에서 익숙하게 봐온 용어들과는 다소 거리감이 있다.
.
├── app # "app" is a Python package
│ ├── __init__.py # this file makes "app" a "Python package"
│ ├── main.py # "main" module, e.g. import app.main
│ ├── dependencies.py # "dependencies" module, e.g. import app.dependencies
│ └── routers # "routers" is a "Python subpackage"
│ │ ├── __init__.py # makes "routers" a "Python subpackage"
│ │ ├── items.py # "items" submodule, e.g. import app.routers.items
│ │ └── users.py # "users" submodule, e.g. import app.routers.users
│ └── internal # "internal" is a "Python subpackage"
│ ├── __init__.py # makes "internal" a "Python subpackage"
│ └── admin.py # "admin" submodule, e.g. import app.internal.admin
위 구조는 FastAPI 공식 문서에서 참고한 FastAPI 아키텍처 가이드다.
익히 알고 있는 용어와 다소 다른 방식을 취한다. 예를들어 EndPoint를 정의하기 위한 네임스페이스를 routers라고 정의한다던지에 관한 부분이다.
내 경우엔 Controller, Service, Repository, UseCase, Event, Application 레이어 등으로 확장되는 용어들에 더 익숙하다 보니 그런 차이가 더 크게 느껴졌다. 지칭만 다른게 뭐가 문제가 되냐고 할 수도 있지만 스탠다드한 개념이 녹아들었는지 아닌지에 대한 부분은 꽤 크지 않을까 생각한다.
따라서 품앗이 프로젝트 구조를 구성할 때는 Domain을 중심에 둔 디렉토리 구조를 구상했다
┌─────────────────────────────────────┐
│ Application Layers │
│ (핵심 비즈니스 로직) │
│ │
│ ┌───────────────────────────────┐ │
│ │ API Endpoints (apis) │ │
│ │ • REST/HTTP 인터페이스 │ │
│ └───────────────▲──────────────┘ │
│ │ calls │
│ ┌───────────────┴──────────────┐ │
│ │ Use Cases (usecase) │ │
│ │ • 비즈니스 규칙 주도 │ │
│ └───────────────▲──────────────┘ │
│ │ uses │
│ ┌───────────────┴──────────────┐ │
│ │ Domain Models (domain) │ │
│ │ • Entities / Value Objects │ │
│ │ • Domain Logic │ │
│ └──────────────────────────────┘ │
│ │
│ ┌───────────────────────────────┐ │
│ │ Infrastructure (infrastructure)│ │
│ │ • Repository / Adapter Ports │ │
│ │ • 외부 시스템 연동 │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
이런 구조를 택한 이유는, Domain 중심 아키텍처를 파이썬으로 구현해보면 어떤 도구를 활용할 수 있고, 어떻게 표현할 수 있을지에 대한 호기심이 가장 컸기 때문이다. 생각보다 파이썬에서도 도메인 개념을 잘 녹여낼 수 있었고, 그 탐구 시간이 꽤 유익했다.
아래는 실제 구조를 캡처한 이미지다.

다만, SQLAlchemy를 활용하여 Domain과 외부 환경을 자연스럽게 연결하는 자료는 상대적으로 부족하다는 걸 체감하기도 했다.
그리고 이렇게 계층을 나눈 것만으로 끝이 아니었다. 각 계층에서 사용할 객체 형태와 역할을 따로 정의해 사용했다. 작성된 코드를 기반으로 ChatGPT에게 정리를 맡기니 다음과 같은 표를 뽑아줬는데, 코드 작성 의도와 꽤 잘 맞아떨어졌다.
각 계층에서 사용해야 할 개념들의 클래스 네이밍 규칙은 다음과 같다.
| apis | API 라우터 Controller | [Resource]Controller 또는 [Resource][Action]Controller | HTTP Endpoint 제공 계층. 요청을 유스케이스에 위임 | AccountController, AccountMentorReadController |
| API Schema | [Resource][Action]Schema.[Action]Request/Response | API 요청/응답 정의. Domain 모델과 분리 | AccountSearchByNickNameSchema.AccountSearchByNicknameRequest | |
| usecase | UseCase | [Resource]UseCase | 특정 시나리오를 수행하는 애플리케이션 서비스 계층. Domain과 Infra 조합 | AccountUseCase, SocialLoginUseCase |
| UseCase DTO | [Purpose]Dto | 유스케이스 진행에 필요한 데이터 전달 | LoginTokenPayload, StatisticsDto | |
| domain | Entity | [Resource]Entity | 핵심 비즈니스 규칙 표현. 상태 보유 | AccountEntity |
| Enum 정의 | [Resource]Type, [Specific]Type | 속성의 유효 범위 정의 | AccountType, SocialType | |
| 인터페이스 | I[Resource][Type] | DIP 준수. 구현체(Infrastructure)와 분리 | IAccountService, IAccountReader | |
| Event | [Resource][Action]Event | 비즈니스 이벤트 | KakaoLoginEvent, PostRegisteredEvent | |
| Event Payload | [EventName]Payload | 이벤트 전달 데이터 | AccountLoginEventPayload | |
| Info DTO | [Resource]Info / [Resource][Detail]Info | Domain 결과물의 표현 전용 정보 | AccountInfo, QnaDetailInfo | |
| 도메인 서비스 | [Resource]Service | 핵심 비즈니스 로직 처리. Infra 의존 없음 | AccountService, PostService | |
| Command | [Resource]Command | 상태 변경 명령 객체 | PostCommand | |
| infrastructure | Repository 구현체 | [Resource]WriterImpl, [Resource]ReaderImpl | 실제 기술 의존 구현(SQLAlchemy 등) | AccountWriterImpl, AccountReaderImpl |
| 추상 Repository | Abstract[Type] | 공통 규약 정의 | AbstractRepository |
이렇게 패턴을 정의하면 코드 작성 방식이 자연스럽게 일관성을 가지게 된다는 점이 가장 큰 장점이다. 반면, 단점도 있었다. 혼자 개발하기에는 꽤 많은 작업량이 필요했다. 단순 조회 기능 하나에도 여러 클래스가 따라오는 구조가 되어버렸기 때문이다. 그래도 장기적인 유지보수 관점에서는 어느 정도 시간 투자가 필요하지 않을까라는 생각이었다. 그 당시만 해도 이 프로젝트를 긴 호흡으로 보고 있었으니 말이다.
또한 하나의 패키지 안에 있어야될 코드 위치가 계층별로 이에 맞춘 구조가 잡혀져 버린다는 점도 단점이었다.(필요한 것은 한 곳에 모여있어야 자연스럽지 않을까?)
자료도 적고, 더 다듬어야 할 부분도 많지만, 결국 중요한 건 나만의 기준을 세워가는 과정이라고 생각한다. 앞으로도 Python 기반의 도메인 아키텍처를 조금씩 발전시켜가며, 더 나은 설계와 유지보수성을 고민해보고자 한다.
'개발 노트 > 개발 삽질' 카테고리의 다른 글
| [품앗이] JWK로 OAuth2 토큰 검증하기 (0) | 2025.11.03 |
|---|---|
| Python으로 푸쉬 알람 전송과 시행착오 (1) | 2025.05.18 |
| Commit Message 자동화하기, 근데 AI를 곁들인 (0) | 2025.04.24 |
| makefile에서 .env 사용 시 특수문자 처리하기 (0) | 2025.02.12 |
| 토스 채용공고는 어떻게 크롤링 할 수 있을까? (0) | 2025.02.12 |