새소식

반응형
Python/Clean Code Series(클린코드)

FastAPI와 함께하는 SOLID 원칙: Python으로 깔끔한 코드 작성하기

  • -
반응형

FastAPI S.O.L.I.D

소프트웨어 설계에서 중요한 SOLID 원칙을 FastAPI와 Python을 활용해 어떻게 적용할 수 있는지 알아보겠습니다. 🚀

SOLID는 로버트 C. 마틴(일명 "Uncle Bob")이 제안한 5가지 설계 원칙으로, 코드를 더 깔끔하고 유지보수하기 쉽도록 도와줍니다. 특히 객체 지향 프로그래밍과 API 개발에서 빛을 발하죠. ✨

 

이 글에서는 온라인 서점의 도서 재고 관리 API를 예제로 삼아 각 원칙을 하나씩 살펴보겠습니다. 이미 FastAPI로 API를 만들어 본 경험이 있는 분들을 위해, 기초적인 설명은 건너뛰고 실전 적용에 집중하겠습니다. 💡 그럼 시작해볼까요? 💨


📚 예제 시나리오: 온라인 서점 재고 관리 시스템

우리가 만들 API는 온라인 서점에서 도서를 관리하는 시스템입니다. 이 API는 다음 기능을 제공해야 합니다:

✅ 새 도서 추가 ✅ 도서 정보 수정 ✅ 도서 정보 조회 ✅ 도서 삭제

이제 이 시나리오를 바탕으로 SOLID 원칙을 하나씩 적용하며 코드를 설계해 보겠습니다. 🏗️


1️⃣ 단일 책임 원칙 (SRP): 한 가지 일만 잘하자 🎯

원칙: "클래스는 단 하나의 책임만 가져야 하며, 변경 이유도 하나여야 한다."

쉽게 말해, 한 클래스가 너무 많은 일을 하면 안 됩니다! ❌ 예를 들어, 도서 데이터를 정의하는 일과 도서를 관리하는 로직을 한꺼번에 처리하면 코드가 복잡해지겠죠? 🤯 그래서 이 둘을 분리해 보겠습니다.

✅ 적용 예시

from pydantic import BaseModel

# 📖 도서 데이터 모델
class Book(BaseModel):
    book_id: int
    title: str
    price: float
    stock: int

# 📦 도서 관리 로직
class BookManager:
    def __init__(self):
        self.book_list = []

    def register_book(self, book: Book):
        self.book_list.append(book)

    def edit_book(self, book_id: int, new_book: Book):
        for idx, item in enumerate(self.book_list):
            if item.book_id == book_id:
                self.book_list[idx] = new_book
                return True
        return False

✅ Book은 데이터 구조만 정의하고, BookManager는 도서 관리 로직만 담당합니다. 🎯 ✅ 이렇게 하면 코드가 훨씬 깔끔해지고, 유지보수도 쉬워집니다! 🛠️

반응형

2️⃣ 개방/폐쇄 원칙 (OCP): 확장은 OK, 수정은 NO 🚪

원칙: "클래스는 확장에 열려 있고 수정에는 닫혀 있어야 한다."

기존 코드를 수정하지 않고도 새로운 기능을 추가할 수 있어야 합니다. 🛠️ 예를 들어, 도서에 할인 기능을 추가하고 싶다면, BookManager를 직접 고치는 대신 확장하는 방식으로 해결할 수 있습니다. ✅

✅ 적용 예시

class SaleBookManager(BookManager):
    def apply_sale(self, book_id: int, sale_rate: float):
        book = self.find_book(book_id)
        if book:
            book.price *= (1 - sale_rate / 100)
            return True
        return False

✅ BookManager를 수정하지 않고 SaleBookManager에서 기능을 추가! 🎉 ✅ 기존 코드를 건드리지 않고도 기능을 확장했으니 OCP를 잘 지켰다!


3️⃣ 리스코프 치환 원칙 (LSP): 부모와 자식의 조화 🤝

원칙: "하위 클래스는 상위 클래스로 대체될 수 있어야 하며, 프로그램 동작이 바뀌면 안 된다."

쉽게 말해, SaleBookManager가 BookManager를 완벽하게 대체할 수 있어야 합니다. 🔄

✅ 적용 예시

def handle_book(manager: BookManager):
    book = Book(book_id=1, title="Python Guide", price=30000, stock=5)
    manager.register_book(book)
    return manager.find_book(1)

# 두 클래스 모두 문제없이 동작 ✅
basic_manager = BookManager()
sale_manager = SaleBookManager()

print(handle_book(basic_manager))  # 정상 출력 ✅
print(handle_book(sale_manager))   # 정상 출력 ✅

✅ BookManager를 기대하는 곳에서 SaleBookManager도 문제없이 동작 💯 ✅ LSP를 충족했으니 안전한 상속을 적용한 셈! 


4️⃣ 인터페이스 분리 원칙 (ISP): 필요한 것만 가져가자 ✂️

원칙: "클라이언트가 사용하지 않는 인터페이스에 강제로 의존하지 않아야 한다."

큰 인터페이스를 만들기보다, 작고 명확한 인터페이스로 나누는 것이 좋습니다. 🛠️

✅ 적용 예시

from abc import ABC, abstractmethod

class BookRecorder(ABC):
    @abstractmethod
    def record(self, book: Book) -> Book:
        pass

✅ 이렇게 필요한 기능만 분리해서 구현하면 불필요한 코드 작성을 줄일 수 있습니다! 🚀


5️⃣ 의존성 역전 원칙 (DIP): 추상화에 의존하자 🏗️

원칙: "고수준 모듈은 저수준 모듈에 의존하면 안 된다. 둘 다 추상화에 의존해야 한다."

직접 구현(예: 데이터베이스)에 의존하지 않고, **인터페이스(추상화)**에 의존하도록 설계합니다. 🎯

✅ 적용 예시

class BookManager:
    def __init__(self, storage: BookStorage):
        self.storage = storage

✅ BookManager는 BookStorage라는 추상 인터페이스에 의존! ✅ ✅ PostgreSQL, MongoDB 등 다양한 저장소로 쉽게 교체 가능! 🔄


🎯 마무리: SOLID로 더 나은 코드를!

FastAPI와 SOLID 원칙을 결합하면 유지보수와 확장이 쉬운 API를 만들 수 있습니다. 💡

책임 분리(SRP)확장성(OCP)안전한 상속(LSP)유연한 인터페이스(ISP)추상화 의존(DIP)

이 원칙들을 적용하면 코드 품질이 올라가고, 변화에 유연하게 대처할 수 있습니다. 🎯

✅ 여러분의 다음 FastAPI 프로젝트에서 SOLID를 적용해 보세요! 🚀

읽어주셔서 감사합니다! 🙌 

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.