본문 바로가기

개발 노트/개발 삽질

[품앗이] FastAPI 아키텍처 어떻게 가져갈 것인가?

728x90
반응형

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 기반의 도메인 아키텍처를 조금씩 발전시켜가며, 더 나은 설계와 유지보수성을 고민해보고자 한다.

728x90
반응형