나는 약 5개월 전에 FastAPI를 사용하여 저장소 패턴을 적용하는 방법에 대한 기사를 썼고 많은 읽음을 받았습니다(감사합니다). 나는 여전히 저장소 패턴을 사용하면서 세션 처리를 처리하는 효율적인 방법에 대해 글을 쓰게 되었습니다.
바로 시작하기 전에 프로덕션 환경에서 API가 데이터베이스 읽기 또는 쓰기와 관련된 트랜잭션을 만들려고 할 때마다 다음 오류 중 하나가 발생한다는 것을 알았습니다.
이 오류는 다른 데이터베이스 작업을 진행하기 전에 롤백해야 하는 커밋되지 않은 트랜잭션이 진행 중임을 나타냅니다.
이 오류의 가장 일반적인 원인은 데이터베이스 트랜잭션 중에 발생하는 처리되지 않은 예외로 인해 트랜잭션이 제대로 커밋되거나 롤백되지 않는 것입니다.
이 오류는 데이터베이스에 대한 작업이나 요청이 잘못되었거나 지원되지 않음을 나타냅니다. 이 오류에는 다음을 포함하여 다양한 원인이 있을 수 있습니다.
이러한 오류를 어떻게 해결할 수 있는지에 대해 생각하고 계시리라 생각합니다. 그러나 문제가 무엇인지 확인하고 수정했는데도 문제가 지속된다는 점을 말씀드리고 싶습니다.
문제를 해결하는 방법이 궁금하다면 다음 단계를 따르세요.
나에게 효과가 있는 것으로 입증된 영구적인 솔루션을 어떻게 작업했는지 바로 살펴보겠습니다. 저장소 패턴 사용 방법을 시연할 때 작업했던 프로젝트를 계속 사용하겠습니다.
다음 코드를 사용하여 orm 기본 세션 믹스인을 저장하는 모듈이 있습니다.
# SQLAlchemy Imports from sqlalchemy.orm import Session # Own Imports from config.database import SessionLocal from core.settings import ledger_settings class ORMSessionMixin: """Base orm session mixin for interacting with the database.""" def __init__(self): """ Get the next database session from the database pool. """ self.orm: Session = self.get_db().__next__() def get_db(self): """ This method creates a database session, yields it, rollback the transaction if there's an exception and then finally closes the session. Yields: db: scoped database session """ db = SessionLocal() try: yield db except Exception: db.rollback() finally: db.close()
이 솔루션의 문제점은 트랜잭션 프로세스에서 예외가 발생하는 경우(사용자 생성, 지갑 자금 조달 등 무엇이든 가능) 예외가 제대로 처리되지 않고 전송 중인 데이터베이스 세션이 제대로 처리되지 않는다는 것입니다. 롤백을 받으세요.
3개월 간의 디버깅과 패치, 그리고 많은 연구 끝에 마침내 효율적인 세션 처리 방법을 구축할 수 있었습니다.
# SQLAlchemy Imports import sqlalchemy from sqlalchemy.orm import Session # Own Imports from config.database.connection import SessionLocal class DatabaseSessionMixin: """Database session mixin.""" def __enter__(self) -> Session: self.db = SessionLocal() return self.db def __exit__(self, exc_type, exc_val, exc_tb): try: if exc_type is not None: self.db.rollback() except sqlalchemy.exc.SQLAlchemyError: pass finally: self.db.close() SessionLocal.remove() def use_database_session(): return DatabaseSessionMixin()
이 코드에서는:
DatabaseSession
세션을 처리하고 오류 발생 시 세션이 올바르게 닫히고 롤백되는지 확인하는 컨텍스트 관리자 클래스입니다.
__enter__
메소드는 세션을 초기화하고 이를 반환합니다.
__exit__
메소드는 예외를 확인하고 예외가 발생하면 세션을 롤백합니다. 그런 다음 세션을 닫고 범위가 지정된 세션에서 제거합니다.
use_database_session
은 세션 사용을 단순화하기 위해 데코레이터 또는 컨텍스트 관리자로 사용할 수 있는 유틸리티 함수입니다.
다음은 use_database_session 유틸리티 함수를 사용하는 방법에 대한 예입니다.
with use_database_session() as db: # perform logic that uses the session # ... # After exiting the context, the session will be automatically closed and removed from the scoped session.
위의 접근 방식은 세션을 처리하는 더 깔끔하고 효율적인 방법을 제공하며 오류 발생 시 적절하게 롤백되거나 닫히도록 보장합니다. ORM에서 데이터베이스 세션을 사용하면서 리포지토리 패턴을 구현하는 방법을 살펴보겠습니다.
# SQLAlchemy Imports import sqlalchemy from sqlalchemy.orm import Session class BaseRepository: def __init__(self, session: Session): self.db = session class UserRepository(BaseRepository): """Operations to interact with the `users` table in the database.""" def get(self, user_id: int) -> User: """This method gets a user from the database.""" user = ( self.db.query(User) .filter(User.id == user_id) .first() ) return user def create(self, name: str, email: str, password: str) -> User: """This method creates a user.""" user = User(name=name, email=email, password=password) self.db.add(user) self.db.commit() self.db.refresh(user) return user def update_user(self, user_id: int, updated_data: dict): """This method updates a user.""" user = self.get(user_id) if user: for key, value in updated_data.items(): setattr(user, key, value) self.db.commit() return user return None def delete_user(self, user_id): """This method deletes a user.""" user = self.get_user(user_id) if user: self.db.delete(user) self.db.commit() return True return False
다음은 위 저장소를 애플리케이션의 서비스 계층에 통합하는 것입니다. 사용자 계정을 생성하는 서비스 기능이 있다고 가정합니다. 새로운 방법을 사용하여 수행하는 방법은 다음과 같습니다.
# Apps Imports from apps.users.models import User from apps.users.repo import UserRepository from apps.users.schemas.auth import UserCreate # Config Imports from config.security.hashers import password from config.database.session_mixin import use_database_session async def create_user(user: UserCreate) -> User: """ This function creates a new user in the database. :param user: schemas.UserCreate :type user: schemas.UserCreate :return: The user object """ with use_database_session() as db: users_repo = UserRepository(db) user = users_repo.create( user.name, user.email, password.hash(user.password) ) return user
위 패턴을 사용하면 상속된 데이터베이스 세션을 활용하면서 저장소 클래스 내에서 데이터베이스 작업을 캡슐화할 수 있습니다. 또한 ORM 모델과 저장소 논리를 깔끔하게 분리합니다.
결론적으로 백엔드 시스템을 구축할 때는 세션을 효율적으로 처리하는 것이 중요합니다.
데이터베이스 트랜잭션 중에 발생하는 sqlalchemy.exc.PendingRollbackError
및 sqlalchemy.exc.InvalidRequestError
와 같은 오류를 제대로 처리하지 않으면 데이터 불일치 및 애플리케이션 오류가 발생할 수 있습니다.
이러한 오류를 식별하고 해결하는 것은 시스템의 무결성과 신뢰성을 유지하는 데 중요합니다.
세션 처리와 관련된 문제를 해결하려면 강력한 전략을 구현하는 것이 필수적입니다. 한 가지 접근 방식은 기사에서 설명한 DatabaseSessionMixin
과 같은 컨텍스트 관리자를 사용하는 것입니다.
이 컨텍스트 관리자는 예외 발생 시 세션이 제대로 열리고 닫히고 롤백되도록 보장합니다. 컨텍스트 관리자 내에서 세션 로직을 캡슐화하면 세션 관리를 간소화하고 오류 처리를 개선할 수 있습니다.
또한 리포지토리 패턴을 애플리케이션의 서비스 계층에 통합하면 세션 처리 효율성을 더욱 향상시킬 수 있습니다.
데이터베이스 작업을 리포지토리 클래스로 분리하고 컨텍스트 관리자에서 상속된 세션을 활용함으로써 더 깔끔한 코드 구성을 달성하고 ORM 모델과 리포지토리 논리를 명확하게 분리할 수 있습니다.
전반적으로 효율적인 세션 처리는 데이터 일관성을 유지하고 오류를 방지하며 백엔드 시스템의 안정성을 보장하는 데 중요합니다.
컨텍스트 관리자 사용 및 저장소 패턴 채택과 같은 모범 사례를 따르면 개발자는 세션을 효과적으로 관리하고 데이터베이스 트랜잭션 중 오류를 처리하는 강력하고 안정적인 시스템을 구축할 수 있습니다.
나는 공연을 작성하고 Python(Django, FastAPI 등)을 사용한 구축과 관련된 계약 역할을 적극적으로 검색하는 데 열려 있습니다.