๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

ํ”„๋กœ์ ํŠธ/Airbnb Clone

[๋กœ๊ทธ์ธ ๋ฐ ํšŒ์›๊ฐ€์ž…] ์‚ฌ์šฉ์ž ํŽธ์˜์„ฑ์„ ๊ณ ๋ คํ•œ ์ด๋ฉ”์ผ ์ธ์ฆ ๊ธฐ๋ฐ˜ ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… ์†Œ๊ฐœ

์š”๊ตฌ์‚ฌํ•ญ ์†Œ๊ฐœ

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

 

๋„คํŠธ์›ŒํŠธ๋ฅผ ํƒ€๊ธฐ ๋•Œ๋ฌธ์— ๋น„๋™๊ธฐ๋กœ ๋™์ž‘ํ•˜๋„๋ก ๋ฐ”๊พผ๋‹ค๋ฉด, ์„œ๋ฒ„ ์„ฑ๋Šฅ์ด ์ข‹์•„์งˆ ์ˆ˜ ์žˆ๋Š” ์š”์†Œ๋กœ ์ถ”ํ›„์— ๋ฐฐํฌํ•œ ์„œ๋ฒ„์—์„œ ํ…Œ์ŠคํŠธ ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.