트랜잭션(transaction)은 데이터베이스 관리 시스템(DBMS)에서 수행되는 논리적인 작업의 단위입니다. 데이터베이스에서 일어나는 일련의 연산들을 의미하며, 이러한 연산들은 모두 성공하거나 모두 실패해야 합니다. 해당 작업 단위로 분리됨으로서 데이터베이스는 일관성을 유지할 수 있습니다.
저희 프로젝트는 연관된 모델이 많고 비지니스 로직마다 연관된 모델를 저장하거나 변경시켜야 하는 부분이 정말 많습니다. 예를 들어 유저가 스토리를 작성했다면, 유저와 연관된 뱃지의 경험치, 스토리의 정보, 스토리 사진, 위치등 많은 정보가 저장되어야 합니다. 만약 스토리를 저장할 때 오류가 난다면 뱃지의 경험치가 변경된 것 또한 롤백 되어야 하지만 트랜잭션이 없으면 해당 부분이 반영되지 않습니다. 이렇게 된다면 데이터베이스의 일관성이 깨지게 됩니다.
START TRANSACTION; //트랜잭션 시작
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
COMMIT;
-- 또는
ROLLBACK;
SQL 쿼리에서는 해당 쿼리처럼 쿼리를 작성하여 Transaction을 수행할 수 있습니다.
TypeORM에서 트랜잭션을 적용하기 위해서는 DataSource나 EntityManager가 제공하는 Transaction를 사용하거나 QueryRunner를 이용하여 Transaction를 적용해야 합니다.
await myDataSource.transaction(async (transactionalEntityManager) => {
// execute queries using transactionalEntityManager
})
// create a new query runner
const queryRunner = dataSource.createQueryRunner()
// establish real database connection using our new query runner
await queryRunner.connect()
// now we can execute any queries on a query runner, for example:
await queryRunner.query("SELECT * FROM users")
// we can also access entity manager that works with connection created by a query runner:
const users = await queryRunner.manager.find(User)
// lets now open a new transaction:
await queryRunner.startTransaction()
이 두 방식 모두 Transaction를 적용할 수 있지만 해당 문제점을 깨달은 시점에서 작업이 어느 정도 진행되었고 기존 구조를 깨뜨리고 해당 방식으로 바꾸기에는 너무 비용이 크다고 판단하였습니다. 코드 품질이 떨어지거나 직접 DataSource를 주입 받아 사용해야 하니까요. 그래서 두 가지 방법 중 1가지를 선택하게 되었습니다.