작업 단위 패턴
-
작업 단위 패턴은 저장소와 서비스 계층 패턴을 하나로 묶어주는 패턴이다.
-
저장소 패턴이 영속적 저장소 개념에 대한 추상화라면 작업 단위 패턴은 원자적 연산이라는 개념에 의한 추상화이다.
-
UoW (Unit of Work)
패턴을 사용하면, 서비스 계층과 데이터 계층을 완전히 분리할 수 있다.
작업 단위 패턴의 장점
-
작업 단위 패턴은 영속적 저장소에 대한 단일 진입점으로 작용한다.
UoW
는 어떤 객체가 메모리에 적재 되었고, 어떤 객체가 최종 상태인지를 기억한다. -
작업에 사용할 데이터베이스의 안정적인 스냅샷을 제공하고, 연산을 진행하는 과정에서 변경하지 않은 객체에 대한 스냅샷도 제공한다.
-
변경 내용을 한번에 영속화할 방법을 제공한다. 어딘가 잘못되더라도 일관성이 없는 상태로 끝나지 않는다.
-
영속성을 처리하기 위한 간단한 API와 저장소를 쉽게 얻을 수 있는 장소를 제공한다.
class FakeUnitOfWork(unit_of_work.AbstractUnitOfWork):
def __init__(self):
self.batches = FakeRepository([])
self.commited = False
def commit(self):
self.commited = True
def rollback(self):
pass
def test_add_batch():
uow = FakeUnitOfWork()
services.add_batch("b1", "CRUNCHY-ARMCHAIR", 100, None, uow)
assert uow.batches.get("b1") is not None
assert uow.commited
def test_allocate_returns_allocation():
uow = FakeUnitOfWork()
services.add_batch("batch1", "CRUNCHY-ARMCHAIR", 100, None, uow)
result = services.allocate("o1", "COMPLICATED-LAMP", 10, uow)
assert result == "batch1"
-
세션을 사용하는 것 보다,
UoW
를 모킹하기가 더 편하다 이들은 영속성 계층을 변경하여 실제 데이터베이스를 사용하지 않고도 메모리상에서 테스트를 진행할 수 있게 해준다. -
가짜 객체 두개를 사용해서 얻을 수 있는 최송 설계에 차이가 있는 것이다.
-
실행이 빠른 테스트를 작성하는데 관심이 있다면, 가짜 객체 두 개를 사용하여 얻을 수 있는 최종 설계에 차이가 있는 것이다.
-
Session
을 사용하여 데이터베이스에 대한 질의를 사용자가 원하는 대로 만들기를 쉽지만, 이렇게 구현하면 데이터베이스에 접근하는 코드가 코드 베이스 여기저기 흩어진다. -
이러한 상황을 피하기 위해서 영속적 계층에 대한 접근을 제한하여 각 컴포넌트가 자신에게 필요한 것만 갖고 불필요한 나머지는 갖지 않도록 해야한다.
def test_rolls_back_uncommited_work_by_default(session_factory):
uow = unit_of_work.SqlAlchemyUnitOfWork(session_factory)
with uow:
insert_batch(uow.session, 'batch1', 'MEDIUM-PLINTH', 100, None)
new_session = session_factory()
rows = list(new_session.execute('SELECT * FROM "batches"'))
assert rows == []
def test_rolls_back_on_error(session_factory):
class MyException(Exception):
pass
uow = unit_of_work.SqlAlchemyUnitOfWork(session_factory)
with pytest.raises(MyException):
with uow:
insert_batch(uow.session, 'batch1', 'LARGE-FORK', 100, None)
raise MyException()
new_session = session_factory()
rows = list(new_session.execute("SELECT * FROM 'batches'"))
assert rows == []
UoW
패턴을 이용하여 서비스 코드를 테스트 한 것이다.
명시적 커밋과 암시적 커밋
Uow
패턴을 이용하면, 디폴트로 결과를 커밋하고 예외가 발생한 경우만 롤백을 하는 코드를 작성할 수 있다.
class AbstractUnitOfWork(abc.ABC):
def __enter__(self):
return self
def __exit__(self, exn_type, exn_value, traceback):
if exn_type is None:
self.commit()
else:
self.rollback()
-
암시적인 커밋은 이렇게 구현할 수 있지만, 언제 상태를 플러시 해야할지를 선택해야 한다면 명시적인 커밋을 요구하는 쪽이 더 나을 것이다.
-
코드를 몇 줄 더 작성해야하기는 하지만, 명시적 커밋을 요구하면 시스템의 상태를 바꾸는 경로가 단 하나만 존재하므로 코드를 추론하는 것이 더 쉬워진다.
-
마지막으로 롤백하는 것이 더 이해하기 쉬워서 롤백하는 쪽을 선호하기도 한다. 이럴 롤백은 마지막 커밋 지점으로 상태를 되돌리기 때문에, 사용자가 직접 커밋하지 않으면 중간의 변화가 모두 사라진다.
정리
작업 단위 패턴은 데이터 무결성 중심의 추상화이다
- 작업 단위 패턴을 사용하면, 연산의 끝에 플러시 연산을 한 번만 수행해 도메인 모델의 일관성을 강화하고 성능을 향상 시킬 때 도움이 된다.
작업 단위 패턴은 저장소와 서비스 계층 패턴과 밀접하게 연관되어 작동한다
- 작업 단위 패턴은 원자적 업데이트를 표현해 데이터 접근에 대한 추상화를 완성시켜준다. 서비스 계층의 유스 케이스들은 각각 블록 단위로 성공하거나 실패하는 별도의 작업 단위로 실행된다.
콘텍스트 관리자를 사용하는 멋진 유스케이스이다
- 콘텍스트 관리자는 파이썬에서 영역을 정의하는 전형적인 방법이다. 콘텍스트 관리자를 사용해 요청 처리가 커밋을 호출하지 않고 끝나면 자동으로 작업을 롤백할 수 있다. 이런식으로 구현하면 시스템은 기본적으로 안전한 상태가 된다.
SQLAlchemy는 이미 작업 단위 패턴을 제공한다
- SQLAlchemy 세션 객체를 더 간단하게 추상화하여 ORM과 코드 사이의 인터페이스를 더 좁힌다. 이렇게 하면 코드의 각 부분의 결합을 느슨하게 유지할 수 있다.
참고 문헌
>> Home