회원가입을 로직을 구현하면서 동시성에 관해 고민을 하게 되었다.
async signUp(createUserDto: CreateUserDto): Promise<void> {
const { username, password } = createUserDto;
await this.checkUser(username); // 아이디가 존재하면 에러 처리
const encryptedPassword = await this.encryptPassword(password); // 비밀번호 암호화
await this.usersRepository.createUser(
{ ...createUserDto, password: encryptedPassword },
PROVIDER.LOCAL,
);
}
async checkUser(username: User['username']): Promise<void> {
const user = await this.usersRepository.getUserByUsername(username);
if (user) {
throw new BadRequestException('이미 존재하는 아이디가 있습니다.');
}
}export class UsersRepository {
constructor(private mysqlService: MysqlPrismaService) {}
async getUserByUsername(username: User['username']): Promise<User> {
return await this.mysqlService.user.findFirst({ where: { username } });
}
async createUser(createUserDto: CreateUserDto, provider: PROVIDER): Promise<void> {
await this.mysqlService.user.create({ data: { ...createUserDto, provider } });
}
}기존 코드의 로직은 다음과 같다.

[1]아이디가 존재하면 에러 처리 실행 → 에러 없음 → [2]비밀번호 암호화 실행 중[1]아이디가 존재하면 에러 처리 실행 → 에러 없음[3]계정 생성 실행 → DB에 다음과 같이 계정 생성username=”test1234”
nickname=”AAA”[3]계정 생성 실행 → DB에 다음과 같이 계정 생성username=”test1234”
nickname="BBB"계정이 두 개 생기게 됨
생각나는 해결방안은 다음과 같다.
트랜잭션 격리 조건에 대해 어떤 것을 써야할 지 고민을 했다.
코드는 다음과 같아진다.
await this.mysqlService.$transaction(
async (tx) => {
const user = await tx.user.findFirst({ where: { username } });
if (user) throw new BadRequestException('이미 존재하는 아이디가 있습니다.');
await this.mysqlService.user.create({ data: { ...createUserDto,provider} });
},
{
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
},
);
기존코드에서 [1]아이디가 존재하면 에러 처리 부분을 제거하고 다음과 같이 쿼리를 두개 보내겠다. (똑같은 아이디가 2개 존재하는 상황 만들기 위해)
{
"username": "test@naver.com",
"password": "password@311",
"nickname": "AAA"
} 결과 
{
"username": "test@naver.com",
"password": "password@311",
"nickname": "BBB"
} 결과 
이렇게 같은 아이디가 2개 생성되면 추후에 아이디를 통해 찾으려고 하면 문제가 발생할 수 있다.
해결책
테이블에 (username, provider)를 UNIQUE로 만들자!


Unique constraint failed on the constraint: User_username_provider_keycheckUser()를 지웠다.UsersRepository
export class UsersRepository {
constructor(private mysqlService: MysqlPrismaService) {}
async createUser(createUserDto: CreateUserDto, provider: PROVIDER): Promise<void> {
await this.mysqlService.user.create({ data: { ...createUserDto, provider } });
}
}
UsersService
export class UsersService {
constructor(private usersRepository: UsersRepository) {}
async signUp(createUserDto: CreateUserDto): Promise<void> {
const encryptedPassword = await this.encryptPassword(createUserDto.password); // 비밀번호 암호화
await this.usersRepository.createUser(
{ ...createUserDto, password: encryptedPassword },
PROVIDER.LOCAL,
);
}
}