23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
소프트웨어 개발에서 결합도(Coupling)를 낮추고 응집도(Cohesion)를 높이는 것은 유지보수성과 확장성을 향상시키는 핵심 전략입니다.
특히 웹 애플리케이션 개발에서 모듈간의 결합도를 낮추면 코드의 재사용성과 테스트 용이성이 크게 향상됩니다.
이번 글에서는 FastAPI 프로젝트에서 파일들을 service 구현 그룹과 router 구현 그룹으로 그룹핑하여 결합도를 낮추는 방법을 살펴보겠습니다.
아키텍처 설계를 소홀히 하거나 결합도를 낮추는 전략을 고려하지 않으면 다음과 같은 문제들이 발생할 수 있습니다:
유지보수성 저하: 한 모듈의 변경이 다른 모듈에 연쇄적으로 영향을 미쳐 수정 범위가 넓어집니다.
예시: 데이터베이스 스키마 변경 시, 비즈니스 로직과 API 레이어 모두를 수정해야 하는 상황이 발생합니다.
확장성 제한: 새로운 기능을 추가하거나 변경하기 어려워집니다.
예시: 새로운 API 엔드포인트를 추가하려면 기존 모듈들을 대폭 수정해야 합니다.
재사용성 감소: 특정 기능을 다른 프로젝트나 모듈에서 재사용하기 어렵습니다.
예시: 다른 프로젝트에서 비슷한 기능이 필요해도 기존 코드를 활용할 수 없습니다.
협업 장애: 팀원 간 작업이 겹치거나 충돌하여 생산성이 떨어집니다.
예시: 여러 개발자가 동일한 파일을 수정하면서 충돌이 빈번하게 발생합니다.
테스트 어려움: 모듈간 의존성이 높아 개별적인 단위 테스트가 어렵습니다.
예시: 서비스 레이어를 테스트하려면 데이터베이스와 API 레이어까지 모두 설정해야 합니다.
이러한 문제를 해결하기 위해 결합도를 낮추는 아키텍처가 필요합니다.
FastAPI는 Python의 최신 기능과 표준을 활용하여 빠르고 효율적인 API를 구축할 수 있게 해주는 프레임워크입니다. 주요 특징은 다음과 같습니다:
고성능: 비동기 지원으로 높은 성능을 제공합니다.
타입 힌트 기반 개발: Python의 타입 힌트를 활용하여 코드의 가독성과 안정성을 높입니다.
자동 문서화: Swagger UI 및 ReDoc을 통한 자동 API 문서 생성을 지원합니다.
개발 생산성 향상: 최소한의 코드로 강력한 기능을 구현할 수 있습니다.
하지만 이러한 장점을 최대한 활용하려면, 모듈 간 결합도를 낮추고 응집도를 높이는 아키텍처 설계가 필요합니다.
파일명 | 설명 |
|---|---|
entity/*.py | 도메인 모델 엔티티 클래스 |
dto/*.py | 서비스 내부에서 사용하는 데이터 전송 객체(DTO) |
repository/*.py | 데이터베이스와의 CRUD 작업을 담당 |
service/*.py | 비즈니스 로직을 구현 |
파일명 | 설명 |
|---|---|
dto/request/*.py | 클라이언트 요청을 위한 DTO |
dto/response/*.py | 클라이언트 응답을 위한 DTO |
usecase/*.py | 특정 유스케이스에 대한 애플리케이션 로직을 처리 |
router/*.py | API 엔드포인트를 정의 |
main/
├── domain/
│ └── example/
│ ├── entity/
│ │ └── example.py
│ ├── dto/
│ │ └── example_dto.py
│ ├── repository/
│ │ └── example_repository.py
│ └── service/
│ └── example_service.py
├── app/
│ └── example/
│ ├── dto/
│ │ ├── request/
│ │ │ └── example_request_dto.py
│ │ └── response/
│ │ └── example_response_dto.py
│ └── usecase/
│ └── example_usecase.py
└── api/
└── example/
└── router/
└── example_router.pymain/
├── core/
│ ├── config.py
│ ├── datastore.py
│ ├── enum.py
│ ├── helper.py
│ ├── util.py
│ └── dto.py
├── domain/
│ └── example/
│ ├── entity/
│ │ └── example.py
│ ├── dto/
│ │ └── example_dto.py
│ ├── repository/
│ │ └── example_repository.py
│ └── service/
│ └── example_service.py
├── app/
│ └── example/
│ ├── dto/
│ │ ├── request/
│ │ │ └── example_request_dto.py
│ │ └── response/
│ │ └── example_response_dto.py
│ └── usecase/
│ └── example_usecase.py
└── api/
└── example/
└── router/
└── example_router.pycore 모듈 설명:
파일명 | 설명 |
|---|---|
config.py | 프로젝트 설정을 관리합니다. |
datastore.py | 데이터베이스 연결 및 세션 관리를 담당합니다. |
enum.py | 프로젝트에서 사용하는 열거형 타입을 정의합니다. |
helper.py | 여러 곳에서 사용되는 헬퍼 함수들을 제공합니다. |
util.py | 공통 유틸리티 함수를 제공합니다. |
dto.py | 공통적으로 사용하는 DTO를 정의합니다. |
from sqlalchemy import Column, Integer, String
from database import Base
class Example(Base):
__tablename__ = 'examples'
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
description = Column(String)from pydantic import BaseModel
class ExampleDTO(BaseModel):
id: int
name: str
description: str
@staticmethod
def from_entity(entity):
return ExampleDTO(
id=entity.id,
name=entity.name,
description=entity.description
)from sqlalchemy.orm import Session
from .entity.example import Example
class ExampleRepository:
def get_example(self, db: Session, example_id: int):
return db.query(Example).filter(Example.id == example_id).first()
def create_example(self, db: Session, example: Example):
db.add(example)
db.commit()
db.refresh(example)
return examplefrom sqlalchemy.orm import Session
from .repository.example_repository import ExampleRepository
from .dto.example_dto import ExampleDTO
from .entity.example import Example
class ExampleService:
def __init__(self):
self.repository = ExampleRepository()
def get_example(self, db: Session, example_id: int) -> ExampleDTO:
example = self.repository.get_example(db, example_id)
if example:
return ExampleDTO.from_entity(example)
else:
return None
def create_example(self, db: Session, name: str, description: str) -> ExampleDTO:
new_example = Example(name=name, description=description)
created_example = self.repository.create_example(db, new_example)
return ExampleDTO.from_entity(created_example)from pydantic import BaseModel
class ExampleRequestDTO(BaseModel):
name: str
description: strfrom pydantic import BaseModel
class ExampleResponseDTO(BaseModel):
id: int
name: str
description: strfrom sqlalchemy.orm import Session
from main.domain.example.service.example_service import ExampleService
from main.app.example.dto.request.example_request_dto import ExampleRequestDTO
from main.app.example.dto.response.example_response_dto import ExampleResponseDTO
class ExampleUseCase:
def __init__(self):
self.service = ExampleService()
def get_example(self, db: Session, example_id: int) -> ExampleResponseDTO:
example_dto = self.service.get_example(db, example_id)
if example_dto:
return ExampleResponseDTO(**example_dto.dict())
else:
return None
def create_example(self, db: Session, request_dto: ExampleRequestDTO) -> ExampleResponseDTO:
example_dto = self.service.create_example(db, request_dto.name, request_dto.description)
return ExampleResponseDTO(**example_dto.dict())from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from main.app.example.usecase.example_usecase import ExampleUseCase
from main.app.example.dto.request.example_request_dto import ExampleRequestDTO
from main.app.example.dto.response.example_response_dto import ExampleResponseDTO
from main.database import get_db
router = APIRouter()
use_case = ExampleUseCase()
@router.get("/examples/{example_id}", response_model=ExampleResponseDTO)
def read_example(example_id: int, db: Session = Depends(get_db)):
result = use_case.get_example(db, example_id)
if result:
return result
else:
raise HTTPException(status_code=404, detail="Example not found")
@router.post("/examples/", response_model=ExampleResponseDTO)
def create_example(request_dto: ExampleRequestDTO, db: Session = Depends(get_db)):
return use_case.create_example(db, request_dto)예시 스토리:
개발자 민수는 비즈니스 로직에 새로운 할인 정책을 적용해야 했습니다.
서비스 레이어에서 로직을 수정했지만, router 레이어와 독립적이기 때문에 다른 팀원들이 작업 중인 API 엔드포인트 코드에는 영향이 없었습니다.
덕분에 민수는 빠르게 수정 작업을 완료하고 배포할 수 있었습니다.
예시 스토리:
신입 개발자 지혜는 프로젝트에 투입된 첫 날, 코드 구조를 파악해야 했습니다.
모듈이 명확히 분리되어 있어 지혜는 서비스 레이어와 router 레이어를 각각 이해할 수 있었고, 필요한 부분에 집중하여 빠르게 업무에 적응할 수 있었습니다.
예시 스토리:
QA 엔지니어 철수는 새로운 기능에 대한 단위 테스트를 작성하고 있었습니다.
모듈간 결합도가 낮아 Mock 객체를 사용하여 데이터베이스와 API 호출 없이도 서비스 레이어의 로직을 검증할 수 있었습니다.
이는 테스트 시간을 크게 단축시켰습니다.
예시 스토리:
프로덕트 매니저는 새로운 모바일 앱 출시를 계획하고 있었습니다.
개발자 수진은 기존 서비스 레이어를 그대로 활용하여 모바일 전용 API를 빠르게 구축할 수 있었습니다.
덕분에 출시 일정에 맞춰 개발을 완료할 수 있었습니다.
예시 스토리:
다른 팀에서 비슷한 기능이 필요하다는 요청이 들어왔습니다.
개발자 영호는 기존 서비스 레이어의 비즈니스 로직을 공유하여 코드 재사용을 가능하게 했고, 다른 팀은 빠르게 기능을 구현할 수 있었습니다.
애저(Azure) SDK: 마이크로소프트는 일부 서비스에서 FastAPI를 사용하여 API를 구축하고 있습니다. FastAPI의 빠른 개발 속도와 높은 성능을 활용하고 있습니다.
내부 도구 개발: 우버의 엔지니어들은 빠른 프로토타이핑과 내부 도구 개발을 위해 FastAPI를 활용하고 있습니다. 비동기 처리를 통한 고성능이 요구되는 환경에서 효과적으로 사용되고 있습니다.
데이터 사이언스 API: 넷플릭스의 데이터 사이언스 팀은 모델 서빙을 위해 FastAPI를 사용하고 있습니다. 빠른 응답 시간과 자동 문서화 기능이 큰 이점으로 작용합니다.
FastAPI를 활용한 웹 애플리케이션 개발에서 service 구현 그룹과 router 구현 그룹으로의 그룹핑은 결합도를 낮추고 응집도를 높이는 효과적인 전략입니다.
이를 통해 개발 생산성을 향상시키고, 유지보수성과 확장성을 높일 수 있습니다.
역할 분리를 통해 모듈간 독립성을 확보합니다.
결합도 감소로 변경의 영향 범위를 최소화합니다.
응집도 향상으로 코드의 가독성과 유지보수성을 높입니다.
유연한 확장성으로 다양한 요구사항에 대응할 수 있습니다.
프로젝트의 규모와 복잡도에 상관없이, 모듈간 결합도를 낮추는 아키텍처를 채택함으로써 더 나은 코드 품질과 개발 효율성을 달성할 수 있습니다.
이러한 전략은 FastAPI의 장점을 최대한 활용하는 데에도 도움이 됩니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.