์๊ตฌ์ฌํญ ์๊ฐ
Airbnb ์ฌ์ดํธ์์ ๋ก๊ทธ์ธํ๊ธฐ ์ํด ์ด๋ฉ์ผ์ ์ ๋ ฅํ์ ๋
- ์ด๋ฉ์ผ์ด ์กด์ฌํ๋ค๋ฉด ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํ๋ ์ฐฝ์ ๋์์ฃผ๊ณ ,
- ์ด๋ฉ์ผ์ด ์กด์ฌํ์ง ์๋๋ค๋ฉด ํด๋น ์ด๋ฉ์ผ์ ๊ฐ์ง๊ณ ํ์๊ฐ์ ์ ํ๋๋ก ์งํ์ํต๋๋ค.
๊ณ ๋ฏผ ํฌ์ธํธ
1. A์ฌ์ฉ์์ B์ฌ์ฉ์๊ฐ ๋์์ ๊ฐ์ ์ด๋ฉ์ผ๋ก ํ์๊ฐ์ ์ ํ ๋, ๋๊ตฌ์ ์ด๋ฉ์ผ์ ์ ํจํ๊ฒ ๋ณผ ๊ฒ์ธ๊ฐ?
์ฒ์์๋ ๋ก๊ทธ์ธํ๊ธฐ ์ํด ์ด๋ฉ์ผ์ ๋จผ์ ์ ๋ ฅํ ์ฌ์ฉ์๋ง ์ฒ๋ฆฌ๋์ผ ํ๋ค ์๊ฐํ์ต๋๋ค.
๊ทธ๋์ ๋์์ ์ด๋ฉ์ผ์ ์ ๋ ฅํ์ ๋์ ์ผ์ด์ค๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์ฑ๊ธ ์ค๋ ๋์ธ Redis ์ ๋จผ์ ๋ค์ด์จ ์ด๋ฉ์ผ์ ์ ์ฅ ํ ์ ์์ง๋ง, ์ดํ์ ์์ฒญํ ์ฌ์ฉ์ ์ ์ฅ์์๋ ํ์๊ฐ์ ํ์ง๋ ์์๋๋ฐ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํ๋ ์ฐฝ์ ๋์์ฃผ๋ ๊ฒ์ ์ฌ์ฉ์๋ก ํ์ฌ๊ธ ๋ถํธํ๋ค๊ณ ํ๋จํ์ต๋๋ค.
๊ทธ๋ฌ๋, ๋ฉ์ผ ์๋ฒ์ API๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๋์์ ๊ฐ์ ์ด๋ฉ์ผ๋ก ํ์๊ฐ์ ํ ๊ฒฝ์ฐ๋ฅผ ๊ณ ๋ คํ์ง ์์๋ ๋จ์ ์์์ต๋๋ค. ๋ฉ์ผ ์๋ฒ์๋ airbnb123@naver.com ์ด๋ ์ด๋ฉ์ผ์ ๋ํด ์ ํจํ ์ฌ์ฉ์๊ฐ ๋จ 1๋ช ๋ง์ด ์กด์ฌํ๊ธฐ ๋๋ฌธ์, ๋ ์ฌ์ฉ์๊ฐ ๊ฐ์ ์ด๋ฉ์ผ๋ก ํ์๊ฐ์ ์ ํ๋๋ผ๋ ๋จ 1๋ช ์ ์ฌ์ฉ์์๊ฒ๋ง ์ด๋ฉ์ผ ์ธ์ฆ๋ฒํธ๊ฐ ๋ฐ์ก๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
2. ์ด๋ฉ์ผ ์ธ์ฆ๋ฒํธ๋ฅผ Redis ์ ๋ฃ์ ๋, ํค-๊ฐ์ผ๋ก [์ด๋ฉ์ผ-UUID] ์ [UUID-์ด๋ฉ์ผ] ์ค ์ด๋ป๊ฒ ๋ฃ์ ๊ฒ์ธ๊ฐ?
๊ฒฐ๋ก : UUID-์ด๋ฉ์ผ๋ก ๋ฃ์์ต๋๋ค.
๋จผ์ , Redis ์ ์ด๋ฉ์ผ ์ธ์ฆ๋ฒํธ๋ฅผ ๋ฃ์ ์ด์ ๋ ์ด๋ฉ์ผ ์ธ์ฆ๋ฒํธ๋ ํ์๊ฐ์ ํ ๋๋ง ์ฌ์ฉ๋๋ ์ผํ์ฑ ๋ฐ์ดํฐ์ ๋๋ค.
๋ง์ฝ, AWS๋ฅผ ์ด์ฉํด ์๋ฒ ์ธํ๋ผ๋ฅผ ๊ตฌ์ถํ๋ค๋ฉด Boot ๋ public subnet ์, DB๋ private subnet ์ ๋์ฐ๊ฒ ๋ ๊ฒ์ ๋๋ค.
์ด ๋, ์ผํ์ฑ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฅธ IP์ฃผ์์ ๋คํธ์ํฌ๋ก ์ ์ก์ ํ๋ ๊ฒ ๋ณด๋ค public subnet ์ redis ์๋ฒ๋ฅผ ๋์ฐ๊ณ , ์ด ์๋ฒ๋ฅผ ์บ์DB๋ก ํ์ฉํ์ฌ ์ผํ์ฑ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ๋ค๋ฉด ๋คํธ์ํฌ ํธ๋ํฝ์ ์ค์ฌ ์ฑ๋ฅ์ ๋์ผ ์ ์์ ๊ฒ์ด๋ผ ํ๋จํ์ต๋๋ค.
UUID-์ด๋ฉ์ผ๋ก ์ ํํ ์ด์ ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
<์ํฉ>
์ฌ์ฉ์ A ๊ฐ gromit123@naver.com ์ผ๋ก ํ์๊ฐ์
์งํ.
์ฌ์ฉ์ B์ ์ ํจํ ์ด๋ฉ์ผ์ gromit124@naver.com ์ด์ง๋ง, ์คํ๋ก gromit123@naver.com ๋ก ํ์๊ฐ์
์งํ
<๋ฌธ์ >
Redis๋ ๊ฐ์ ํค์ ๋ํด ์์ฒญ์ด 2๋ฒ ๋ค์ด์ค๋ฉด, ๋ฆ๊ฒ ์์ฒญํ ๊ฐ์ด ์ ์ฉ๋ฉ๋๋ค.
์ฆ, [์ด๋ฉ์ผ-UUID] ๋ฐฉ์์ ์ฑํํ๋ฉด, ์ ์ํฉ์์ ์ ํจํ ์ด๋ฉ์ผ์ ๊ฐ์ง ์ฌ์ฉ์A๊ฐ ์ฌ์ฉ์B๋ก ์ธํด ์ธ์ฆ์ด ๋ถ๊ฐ๋๋ ๋ฌธ์ ๋ฐ์ํฉ๋๋ค.
<ํด๊ฒฐ>
๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ํค-๊ฐ์ผ๋ก UUID-์ด๋ฉ์ผ์ ์ฑํํ์ต๋๋ค..
<๋จ์ >
๊ทธ๋ฌ๋, ํ๋์ ์ด๋ฉ์ผ์ ์ธ์ฆ๋ฒํธ๋ฅผ ํธ์ถํ ๋๋ง๋ค ๋ ๋์ค์ ์๋ก์ด ๊ฐ์ด ์ถ๊ฐ๋ฉ๋๋ค.
<ํด๊ฒฐ>
Redis ์ UUID-์ด๋ฉ์ผ์ด ์ ์ฅ๋ ๋, ๋ง๋ฃ ์๊ฐ์ 5๋ถ์ผ๋ก ์ค์ ํ๊ณ
๊ฐ์ ์์ฒญ์ ๋ํด Redis ์ ๊ฐ์ด ๊ณ์ ์ถ๊ฐ๋๋ ๊ฒ์ ๋ง๊ธฐ ์ํด Rate Limit ์ ์ค์ ํ ์ ์์ง๋ง, ๊ตฌํํ์ง๋ ์์์ต๋๋ค.
3. ์ ํจํ์ง ์์ ์ด๋ฉ์ผ์ ์ธ์ฆ๋ฒํธ๋ฅผ ๋ณด๋ด๋ฉด ์๋ฒ์ ๋ถํ๊ฐ ์ปค์ง๋, ์ด๋ฉ์ผ ๊ฒ์ฆ์ ์ด๋๊น์ง ํ ๊ฒ์ธ๊ฐ?
- ๋ชจ๋ ์ด๋ฉ์ผ์ ์ธ์ฆ ๋ฉ์ผ์ ์ ์กํ์ง ์๊ฒ ํ์ฌ ์๋ฒ์ ๋คํธ์ํฌ ๋ถํ๋ฅผ ๋ฎ์ถฐ์ผ ํ๋ค๊ณ ์๊ฐํ์ต๋๋ค.
- Spring Validation ์ @Email ๋ก๋ ๊ฒ์ฆ์ด ๋ถ์กฑํ๋ค๊ณ ํ๋จํ์ต๋๋ค.
- ๊ทธ๋์, Request(Dto) ์ ์ด๋ฉ์ผ์ ์ ๊ทํํ์์ ์ถ๊ฐํ์ต๋๋ค.
์ด๋ฉ์ผ์ '๋ก์ปฌ@๋๋ฉ์ธ' ์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค.
์๋ฅผ ๋ค์ด, gromit123@naver.com ์์ gromit123 ์ ๋ก์ปฌ, naver.com ์ ๋๋ฉ์ธ์ด๋ผ ๋ถ๋ฆ ๋๋ค. (๋ง์ฝ, co.kr ๊ฐ์ ๋๋ฉ์ธ์ผ ๊ฒฝ์ฐ .kr ์ ์ต์์ ๋๋ฉ์ธ์ด๋ผ ๋ถ๋ฆ)
[๊ฒ์ฆ ์๊ฐ]
๋ก์ปฌ์ @๋ฅผ ์ ์ธํ ์๋ฌด ๊ธฐํธ,์ซ์,๋ฌธ์ ํ์ฉ
๋๋ฉ์ธ์ @๋ค์ - ์ ์ ์ธํ ์ํ๋ฒณ/์ซ์๊ฐ 1๊ฐ์ด์ ํฌํจํด์ผ ํ๊ณ , ๋ฐ๋ณต๋ ์ ์์ง๋ง ๋๋ฉ์ธ์ ๋์ ์ํ๋ฒณ 2์๋ฆฌ ์ด์์ผ๋ก ๋๋์ผ ํ๋ ์ ๊ทํํ์์ ์ค์ ํ์ต๋๋ค.
/**
* [^-] : @ ๋ค์ - ๋ก ์์ํ์ง ์์
* [A-Za-z0-9-]+ : ์ํ๋ฒณ, ์ซ์๊ฐ 1๊ฐ ์ด์ ํฌํจ๋์ด์ผ ํจ
* (\.[A-Za-z0-9-]+)* : ์์ ํํ์์ด . ์ ํฌํจํด์ 0๋ฒ์ด์ ๋ฐ๋ณต๋ ์ ์์
* (\.[A-Za-z]{2,})$ : ๋์ ์ํ๋ฒณ 2์๋ฆฌ ์ด์์ผ๋ก ๋๋์ผ ํจ
*/
@Email(regexp = "^[^@]+@[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$")
@NotNull
String email
๋๋ฉ์ธ์ ์ ๊ทํํ์์ https://www.baeldung.com/java-email-validation-regex ์ 4. Strict Regular Expression Validation ๋ถ๋ถ์์ ๊ฐ์ ธ์์ต๋๋ค.
4. JavaMailSender ์ send ๋ฉ์๋๋ ๋๊ธฐ API ? ๋น๋๊ธฐ API? ๋ฉํฐ์ฐ๋ ๋์์ ์์ ํ์ง๋ฅผ ๊ณ ๋ฏผํ์ต๋๋ค.
- ๋๊ธฐ API ์ธ์ง ๋น๋๊ธฐ API ์ธ์ง์ ๋ฐ๋ผ, send ๋ค์์ ์๋ Redis ์ ๊ฐ์ ๋ฃ๋ ๋ก์ง์ ์์๊ฐ ๋ฌ๋ผ์ง๋๋ค.
- JavaMailSenderImpl ์ด ์คํ๋ง(๋ฉํฐ์ฐ๋ ๋) ํ๊ฒฝ์์ thread-safe ํ์ง ์์๋ณด์์ต๋๋ค.
import airdnb.be.exception.BusinessException;
import airdnb.be.exception.ErrorCode;
import airdnb.be.utils.RedisUtils;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import lombok.RequiredArgsConstructor;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor
@Service
public class EmailService {
private static final Long UUID_TTL = 5L;
private static final TimeUnit UUID_TTL_UNIT = TimeUnit.MINUTES;
private final Environment env;
private final JavaMailSender javaMailSender;
private final RedisUtils redisUtils;
public void sendAuthenticationEmail(String memberEmail) {
try {
String randomUUID = createRandomUUID();
SimpleMailMessage message = createSimpleMessage(memberEmail, randomUUID);
>>>> javaMailSender.send(message); <<< ๋น๋๊ธฐAPI / ๋๊ธฐAPI ?
redisUtils.addData(randomUUID, memberEmail, UUID_TTL, UUID_TTL_UNIT);
} catch (MailException ex) {
log.error("๋ฉ์ผ ์ ์ก ๋ถ๊ฐ : {}", ex.getMessage());
}
}
}
์๋ ๋งํฌ์์ ๋ฉ์ผ์ ๋ณด๋ด๋ send ๋ฉ์๋๋ ๋๊ธฐ API์ด๋ฉฐ, thread-safe ํ ๋ฉ์๋์์ ํ์ธํ์์ต๋๋ค. https://stackoverflow.com/questions/37777372/is-javamailsenderimpl-of-spring-thread-safe
๋คํธ์ํธ๋ฅผ ํ๊ธฐ ๋๋ฌธ์ ๋น๋๊ธฐ๋ก ๋์ํ๋๋ก ๋ฐ๊พผ๋ค๋ฉด, ์๋ฒ ์ฑ๋ฅ์ด ์ข์์ง ์ ์๋ ์์๋ก ์ถํ์ ๋ฐฐํฌํ ์๋ฒ์์ ํ ์คํธ ํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.