리팩토링 하며 , 회원가입 과정이 대규모 수정이 되었다.
이메일 인증 완료 -> 서버에 전송 -> DB 에 Insert -> 여부에 따른 분기 처리
큰 문제점은 없었으나 , 이메일 인증을 프론트 단에서 검증하고 , 백엔드로 보낼때는 검증할 수 없다는 점
동기적인 처리로 , 결과 대기 등 비효율적인 점이 문제였다.
더불어 , MVC Pattern 으로 인해 , 각 Class 들이 의존 하는게 문제였다.
우선 , 이메일 인증을 임시 회원가입을 완료한 후 발송이 되고 ,
이메일에서 , redirect Code 가 포함된 사이트를 클릭할 시 , 회원가입이 마무리 되게 하였다.
비동기 적이고 각 파일이 각각 역활만을 하는 서버 구현을 위해 , 전체적으로 부분을 나누었다.
하단에 코드랑 같이 자세히 설명하겠다. ( repository 는 단순 Read 작업이므로 생략 )
@Controller('auth/register')
export class CreateLocalUserController {
constructor(private readonly commandBus: CommandBus) {}
/**
* 사용자 회원가입 기능
*
* Body 를 통해 받은 createUserProps( email , nickname , name , password )를 통해 User 를 만든다.
*
* @tag user
* @param createUserProps
* @returns
*/
@TypedRoute.Post()
async create(
@TypedBody() createLocalUserProps: CreateLocalUserProps,
): Promise<
| ResponseBase<{ id: string }>
| EmailAlreadyExistError
| NicknameAlreadyExistError
> {
const command = new CreateLocalUserCommand(createLocalUserProps);
const result: Result<
string,
EmailAlreadyExistError | NicknameAlreadyExistError
> = await this.commandBus.execute(command);
return match(result, {
Ok: (id: string) => new ResponseBase({ id }),
Err: (error: EmailAlreadyExistError | NicknameAlreadyExistError) => {
throw error;
},
});
}
}
@CommandHandler(CreateLocalUserCommand)
export class CreateUserCommandHandler implements ICommandHandler {
constructor(
@Inject(USER_REPOSITORY)
private readonly userRepository: UserRepositoryPort,
private readonly eventEmitter: EventEmitter2,
) {}
async execute(
command: CreateLocalUserCommand,
): Promise<
Result<string, EmailAlreadyExistError | NicknameAlreadyExistError>
> {
const { email, nickname } = command;
if (await this.userRepository.findByEmail(email))
return Err(new EmailAlreadyExistError());
if (await this.userRepository.findByNickname(nickname))
return Err(new NicknameAlreadyExistError());
const user = UserEntity.create(command);
await user.publishEvents(this.eventEmitter);
return Ok(user.id);
}
}
export class UserEntity extends AggregateRoot<CreateLocalUserProps> {
protected readonly _id: AggregateId;
static create(createProps: CreateLocalUserProps): UserEntity {
const id = randomId();
createProps.password = hashingPassword(createProps.password);
const user = new UserEntity({ id, props: createProps });
const vertificationRedirectCode = randomCode();
user.addEvent(
new SendVertificationEmailDomainEvent({
aggregatedId: id,
email: createProps.email,
nickname: createProps.nickname,
redirectCode: vertificationRedirectCode,
}),
);
user.addEvent(
new SaveTemporalRegisterDataDomainEvent({
aggregatedId: id,
data: createProps,
key: vertificationRedirectCode,
}),
);
return user;
}
@Injectable()
export class SendVertificationEmailEventListener {
constructor(@Inject(PRODUCER) private readonly producer: Producer) {}
@OnEvent(SendVertificationEmailDomainEvent.name)
handleSendVertificationEmailEvent(event: SendVertificationEmailDomainEvent) {
const result = this.producer.send({
messages: [
{
value: JSON.stringify({
email: event.email,
code: event.redirectCode,
nickname: event.nickname,
}),
},
],
topic: SEND_VERTIFICATION_EMAIL,
acks: 1,
});
result.then((data) => data);
result.catch((err) => err);
}
}
import { Injectable, Logger } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { SaveTemporalRegisterDataDomainEvent } from '../domain/events/save-temporal-register-data.domain-event';
import { RedisProvider } from '@src/providers/redis.provider';
@Injectable()
export class SaveTemporalRegisterDataEventListener {
private readonly logger = new Logger(
SaveTemporalRegisterDataDomainEvent.name,
);
constructor(private readonly redis: RedisProvider) {}
@OnEvent(SaveTemporalRegisterDataDomainEvent.name)
handleSaveTemporalRegisterDataEvent(
event: SaveTemporalRegisterDataDomainEvent,
) {
const key = `temporalRegister : ${event.key}`;
this.redis.saveData(key, JSON.stringify(event.data));
}
}
@Controller()
export class SendVertificationEmailMessageController {
constructor(private readonly emailerProvider: EmailerProvider) {}
@MessagePattern(SEND_VERTIFICATION_EMAIL)
async execute(@Payload() payload: SendVertificationPayload) {
this.emailerProvider.sendVertification(payload);
}
}
async execute(@Payload() payload: SendVerificationPayload) {
try {
this.emailerProvider.sendVerification(payload);
} catch (err) {
console.error(err);
if (this.count < 3) {
this.count++;
this.execute(payload);
}
}
}