////
Search
📗

5. FastAPI 도메인 구조 이해하기 (Router, Schema, Service, Repository, Model)

최근들어 FastAPI에 대해 학습하고 있다. FastAPI를 학습하다 보면 다음과 같은 구조를 자주 보게 된다.
router schema service repository model
Plain Text
복사
대략적으로 이 구조를 왜 사용하는지 알고는 있지만 완벽하게 이해하지 못하고 있고 각 계층이 무슨 역할을 하는지 궁금하여 정리하고자 이 글을 작성하게 되었다.
많은 자료에서 다음과 같은 흐름으로 FastAPI 를 구현한다.
이 글에서 설명하는 구조는 FastAPI 뿐만 아니라 많은 백엔드 및 개발 과정에서 적용하는 구조인 것 같은데, 현재 FastAPI 학습 중에 있어 FastAPI 구조에 대한 언급이 많다.
Router -> Service -> Repository -> Model
Plain Text
복사
하지만 개발을 하다 보면 이 흐름이 항상 고정된 순서로만 동작하는 것은 아니다.
예를 들어, schema는 요청/응답 데이터 검증을 담당하고, model은 데이터베이스 구조를 정의하는 역할을 하기 때문에 단순히 하나의 직선적인 흐름으로 이해하기보다는 각 계층의 역할을 중심으로 이해하는 것이 도움이 될 것이다. 이 글에서는 위에서 언급한 것과 같이 FastAPI에서 자주 사용하는 다섯 계층의 구조를 정리해보고 각 계층이 어떤 역할을 하는지, 요청이 어떻게 처리되는지를 살펴보고자 한다.
보통 FastAPI 프로젝트를 도메인 단위로 구성하면 다음과 같은 구조를 가진다.
여기서 말하는 도메인은 서비스를 구성하는 큰 단위의 개체라고 가볍게 이해하자. 더해서, 처음에는 FastAPI 기초를 알기 위해 도메인 단위로도 구조를 잡지 않고 무작정 파일을 생성했는데, 도메인 단위로 프로젝트를 구성하고 구현하면 개발하는데에 용이하다.
app/ ├── domains/ │ └── user/ │ ├── router.py │ ├── schema.py │ ├── service.py │ ├── repository.py │ └── model.py
Plain Text
복사
각 파일의 역할은 다음과 같다.
구성 요소
역할
router
API 요청을 받는 진입점
schema
요청/응답 데이터 검증 및 구조 정의
service
비즈니스 로직 처리
repository
데이터베이스 접근 로직
model
데이터베이스 테이블 구조 정의
Router
RouterAPI 요청을 처리하는 진입점이다. 클라이언트에서 HTTP 요청이 들어오면 가장 먼저 Router가 이를 받는다.
router = APIRouter(prefix="/users") @router.post("/") def create_user(payload: UserCreate): return user_service.create_user(payload)
Python
복사
Router의 역할은 다음과 같다.
HTTP end-point 정의
요청 데이터 받기
schema를 통한 요청 검증
service 호출
응답 반환
즉, Router는 요청을 받아 적절한 로직으로 전달하는 역할을 한다.
Schema
Schema요청(Request)응답(Response)의 데이터 구조를 정의하는 역할을 한다. FastAPI에서는 Pydantic을 사용하여 schema를 정의한다.
class UserCreate(BaseModel): name: str email: EmailStr
Python
복사
Schema의 주요 역할은 다음과 같다.
요청 데이터 검증
데이터 타입 검사
직렬화(Serialization) / 역직렬화(Deserialization)
응답 데이터 구조 정의
예를 들어 클라이언트가 다음과 같은 데이터를 보내면
{ "name": "James", "email": "example@test.com" }
Python
복사
FastAPI는 이를 자동으로 다음과 같은 Python 객체로 변환한다.
UserCreate(name="James", email="example@test.com")
Python
복사
이 과정을 역직렬화(Deserialization) 라고 한다. 반대로, Python 객체를 JSON으로 변환하여 응답으로 보내는 과정은 직렬화(Serialization) 라고한다.
Service
Service비즈니스 로직을 담당하는 계층이다.
예를 들어 사용자 생성 로직을 생각해보자. 사용자를 생성할 때 단순히 데이터를 저장하는 것 전에 다음과 같응ㄴ 작업이 필요할 수 있다.
이메일 중복 검사
비밀번호 해싱
기본 권한 설정
로그 기록
기타 데이터 검증
이러한 것은 비즈니스 로직으로 Router가 아니라 Service에서 처리하는 것이 좋다.
비즈니스 로직은 서비스 성격에 따라 있을수도 있고 없을수도 있다.
아래는 Service 계층에서 사용하는 예시이다.
def create_user(db: Session, payload: UserCreate): existing_user = repository.get_user_by_email(db, payload.email) if existing_user: # 아래는 에러 처리 raise HTTPException(status_code=400, detail="이미 존재하는 이메일입니다.") return repository.create_user(db, payload)
Python
복사
Repository
Repository데이터베이스 접근을 담당하는 계층이다. 데이터 조회, 저장, 수정, 삭제와 같은 CRUD 작업을 수행한다.
def get_user_by_email(db: Session, email: str): return db.query(User).filter(User.email == email).first()
Python
복사
Repository의 역할은 다음과 같다.
데이터 조회
데이터 저장
데이터 수정
데이터 삭제
ORM 쿼리 관리
즉, Repository는 데이터베이스와 가장 가까운 계층이다.
Model
Model은 데이터베이스 테이블 구조를 정의하는 계층이다. FastAPI에서는 보통 SQLAlchemy ORM 모델을 사용한다.
class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True) name = Column(String) email = Column(String)
Python
복사
이 모델은 실제 데이터베이스의 users 테이블과 매핑된다.
Model은 보통 다음을 정의한다.
테이블 이름
컬럼 구조
데이터 타입
제약 조건
즉, Model데이터베이스 구조를 표현하는 객체라고 볼 수 있다.
지금까지 각 계층이 존재하는 이유어느 역할을 담당하는지에 대해 작성 해보았다. 작성 순서대로 사용자의 요청을 처리하는데 아래는 FastAPI에서 요청이 처리되는 일반적인 흐름을 나타냈다.
(위에서 아래로 가면서 요청을 처리함) Client | Router | Schema (요청 검증) | Service (비즈니스 로직) | Repository (DB 접근 및 데이터 입력) | Model (DB 테이블 매핑) | Database
Plain Text
복사
그리고 응답은 반대로 반환된다.
하지만 중요한 점은 이 흐름이 항상 고정된 순서로만 동작하는 것은 아니라는 것이다. 따라서 이 구조를 단순한 실행 순서라기보다 역할 중심 구조로 이해하는 것이 더 좋다.
지금까지 FastAPI에서 자주 사용하는 도메인 구조에 대해 알아보았는데 이러한 구조를 사용하는 이유는 크게 다음과 같다.
코드 책임 분리
유지보수 용이
테스트 용이
코드 재사용성 향상
사실상 프로젝트를 깔끔하게 하고 유지보수가 용이하게 하는 것이 주된 목적이다. 처음에는 복잡하게 느껴질 수 있지만, 프로젝트 규모가 커질수록 이러한 계층 분리가 큰 도움이 된다.