slowlp
← 블로그
방법 2026.06.22 · 4분 읽기

같은 코드를 두 번 쓰지 않으려고

도메인 셋째쯤 반복되는 코드를 공통 모듈·Base 클래스·명명 규칙으로 모으기

방법

같은 코드를 두 번 쓰지 않으려고: 공통 모듈·Base 클래스·명명 규칙

도메인 셋째쯤 반복되는 코드를 common으로 올린다

도메인을 두 개쯤 만들고 나면 묘한 기시감이 든다. 방금 친 코드, 옆 도메인에서도 거의 똑같이 친 것 같은데. 모델마다 id와 생성·수정 시각 필드가 붙고, 리포지토리마다 단건 조회·목록 조회·생성·삭제가 반복되고, 스키마마다 같은 설정이 들어간다. 복붙으로 넘어갈 수는 있다. 그러다 셋째 도메인쯤에서 깨닫는다. 이거, 한 군데로 모았어야 했다.

그래서 common이라는 자리를 따로 둔다. 특정 비즈니스 로직에 묶이지 않고, 도메인 어디서나 공유되는 기반 코드를 모아두는 곳이다. 역할대로 나눈다.

  • base/ — 모델·스키마·리포지토리의 기반 클래스
  • config/ — 환경 변수, 로깅 설정
  • exception/ — 공통 예외
  • schema/ — 도메인에 안 묶이는 공통 데이터 구조
  • util/ — 쿼리 빌더, 날짜·시간 같은 독립 유틸

Base 클래스: 반복되는 걸 위로 올린다

핵심은 base/다. 모든 테이블 모델이 상속하는 BaseSqlModel 하나를 정의하면, id·inserted_at·updated_at 같은 공통 필드와 생성·수정 메서드를 모든 모델이 공짜로 갖는다. 새 도메인 모델은 자기만의 필드만 추가하면 된다. 리포지토리도 마찬가지다. BaseRepository에 단건 조회, 조건 조회, 목록, 페이지네이션, 생성, 삭제를 한 번 짜두면, 각 도메인 리포지토리는 그걸 상속받고 자기 도메인 특유의 쿼리만 더한다. 스키마도 BaseSchema·BaseCreateSchema·BaseUpdateSchema로 공통 설정과 모델 변환 로직을 끌어올린다.

상속 관계로 정리하면 이렇게 된다.

# common/base: 한 번만 짠다
class BaseSqlModel(SQLModel):
    id: int | None = Field(default=None, primary_key=True)
    # inserted_at, updated_at, 생성·수정 메서드 ...

# 각 도메인: 자기 것만 더한다
class UserAccount(BaseSqlModel, table=True):
    name: str
    email: str

규칙은 단순하다. 두 도메인에서 똑같이 반복되면 base로 올린다. 한 도메인에만 있으면 그냥 둔다.

설정은 코드 밖으로

같은 이유로 설정도 한 곳에 모은다. 다만 모으는 방식이 중요한데, 값을 코드에 박지 않고 환경 변수로 받는다. DB 주소, 외부 서비스 URL 같은 건 개발·테스트·프로덕션마다 다르다. 코드에 박아두면 환경 바꿀 때마다 코드를 고쳐야 한다.

그래서 설정 클래스 하나를 두고 환경 변수를 타입과 함께 선언한다.

from pydantic_settings import BaseSettings, SettingsConfigDict

class Settings(BaseSettings):
    DATABASE_URL: str
    APP_ENV: str = "dev"

    model_config = SettingsConfigDict(env_file=".env", extra="ignore")

settings = Settings()

앱이 뜰 때 환경 변수를 읽어 settings 객체를 만들고, 다른 코드는 이걸 임포트해서 쓴다. 좋은 점이 하나 더 있다. 필수 변수가 빠져 있으면 앱이 아예 안 뜨고 바로 에러를 낸다. 설정 누락을 런타임 한참 뒤가 아니라 시작하는 순간 잡아준다.

여기서 멈춘다. Docker로 어떻게 주입하고 Celery 워커는 어떻게 받는지는 환경마다 다르다. 원칙만 가져가면 된다. 값은 코드 밖, 환경에.

이름이 일관되면 코드가 읽힌다

마지막은 이름이다. 사소해 보이지만 가장 매일 부딪히는 부분이다. 기준은 PEP 8을 따르고, 거기에 자리별 규칙을 정해둔다.

  • 모듈(파일) — snake_case (account_router.py)
  • 클래스 — PascalCase (UserAccountRepository, NotFoundException)
  • 함수·메서드·변수 — snake_case (create_account, account_id)
  • 상수 — ALL_CAPS (MAX_RETRIES)

규칙 자체보다 일관성이 본질이다. 이름만 보고 “이건 클래스, 저건 상수, 이건 함수”가 바로 읽히면, 코드를 읽을 때 머리가 한 박자 덜 쓰인다. 임포트를 표준 → 서드파티 → 내부 순서로 묶고 절대 경로로 쓰는 것도 같은 이야기다. 파일을 열었을 때 어디서 온 코드인지 한눈에 보인다.

타입 힌트도 빼놓을 수 없다. 인자·반환·변수에 타입을 명시하면 IDE 자동완성이 살아나고, 짜는 중에 타입 오류를 잡는다. FastAPI·Pydantic·SQLModel은 타입 힌트를 적극적으로 활용하니 이 스택에선 거의 필수다.

공통 모듈로 중복을 없애고, 설정을 코드 밖으로 빼고, 이름을 일관되게 맞춘다. 세 가지 다 “나중의 나”를 위한 일이다. 도메인이 세 개를 넘어가는 순간부터 체감이 온다.

관련 프로젝트
주식앱 (마이폴리오) LAB
개별 종목 분석 없이 포트폴리오 단위로 투자하는 앱 — KIS 계좌 연동 실주문까지, 본인 계좌 전용으로 운영 중.
보기 →
댓글