2025-03-05 tryCUD, doCUD로 안전장치 마련하기
데이터베이스에 변경을 가하는 작업, Create, Update, Delete는 위험하다. 따라서 tryCreate
, tryUpdate
, tryDelete
를 작성하여 변경전후를 미리 테스팅하거나 로깅해볼 수 있도록 하는 안전장치를 작성하는 것이 중요하다. try*
메서드는 직접 데이터베이스를 건드는 것이 아니므로 추가될, 변경될, 그리고 삭제될 엔티티를 미리 받아보아야 한다. 그럼 실제 작업은 어디서 할까? 바로 do*
메서드를 이용하는 것이다. 특히 doCreate
, updateCreate
작업이 용이한데, 엔티티의 상태 전후를 추적할 수 있기 때문이다.
GPT
1. 안전장치의 필요성
1.1. 데이터 무결성 보호
- 예상치 못한 예외나 충돌로 인해 데이터가 손상될 가능성이 있다.
- 기존 데이터를 유지하면서 변경 사항을 미리 검증할 필요가 있다.
1.2. 로깅 및 디버깅 용이성
- 변경 전후 데이터를 비교하여 예상하지 못한 동작을 추적할 수 있다.
- 필요 시 변경 사항을 취소(rollback)할 수 있는 여지를 남길 수 있다.
1.3. 예외 처리를 통한 안정성 향상
- 변경 작업이 실패했을 경우, 적절한 예외 처리를 통해 애플리케이션의 안정성을 높일 수 있다.
2. tryCreate
, tryUpdate
, tryDelete
구현 예제
아래는 TypeScript와 TypeORM을 기반으로 한 예제 코드이다.
2.1. tryCreate
function tryCreate<T>(entity: T): T {
console.log("[tryCreate] Preview entity:", entity);
return entity;
}
2.2. tryUpdate
function tryUpdate<T>(existingEntity: T, updatedFields: Partial<T>): T {
const updatedEntity = { ...existingEntity, ...updatedFields };
console.log("[tryUpdate] Before update:", existingEntity);
console.log("[tryUpdate] After update:", updatedEntity);
return updatedEntity;
}
2.3. tryDelete
function tryDelete<T>(entity: T): T {
console.log("[tryDelete] Preview entity before deletion:", entity);
return entity;
}
3. 실제 데이터 변경을 위한 doCreate
, doUpdate
, doDelete
do*
메서드는 try*
메서드에서 확인한 엔티티를 실제로 데이터베이스에 반영하는 역할을 한다.
3.1. doCreate
import { Repository } from "typeorm";
async function doCreate<T>(repository: Repository<T>, entity: T): Promise<T> {
const createdEntity = repository.create(entity);
return await repository.save(createdEntity);
}
3.2. doUpdate
async function doUpdate<T>(repository: Repository<T>, id: number, updatedFields: Partial<T>): Promise<T | null> {
const entity = await repository.findOneBy({ id } as any);
if (!entity) return null;
Object.assign(entity, updatedFields);
return await repository.save(entity);
}
3.3. doDelete
async function doDelete<T>(repository: Repository<T>, id: number): Promise<boolean> {
const entity = await repository.findOneBy({ id } as any);
if (!entity) return false;
await repository.remove(entity);
return true;
}
4. 적용 예제
다음은 User
엔터티를 관리하는 Repository에 위의 함수를 적용하는 예제이다.
import { DataSource, Repository } from "typeorm";
import { User } from "./entities/User";
const dataSource: DataSource = new DataSource({
type: "postgres",
host: "localhost",
port: 5432,
username: "user",
password: "password",
database: "test_db",
entities: [User],
synchronize: true,
});
async function main() {
await dataSource.initialize();
const userRepository: Repository<User> = dataSource.getRepository(User);
const previewUser = tryCreate({ name: "John Doe", email: "john@example.com" });
const newUser = await doCreate(userRepository, previewUser);
const existingUser = await userRepository.findOneBy({ id: 1 });
if (existingUser) {
const previewUpdatedUser = tryUpdate(existingUser, { email: "john.doe@example.com" });
await doUpdate(userRepository, 1, previewUpdatedUser);
}
const previewDeletedUser = tryDelete(existingUser!);
await doDelete(userRepository, 1);
}
main().catch(console.error);
5. 결론
tryCreate
, tryUpdate
, tryDelete
패턴을 도입하면 데이터 변경 작업을 보다 안전하게 수행할 수 있다. 이를 통해 다음과 같은 이점을 얻을 수 있다.
- 사전 검증 및 로깅: 변경 사항을 미리 점검하고 로깅할 수 있음
- 예외 처리 강화: 데이터베이스 작업 중 발생할 수 있는 오류를 효과적으로 처리
- 데이터 보호: 실수로 인한 데이터 손상을 방지할 수 있음
이러한 패턴을 활용하면 데이터 변경의 안전성을 높이고 디버깅과 검증이 용이해진다. try*
메서드로 미리 변경 사항을 검토한 후 do*
메서드로 실제 변경을 적용하는 방식이 실무에서도 매우 유용할 것이다.