• [파이썬으로 살펴보는 아키텍처 패턴] 챕터 2 저장소 패턴

    2023. 7. 30.

    by. haong_

    저장소 패턴은 데이터 저장소를 더 간단히 추상화 한것으로 이 패턴을 사용해 모델 계층과 데이터 계층을 분리 할 수 있다.

    데이터 접근에 DIP 적용하기

    양파 아키텍쳐 - 도메인 모델에는 어떤 의존성도 없길 바란다. 모델을 내부에 있는 것으로 간주하고 의존성이 내부로 들어오게 만들어야 함.표현계층 -> 비즈니스 로직 <- 데이터베이스 계층

    일반적인 ORM 방식 : ORM에 의존하는 모델

    객체 관계 매핑(ORM) - 객체 지향 프로그래밍 언어와 관계형 데이터베이스 사이의 간격을 줄이고, 객체와 데이터베이스 간의 변환을 자동화해주는 역할, 직접 SQL을 생성하지 않고 객체를 데이터베이스에 저장하거나 가져올 수 있음. 
    영속성 무지 - 도메인 모델이 데이터를 어떻게 적재하는지 영속화하는지 알 필요가 없다는 뜻. 영속성 무지 성립시 특정 데이터 베이스 기술에 도메인이 직접 의존하지 않도록 유지 가능 

    SQLAlchemy 선언적 문법, 모델은 ORM에 의존

    from sqlalchemy import Column, ForeignKey, Integer, String 
    from sqlalchemy.ext.declarative import declarative_base 
    from sqlalchemy.orm import relationship
    
    Base = declarative_base()
    
    class OrderLine(Base):
    	id = Column(Integer, primary_key=True)
        
    class OrderLine(Base):
        id = Column(Integer, primary_key=True)  
        sku = Column(String(2S0))
        qty = Integer(String(2S0))
        order id = COlumn(Integer, ForeignKey('order. id')) 
        order = relationship(Order)
        
    class Allocation(Base):
    ....

    모델 프로퍼티가 직접 데이터베이스 컬럼과 연관되어 있는데 이 모델이 정말 데이터베이스에 대해 무지하다고 말할 수 있을지? 

    의존성 역전: 모델에 의존하는 ORM

    스키마 별도 정의하고 스키마와 도메인 모델을 상호 변환하는 매퍼(mapper) 정의  

    from sqlalchemy.orm import mapper, relationship
    
    import model 
    
    
    metadata = MetaData()
    
    order_lines = Table(  
        "order_lines",
        metadata,
        Column("id", Integer, primary_key=True, autoincrement=True),
        Column("sku", String(255)),
        Column("qty", Integer, nullable=False),
        Column("orderid", String(255)),
    )
    
    ...
    
    def start_mappers():
        lines_mapper = mapper(model.OrderLine, order_lines)

    start_mappers 호출하면 쉽게 도메인 모델 인스턴스를 DB에 저장하거나 불러올 수 있음. 하지만 호출하지 않으면 도메인 모델 클래스는 DB 인식 못함.
    이 작업을 하는 이유는 코드에서 사용된 SQLAlchemy를 제거하고 다른 ORM을 사용할 수도 있으며, 도메인 모델은 변경할 필요가 없는 이점이 있다.

    저장소 패턴 소개

    저장소 패턴은 영속적 저장소를 추상화 한 것. 모든 데이터가 메모리 상에 존재 하는 것처럼 가정해 데이터 접근과 관련된 세부사항을 감춤. 
    모든 객체가 메모리에 있더라도 객체들을 나중에 다시 찾을 수 있도록 어딘가에 보관해야함. 인메모리 데이터는 리스트나 집합과 같은 새로운 객체들을 얼마든지 추가 할 수 있다. 객체가 메모리에 존재하므로 .save() 메서드 호출 필요 없음. 

    추상화한 저장소 

    가장 간단한 저장소의 메서드 두가지 

    • add() 새 원소를 저장소에 추가
    • get() 추가한 원소를 저장소에서 가져옴 

    이렇게 강제로 단순성을 유지하면 도메인 모델과 DB 사이의 결합을 끊을 수 있다. 

    최대한 단순화한 저장소

    class AbstractRepository(abc.ABC):
    
    	@abc.abstractmethod 
    	def add(self, batch: model.Batch):
    		raise NotlmplementedError
            
    	@abc.abstractmethod
    	def get(self, reference) -> model.Batch:
    		raise NotlmplementedError

    트레이드 오프란? 

    • 이 패턴을 선택함으로 얻는 이익과 치뤄야 하는 댓가는? 
    • DDD와 의존성 역전이라는 경로를 택한 이상 저장소 패턴은 이 책에서 나열한 패턴 중에서도 가장 채택하기 쉬운 패턴.
    • 코드만 고려한다면 저장소 패턴은 단지 SQLAlchemy 추상화 (session.query(Batch))를 우리가 직접 설계한 다른 추상화 (batches_repo.get)로 바꿔치기 한 것에 지나지 않음. 
    • 읽어야 하는 도메인 객체를 새로 추가 할 때마다 저장소 클래스에 코드 몇줄 더 추가해야하지만 반대로 저장소 계층을 간단하게 추상화 할 수 있고, 추상화를 제어 할 수도 있다. 
    • 사물을 저장하는 방법을 더 쉽게 바꿀 수 있고 단위 테스트 시 가짜 저장소 제공이 쉬워짐. 

    테스트에 사용하는 가짜 저장소를 쉽게 만드는 방법

    집합을 사용하는 간단한 가짜 저장소

    class FakeRepository(AbstractRepository):
    
        def __init__(self, batches): 
        	self._batches =set(batches)
            
        def add(self, batch): 
        	self._batches.add(batch)
            
        def get(self, reference):
        	return next(b for b in self._batches if b.reference == reference)
            
        def list(self):
        	return list(self._batches)
    • 클래스가 set을 감싸는 간단한 래퍼에 불과하므로 모든 메서드는 한줄로 끝난다.
    • 추상화를 대신하는 가짜 객체를 만드는 것은 설계에 대한 피드백을 얻는 아주 좋은 방법.
    • 가짜 객체를 만들기 어렵다면 추상화를 너무 복잡하게 설계했기 때문일 것이다.

    저장소 패턴과 영속성에 대해 무지한 모델의 트레이트오프 

    장점 단점
    - 영속적 저장소와 도메인 모델 사이 인터페이스를 간단하게 유지 
    - 모델과 인프라에 대한 사항을 완전히 분리 했기 때문에 단위 테스트를 위한 가짜 저장소 쉽게 만들수 있음
    - 영속성에 대해 생각하기 전에 도메인 모델을 작성하려면 처리해야 할 비즈니스 문제에 더 잘 집중 할 수 있 
    - 객체를 테이블에 매핑하는 과정을 원하는 대로 데어 할 수 있어서 스키마를 단순화 할 수 있음 
    - ORM이 어느정도 모델과 저장소의 결합을 완화시켜준다. ORM 사용시 외래키를 변경하기는 어렵지만 필요할때 MySQL과 Postgres를 서로 바꾸기는 쉬움
    - ORM 매핑을 수동으로 하려면 작업과 코드가 더 필요 
    - 간접 계층을 추가하면 유지보수 비용 증가

    저장소 패턴 정리

    ORM에 의존성 역전을 적용하자
    - 도메인 모델은 인프라에 대해 걱정할 필요가 없어야 한다. ORM은 모델을 임포트해야하며 모델이 ORM을 임포트하면 안된다.

    저장소 패턴은 영속적 저장소에 대한 단순한 추상화다. 
    - 저장소는 컬렉션이 메모리상에 있는 객체라는 환상을 제공한다. 저장소를 사용하면 핵심 애플리케이션에는 영향을 미치지 않으면서 인프라를 이루는 세부 구조를 변경하거나 FakeRepository를 쉽게 작성 할 수 있다.

    댓글