5. FastAPI 도메인 구조 이해하기 (Router, Schema, Service, Repository, Model) 에서 FastAPI에서 자주 사용하는 구조와 구조 내에 있는 각 계층에 대해 설명했었다.
계층 중에는 Model과 Schema 계층이 존재하는데 처음에는 두 계층이 굉장히 비슷해보였고 차이점을 알지 못했었다. 예를 들어 Model에 있는 User과 Schema를 보면 구조도 거의 비슷하다.
이와 같이 구조가 비슷하기 때문에 하나로 통합하는 것이 불필요한 코드도 줄일 수 있을 것으로 생각했다. 하지만 실제 개발에서는 Schema와 Model을 분리하는 것이 매우 중요하다는 것을 알게 되었다.
이 글에서는 다음 내용을 정리해보고자 한다.
•
Model이 무엇인지
•
Schema가 무엇인지
•
왜 두 계층을 분리해야 하는지
Model
Model은 데이터베이스 테이블 구조를 정의하는 클래스이다. FastAPI에서는 보통 SQLAlchemy ORM 모델을 사용한다. 예를 들어 사용자 테이블을 정의하면 다음과 같다.
from sqlalchemy import Column, Integer, String
from app.db.base import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
email = Column(String)
Python
복사
이 모델은 실제 데이터베이스의 users 테이블과 매핑되고 역할은 다음과 같다.
•
데이터베이스 테이블 구조 정의
•
컬럼 타입 정의
•
테이블 관계 정의
•
ORM 객체 생성
쉽게 말하면 Model은 데이터베이스와 연결된 객체이다.
Schema
Schema는 API 요청(Request)과 응답(Response)의 데이터 구조를 정의하는 클래스이다. FastAPI에서는 Pydantic을 사용하여 schema를 작성한다. 예를 들어 사용자 생성 요청을 위한 schema는 다음과 같다.
from pydantic import BaseModel, EmailStr
class UserCreate(BaseModel):
name: str
email: EmailStr
Python
복사
응답용 schema는 다음과 같이 작성할 수 있다.
class UserResponse(BaseModel):
id: int
name: str
email: EmailStr
class Config:
from_attributes = True
Python
복사
Schema의 주요 역할은 다음과 같다.
•
요청 데이터 검증
•
데이터 타입 검사
•
직렬화 / 역직렬화
•
응답 데이터 구조 정의
다시 말해 Schema는 API에서 사용하는 데이터 구조라고 볼 수 있다.
Model과 Schema의 차이
구분 | Model | Schema |
목적 | 데이터베이스 구조 정의 | API 데이터 구조 정의 |
사용 라이브러리 | SQLAlchemy | Pydantic |
사용 위치 | Repository / DB | Router / API |
역할 | DB 테이블 매핑 | 요청 / 응답 검증 |
Schema와 Model을 분리해야 하는 이유
이 글을 시작할 때, Schema와 Model 계층의 내용이 크게 다르지 않아서 분리하는것보다 오히려 통합하는 것이 좋지 않을까 고민했다고 했다. 하지만 Model과 Schema 계층의 역할이 다르고 차이점도 명확하기 때문에 아래와 같은 이유로 분리하는 것이 좋다.
1.
보안 문제
데이터베이스는 외부로 노출되면 안 되는 정보가 포함될 수 있다.
예를 들어 사용자 테이블이 다음과 같다고 가정해 보자.
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String)
password = Column(String)
Python
복사
만약 Model을 그대로 API 응답으로 반환하면 다음과 같은 문제가 생긴다.
{
"id": 1,
"email": "test@test.com",
"password": "hashed_password"
}
JSON
복사
비밀번호가 그대로 노출될 수 있다.
하지만 Schema를 활용해 응답 형태를 지정하면 필요한 필드만 사용하여 응답할 수 있다.
class UserResponse(BaseModel):
id: int
email: str
Python
복사
{
"id": 1,
"email": "test@test.com"
}
JSON
복사
2.
요청 데이터 검증
Schema는 입력 데이터 검증을 담당한다. 예를 들어 이메일 형식을 검증할 수 있다.
class UserCreate(BaseModel):
name: str
email: EmailStr
Python
복사
위와 같이 유저 생성 요청에 대한 형식이 정해져 있을 때 사용자가 다음과 같은 요청을 보낸다고 하자.
{
"name": "James",
"email": "not-email"
}
JSON
복사
이럴 경우 email 형식이 맞지 않기 때문에 FastAPI는 자동으로 에러를 발생시킨다. 즉, Schema는 API 입력 데이터를 안전하게 만드는 역할도 수행한다.
3.
API와 DB 구조 분리
API에서 사용하는 데이터 구조와 DB 구조는 항상 같지 않다. 예를 들어 데이터베이스에는 다음과 같은 필드가 있을 수 있다.
id
email
password
created_at
updated_at
Plain Text
복사
하지만 API 응답은 이렇게 간단할 수도 있다.
id
email
Plain Text
복사
이 것은 보안 문제와도 연결 된 내용인데, 이처럼 API 구조와 DB 구조를 독립적으로 관리하기 위해 Schema와 Model을 분리한다.
4.
목적에 맞는 다양한 데이터 구조 생성 가능
예를 들어 사용자 도메인에서는 다음과 같은 schema가 필요할 수 있다.
UserCreate
UserUpdate
UserResponse
UserListResponse
Plain Text
복사
class UserCreate(BaseModel):
name: str
email: EmailStr
class UserUpdate(BaseModel):
name: str | None = None
email: EmailStr | None = None
Python
복사
이처럼 API 목적에 맞는 다양한 데이터 구조를 만들 수 있다.
정리
FastAPI에서 Schema와 Model 계층을 분리하는 이유에 대해 알아보았다. 그 큰 이유는 다음과 같다.
1.
데이터 보안
2.
요청 데이터 검증
3.
API 구조와 DB 구조 분리
4.
목적에 맞는 다양한 데이터 구조 생성 가능
Schema는 요청 데이터 검증과 응답 데이터 변환을 담당하며, Model과의 구조가 비슷해 보일 수 있지만 프로젝트 규모가 커질수록 Schema와 Model의 분리는 매우 중요해진다. FastAPI에서 유지보수하기 좋은 구조를 만들기 위해서는 이 두 개의 역할을 명확히 구분하는 것이 좋다.