2025-03-05 tryCUD, doCUD로 안전장치 마련하기

데이터베이스에 변경을 가하는 작업, Create, Update, Delete는 위험하다. 따라서 tryCreate, tryUpdate, tryDelete를 작성하여 변경전후를 미리 테스팅하거나 로깅해볼 수 있도록 하는 안전장치를 작성하는 것이 중요하다. try* 메서드는 직접 데이터베이스를 건드는 것이 아니므로 추가될, 변경될, 그리고 삭제될 엔티티를 미리 받아보아야 한다. 그럼 실제 작업은 어디서 할까? 바로 do* 메서드를 이용하는 것이다. 특히 doCreate, updateCreate 작업이 용이한데, 엔티티의 상태 전후를 추적할 수 있기 때문이다.

GPT

1. 안전장치의 필요성

1.1. 데이터 무결성 보호

1.2. 로깅 및 디버깅 용이성

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* 메서드로 실제 변경을 적용하는 방식이 실무에서도 매우 유용할 것이다.