Skip to content

uow-pattern

TL;DR

  • Unit of Work 패턴은 데이터베이스 작업을 하나의 작업 단위로 묶어 일관성을 보장하고, 커밋과 롤백을 통해 데이터의 무결성을 유지하는 패턴임.
  • 이 패턴은 주로 트랜잭션 관리를 위해 사용되며, 여러 작업을 한 번에 처리하고, 문제가 발생했을 때 모두 롤백할 수 있는 장점이 있음.
  • Repository 패턴과 함께 사용되어 데이터 작업의 분리를 더욱 명확히 할 수 있음.

Unit of Work 패턴 개요

  • 단일 작업 단위: 여러 데이터베이스 작업을 하나의 작업 단위로 묶어 관리함. 이를 통해 트랜잭션 내에서 일관된 상태를 유지할 수 있음.
  • 트랜잭션 관리: Unit of Work는 트랜잭션의 시작과 끝을 관리함. 작업이 성공하면 커밋하고, 오류가 발생하면 롤백하여 데이터의 무결성을 보장함.
  • 변경 추적: 객체의 상태 변화를 추적하여, 필요한 경우 데이터베이스에 반영함. 이를 통해 불필요한 작업을 줄이고 효율성을 높일 수 있음.

Unit of Work 패턴의 구현

SQLAlchemy를 사용한 구현 예시

SQLAlchemy에서 Unit of Work 패턴을 구현할 때, with 문을 이용해 트랜잭션의 범위를 지정하고, Repository 객체가 Unit of Work 객체를 받아서 데이터베이스 작업을 처리함.

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)

# Unit of Work 클래스 정의
class UnitOfWork:
    def __init__(self, session_factory):
        self.session_factory = session_factory
        self.session = None

    def __enter__(self):
        self.session = self.session_factory()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            self.session.rollback()
        else:
            self.session.commit()
        self.session.close()

# Repository 클래스 정의
class UserRepository:
    def __init__(self, uow: UnitOfWork):
        self.uow = uow

    def add(self, user):
        self.uow.session.add(user)

    def get(self, user_id):
        return self.uow.session.query(User).filter_by(id=user_id).first()

# 데이터베이스 연결 및 세션 생성
engine = create_engine('sqlite:///example.db')
Session = sessionmaker(bind=engine)

# Unit of Work 사용 예시
with UnitOfWork(Session) as uow:
    user_repo = UserRepository(uow)

    user1 = User(name='Alice')
    user2 = User(name='Bob')

    user_repo.add(user1)
    user_repo.add(user2)
    # Unit of Work는 with 블록의 끝에서 자동으로 커밋됨
  • with 문을 통한 트랜잭션 관리: UnitOfWork 클래스가 __enter____exit__ 메서드를 통해 트랜잭션을 관리함. with 블록을 벗어나면 트랜잭션이 커밋되거나 롤백됨.
  • Repository와의 통합: UserRepository 클래스가 Unit of Work 객체를 받아서 데이터베이스 작업을 수행함. 이를 통해 데이터 작업의 책임을 분리하고 코드의 가독성을 높임.

Django를 사용한 구현 예시

Django에서는 transaction.atomic()을 사용하여 Unit of Work 패턴을 구현할 수 있으며, Repository 객체가 Unit of Work 객체를 받아서 with 문을 통해 작업을 처리할 수 있음.

from django.db import transaction
from myapp.models import User

# Unit of Work 클래스 정의
class UnitOfWork:
    def __enter__(self):
        self.transaction = transaction.atomic()
        self.transaction.__enter__()

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            self.transaction.__exit__(exc_type, exc_val, exc_tb)
        else:
            self.transaction.__exit__(None, None, None)

# Repository 클래스 정의
class UserRepository:
    def __init__(self, uow):
        self.uow = uow

    def add(self, user):
        user.save()

    def get(self, user_id):
        return User.objects.get(id=user_id)

# Unit of Work 사용 예시
with UnitOfWork() as uow:
    user_repo = UserRepository(uow)

    user1 = User(name='Alice')
    user2 = User(name='Bob')

    user_repo.add(user1)
    user_repo.add(user2)
    # Unit of Work는 with 블록의 끝에서 자동으로 커밋됨
  • transaction.atomic()을 통한 트랜잭션 관리: Django의 transaction.atomic()을 사용하여 Unit of Work를 구현함. with 문 내에서 작업이 성공하면 자동으로 커밋되고, 예외가 발생하면 롤백됨.
  • Repository와의 통합: UserRepository 클래스가 Unit of Work 객체를 받아 Django ORM을 통해 데이터베이스 작업을 처리함.

Python에서의 Unit of Work 패턴 사용

  • 상황에 따른 적용: 웹 애플리케이션, 특히 데이터베이스 작업이 복잡하고 다수의 엔터티가 연관된 경우 Unit of Work 패턴을 활용하는 것이 유용함.
  • 라이브러리 사용: SQLAlchemy나 Django ORM에서 Unit of Work 패턴을 기본적으로 지원함. 이를 통해 트랜잭션 관리를 자동화할 수 있음.
  • 직접 구현: 특수한 요구사항이 있는 경우 Unit of Work 패턴을 직접 구현하여 트랜잭션 관리, 변경 추적, 커밋 및 롤백 기능을 커스터마이징할 수 있음.

이와 같은 방법으로 Python에서 Unit of Work 패턴을 사용하면 데이터베이스 작업의 일관성과 무결성을 유지할 수 있음. SQLAlchemy와 Django는 이 패턴을 내장하고 있어, 개발자가 쉽게 트랜잭션 관리를 수행할 수 있도록 지원함.

Reference