본문으로 바로가기
728x90
반응형

목차

     

    1.  개요

    어떤 API를 만들던 웬만해서는 repository pattern을 사용하고 있는 중이다. 처음엔 장단점을 의식하면서 사용했지만 지금은 익숙해서 그런지 별로 신경을 쓰고 있지 않는다. 최근 진행하고 있는 프로젝트에도 repositoy pattern을 사용하고 있으며 이와 더불어 "dependency-injector"라는 라이브러리도 같이 사용 중이다.

     

    그러나 처음에 depenencey-injector와 함께 repository pattern을 같이 사용하는 것에 어려움이 많았는데 그 당시 했던 고민을 따로 기록해두지 않아 이번 기회에 이 내용을 정리해 보고자 한다.

     

     

    2. Repository를 DI 해보자.

    Repository Pattern에 dependency-injector를 함께 사용하려고 했던 목적은 "Repository"를 "Container"로 관리해보고 싶다는 아이디어를 구현해 보기 위함이었다. 이러한 아이디어는 Repository를 사용하려면 항상 instance를 얻어야 하는 코드를 만들어야 하는 점이 눈엣가시였기 때문이었다.

     

    코드를 통한 예시를 덧붙이자면 다음과 같은 코드가 전반적으로 사용되는 형태였다.

    def get_id():
        repository = Repository()
        repository.find_by_id()
        ...

    결국 사용하고 싶은 코드는 다음과 같은 코드였다.

    def get_id():
        repository.find_by_id() # instance를 얻은 repository

    사실 Container를 사용하는 것이 아닌 Repository에 대한 Facade를 만들고 이를 이용하여 특정 Repository의 instance를 사용할 수 있는 방법도 존재했다. 그러나 굳이 dependency-injector라는 라이브러리를 가져다 사용한 것은 Facade를 "DI" 한다는 게 뭔가 어색했기 때문이다.

     

     

    3. Dependency Injector를 이용해 Repository의 세션 처리에 대한 문제

    가장 먼저 고민이 되었던 부분은 Repository Pattern 일관화였다. Repository Pattern의 특정 메서드는 DB에 SQL을 날리는 동작이 포함된다. 이 말인즉슨 ORM을 사용하던 Raw SQL을 사용하던 DB에 쿼리를 질의할 수 있는 부분이 있어야 한다는 의미였다. 

     

    SQLAlchemy를 애용하는 나로서는 "session"을 Repository에서 사용해야 했기에 다음과 같은 코드가 만들어졌다.

    # Example
    class Repository:
    
        def find_by_id(self):
            session.query() # session은 SQLALchemy Session
            ...

    위 형태를 보고 Repository Class를 다음과 같이 구성하여 SQLAlchemy의 session을 사용하려 했다.

    class AbstractRepository:
    	def __init__(self, session=None):
    		self.session = None
       
    
    class Repository(AbstractRepository):
        def find_by_id(self):
        	self.session.query()
    ...

    그런데 위와 같은 코드를 dependency-injector와 함께 사용하려다 보니 다음과 같은 코드가 만들어졌다.

    from dependency_injector import providers, containers
    
    
    class RepositoryContainer(containers.DeclarativeContainer):
    	session = providers.Dependency()
        
        repository1 = providers.Singleton(OneRepository, session=session)
        repository2 = providers.Singleton(TwoRepository, session=session)
        
        
    repository_container = RepositoryContainer(session=SQLALchemySessionFactory.get_session())
    repository_container.wire(packages=self.packages)

    container에 Repository를 하나씩 추가할 때마다 공통적으로 session으로 넘겨야 한다는 점이 불편했고 보기에도 뭔가 "아닌 것 같은데"라는 느낌을 지울 수 없는 코드가 만들어졌다.

     

    4.  DI를 활용해 Session 처리하기

    나중에 와서야 깨달았지만 Container를 선언할 때 굳이 session을 받을 필요 없이 AbstractRepository에 DI로 바로 session 주입할 수 있는 방법을 알게 되었다.

    from dependency_injector.wiring import Provide
    
    class AbstractRepository:
        def __init__(self, session=None):
            self.session = Provide[DataBaseContainer.session]

    그러나 위와 같은 방법은 Repository에 대한 container가 아닌 DataBaseSession을 담고 있는 Container가 하나 더 필요했다.

    from dependency_injector import providers, containers
    
    class DataBaseContainer(containers.DeclarativeContainer):
    
        engine = providers.Dependency()
        session = providers.Singleton(SQLALchemySessionFactory.get_session, engine=engine)

    SQLAlchemy의 session을 생성할 때는 SQLAlchemy의 "engine"이 필요하기에 이 부분은 DataBaseContainer를 초기화할 때 인자로 던져주는 방법을 택했다.

    database_container = DataBaseContainer(
        engine=SQLALchemyEngineFactory.get_engine(url=database.get_url())
    )
    database_container.wire(packages=self.packages)

     

     

    5.  실제로는 사용할 때는...

    그러나 아직 미처 해결하지 못한 부분은 container에 선언된 repository를 사용하는 것을 다음과 같이 상위 클래스에 container에 선언된 repository 만큼 di를 class attribute로 선언해 놓는 다점이다.

    class AbstractClass:
    
        repository1 = Provide[RepositoryContainer.repository1]
        repository2 = Provide[RepositoryContainer.repository2]
        ...

    이렇게 사용하는데서 오는 단점은 아직은 체감하지 못했다. Repository를 사용하는 구간을 딱 하나의 구간으로 선정해 놓고 사용하고 있으며 이 구간을 벗어나는 코드를 작성할 일을 아직까지는 마주하지 않았다.

     

     

     

    6. 마치며

    아직까지는 이러한 형태로 사용하는 코드는 많이 도전적인 코드라 생각하고 있다. dependency-injector에서 session을 class attribute로 di를 할 때 어떤 provider를 사용하는 것이 "Best Practice"인지 잘 모를뿐더러 Transaction 관리가 복잡해지면 이보다 더 발전된 형태로 사용해야 되지 않을까 싶다.

    728x90
    반응형