Dependency Injection 이해하기(with FastAPI)

hodu·2023년 1월 23일
0

클린아키텍쳐

목록 보기
2/2

Dependency Injection

의존성 주입이란?

디자인 패턴으로 모듈이나 컴포넌트의 의존성을 런타임에 주입할 수 있도록 한다. 의존성 주입의 궁극적인 목적으로는 "관심사 분리"에 있다

조금 더 쉽게 말해보자면...

A라는 코드는 반드시 B라는 코드를 실행해야만 동작이 된다.
이런 관계에서는 A 👉 B 흐름으로 의존 관계가 형성된다.
이런 코드가 현실 세계에서는 굉장히 많을 수도 있다.

하지만 이 코드를 지양해야 하는 이유는 B코드가 수정되면 반드시 A에 영향을 주게된다.

이런 흐름을 역전시키는 원칙이 SOLID 원칙 중 DIP(Dependency Injection Principle)이다.

  • 상위 모듈은 하위 모듈에 의존해서는 안된다. 둘다 추상화에 의존해야 한다.
  • 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.

이 두 가지 원칙을 지키면 두 코드 모두 의존하지 않고, 영향을 받지 않게된다.

그러면 의존성 주입은 무엇일까?

하드코딩 된 A코드와 B코드 사이의 관계를 인자로 전달하는 방법으로 변경하여 클래스, 함수의 종속성을 관리하는 것을 의미한다.

예를 들어보자.

class Database:
    def __init__(self):
        self._connection = None
    
    def connect(self):
        self._connection = "데이터 베이스랑 연결되었습니다."
        print(self._connection)

    def get_connection(self):
        return self._connection
    
class UserService:
    def __init__(self, db: Database):
        self._db = db
    
    def get_user(self, user_id: int) -> dict:
        self._db.connect()
        return {'user_id': user_id, 'name': '안뇽'}

database = Database()
user_service = UserService(database)
user = user_service.get_user(1)
print(user)

마지막줄을 보면

database = Database()
user_service = UserService(database)

UserService 클래스는 Database 클래스에 따라 달라지므로 Database 클래스의 인스턴스가 UserService 생성자에게 인자로 전달된다.
이를 통해 UserService는 데이터베이스 객체를 사용하여 데이터베이스에 연결하고 사용자를 검색할 수 있다.

또한 이렇게 구현하면 UserService 클래스를 수정하지 않고 데이터베이스 클래스의 구현을 변경하거나 다른 데이터베이스를 사용할 수 있다.

UserService 클래스는 데이터베이스 클래스가 연결을 만드는 방법을 알 필요가 없으며 connect() 메서드만 존재한다.
이런식으로 구현하면 UserService 클래스가 Database 클래스와 분리되어 보다 유연하고 쉽게 테스트할 수 있다.

Python의 다양한 DI 툴

Fastapi에서는 Depends를 이용하고 django에서는 dictionary를 이용하여 주입한다. DRF에서는 클래스를 이용하여 주입하게 된다고 함.
문제는 프레임워크에 강하게 종속적이기 때문에 다른 프레임 워크에서는 사용하기 어렵다고 한다.

fastapi의 Depends의 다양한 사용법

Depends는 의존성 주입 외에도 다양한 이유로 사용한다.

  1. 인증, 권한 부여
    OAuth2PasswordBearer를 이용하여 유저를 인증할 수 있다.

    ...
    oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")
    
    async def get_current_user(token: str = Depends(oauth2_scheme)):
        user = authenticate_user(token)
    	...(생략)...
        return user
    
    @app.get("/items/{item_id}")
    async def read_item(item_id: int, current_user=Depends(get_current_user)):
        return {"item_id": item_id, "owner": current_user.username}
  2. 공통 기능 최적화(코드 최적화)

  3. 추상화

  4. 데이터 유효성 검사

    # 데코레이터를 이용하여 사용한다.
    # Depends를 이용하여 유효성 검사가 가능하다.
    from fastapi import FastAPI, Depends, Query
    
    app = FastAPI()
    
    @app.get("/items/{item_id}")
    async def read_item(item_id: int, limit: int = Depends(validate_query_param)):
        return {"item_id": item_id, "limit": limit}
    
    async def validate_query_param(limit: int = Query(..., gt=0)):
        return limit
  5. 의존성 주입

추가 정보

  • 의존을 의존할 수 있다.(원하는 만큼 의존에 의존에 의존을 더할 수 있다.)

  • 특정 값을 리턴하지 않지만 실행은 해야하는 경우 데코레이터에 리스트 형태로도 작성할 수 있다.

    class Item(BaseModel):
        name: str
        description: str
    
    def validate_item(item: Item):
        if item.name != "valid":
            raise ValueError("Invalid item")
        return item
    
    @app.post("/items/", dependencies=[Depends(validate_item)])
    async def create_item(item: Item):
        return {"item": item}
    

참고 링크

profile
안녕 세계!

0개의 댓글