๐Ÿ“š[Spring] ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜

ํ…ํ…ยท2025๋…„ 7์›” 14์ผ

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜์—์„œ์˜ ๋ฌด๊ฒฐ์„ฑ ๋ณด์™„ - ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜

์‚ฌ์šฉ์ž ์ €์žฅ ์‹œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด์™€ ์ผ์น˜ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง„ ์กฐํšŒ์šฉ ํ”„๋กœํ•„ ๋ฐ์ดํ„ฐ๋„ ํ•จ๊ป˜ ์ €์žฅํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ž.
์ด ๋‘ ๋ฐ์ดํ„ฐ๋Š” ๊ฐ๊ฐ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ”์— ์กด์žฌํ•˜์ง€๋งŒ ๊ฐ™์€ ์ •๋ณด๋ฅผ ๊ณต์œ ํ•˜๋ฏ€๋กœ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅ๋ฐ›์•„์•ผ ํ•œ๋‹ค.


๋ชจ๋†€๋ฆฌ์‹ ์•„ํ‚คํ…์ฒ˜์—์„œ์˜ ์›์ž์„ฑ ๋ณด์žฅ

์ผ๋ฐ˜์ ์ธ ๋ชจ๋†€๋ฆฌ์‹ ์•„ํ‚คํ…์ฒ˜(monolithic architecture) ์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‘ ๊ฐœ์˜ ๋ ˆํฌ์ง€ํ„ฐ๋ฆฌ๋ฅผ ์„œ๋น„์Šค์— ์ฃผ์ž…ํ•˜๊ณ 
๋‹จ์ผ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ฒ˜๋ฆฌํ•จ์œผ๋กœ์จ ์›์ž์„ฑ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.

@Transactional
public CreateUserResponse createUserAndProfile(CreateRequest request){
	String encodedPassword = passwordEncoder.encode(request.getPassword());
	User user = new User(request.getEmail(), request.getName(), encodedPassword);
	Profile profile = new Profile(request.getEmail, request.getName());
	
	userRepository.save(user);
	profileRepository.save(profile);
    ...       
}

์œ„์˜ createUserAndProfile() ๋ฉ”์„œ๋“œ๋Š” @Transactional ์–ด๋…ธํ…Œ์ด์…˜์œผ๋กœ ํŠธ๋žœ์žญ์…˜์ด ๋ฌถ์—ฌ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ค‘๊ฐ„์— ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ์ „์ฒด ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋˜์–ด ๋ฐ์ดํ„ฐ์˜ ์›์ž์„ฑ๊ณผ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅํ•  ์ˆ˜ ์žˆ๋‹ค.


์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์ฒ˜๋ฆฌ ์‹œ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ๋ฌธ์ œ

ํ•˜์ง€๋งŒ ๋‘ ๋ฐ์ดํ„ฐ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์—์„œ ์ฒ˜๋ฆฌ๋  ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ์˜ ์›์ž์„ฑ์„ ๋ณด์žฅ๋ฐ›์„ ์ˆ˜ ์—†๋‹ค.
๋„๋ฉ”์ธ ๊ฐ„ ์˜์กด๋„๋ฅผ ๋‚ฎ์ถ”๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ์ž ์ €์žฅ ์ดํ›„ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•˜์—ฌ ํ”„๋กœํ•„ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ•ด๋ณด์ž.

@Transactional
public CreateUserResponse signup(SignupRequest request) {
	String encodedPassword = passwordEncoder.encode(request.getPassword());
	User user = new User(request.getEmail(), request.getName(), encodedPassword);
	User savedUser = userRepository.save(user);

	//ํšŒ์›๊ฐ€์ž… ์ด๋ฒคํŠธ ๋ฐœํ–‰
	eventPublisher.publishEvent(
		UserRegisteredEvent.of(savedUser.getUserId(), savedUser.getEmail(),
			savedUser.getName())
	);
	
    ...
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handlerUserRegisteredEvent(UserRegisteredEvent event) {
    Profile profile = Profile.fromUserEvent(
		event.getUserId(), 
        event.getEmail(), 
        event.getName()
    );	
    profileRepository.save(profile);
}

์œ„ ์ฝ”๋“œ์—์„œ @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) ์–ด๋…ธํ…Œ์ด์…˜์€ ํ•ด๋‹น ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœํ–‰๋œ ํŠธ๋žœ์žญ์…˜์ด ์ •์ƒ์ ์œผ๋กœ ์ปค๋ฐ‹๋œ ์ดํ›„์— ์ด๋ฒคํŠธ ๋กœ์ง์ด ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•œ๋‹ค.

๋”ฐ๋ผ์„œ ์‚ฌ์šฉ์ž ์ €์žฅ(User)์€ ์ปค๋ฐ‹์ด ์™„๋ฃŒ๋œ ์ƒํƒœ์ด๋ฏ€๋กœ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ”„๋กœํ•„ ์ €์žฅ์ด ์‹œ์ž‘๋œ๋‹ค.
ํ•˜์ง€๋งŒ ์ด ํ›„์† ์ž‘์—… ์ค‘์— ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์ค‘ ๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜ ๋ฐœ์ƒ
  • profileRepository.save(profile) ์ˆ˜ํ–‰ ์ค‘ DB ์ œ์•ฝ์กฐ๊ฑด ์œ„๋ฐ˜
    ์ด๋Ÿฐ ๊ฒฝ์šฐ ํ”„๋กœํ•„ ์ €์žฅ์€ ์‹คํŒจํ•˜๊ณ  ์ด๋ฏธ ์ปค๋ฐ‹๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋Š” ๋˜๋Œ๋ฆด ์ˆ˜ ์—†๋‹ค.
    ๊ฒฐ๊ณผ์ ์œผ๋กœ User๋Š” ์ €์žฅ๋˜์—ˆ์ง€๋งŒ Profile์€ ์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋น„์ •์ƒ์ ์ธ ์ƒํƒœ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๊ณ  ์ด๋Š” ์‹œ์Šคํ…œ ๋ฐ์ดํ„ฐ์˜ ๋ฌด๊ฒฐ์„ฑ์„ ํ•ด์น  ์ˆ˜ ์žˆ๋‹ค.

BEFORE_COMMIT๊ณผ AFTER_COMMIT์˜ ์ฐจ์ด์™€ ์„ ํƒ ์ด์œ 

์‚ฌ์‹ค ํ•ด๋‹น ๊ตฌ์กฐ์—์„œ๋Š” @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) ์œผ๋กœ ์„ค์ •ํ•˜๋ฉด ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๊ฐ€ ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์ „์— ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋กœ์ง๋„ ๋™์ผํ•œ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด ์ด๋ก ์ ์œผ๋กœ๋Š” ๋ฐ์ดํ„ฐ ์›์ž์„ฑ ๋ณด์žฅ์ด ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.

ํ•˜์ง€๋งŒ ์‹ค๋ฌด์—์„œ๋Š” ์ด ๋ฐฉ์‹๋ณด๋‹ค AFTER_COMMIT ์ด ์„ ํ˜ธ๋˜๋Š”๋ฐ ๊ทธ ์ด์œ ๋Š” ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€ ์ด์œ ๊ฐ€ ์žˆ๋‹ค.

  1. ํŠธ๋žœ์žญ์…˜ ์ƒํƒœ์— ๋”ฐ๋ผ ์ด๋ฒคํŠธ๊ฐ€ ๋ฌด์‹œ๋˜๊ฑฐ๋‚˜ ์‹คํŒจํ•  ์ˆ˜ ์žˆ์Œ
    • BEFORE_COMMIT์€ ๋ฉ”์ธ ํŠธ๋žœ์žญ์…˜ ๋„์ค‘ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์‹คํ–‰๋˜์ง€ ์•Š๊ฑฐ๋‚˜ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์ค‘ ์˜ˆ์™ธ๊ฐ€ ํŠธ๋žœ์žญ์…˜ ์ „์ฒด์— ์˜ํ–ฅ์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค.
    • ์ด๋ฒคํŠธ ์ž์ฒด๋ฅผ ์‹คํŒจํ•ด๋„ ๋ฉ”์ธ ๋กœ์ง์—” ์˜ํ–ฅ์ด ์—†๋„๋ก ์„ค๊ณ„๋๋‹ค๋ฉด ๊ทธ ๋ชฉ์ ๊ณผ ์–ด๊ธ‹๋‚œ๋‹ค.
  2. ๋„๋ฉ”์ธ ๊ฐ„ ๊ฒฐํ•ฉ๋„๋ฅผ ๋†’์ด๋Š” ๊ฒฐ๊ณผ๊ฐ€ ๋จ
    • ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ๋Š” ๋ณธ์งˆ์ ์œผ๋กœ ํ›„์† ์ž‘์—…์„ ๋ถ„๋ฆฌํ•˜์—ฌ ๋А์Šจํ•œ ๊ฒฐํ•ฉ์„ ์ง€ํ–ฅํ•˜๋Š” ๊ฐœ๋…์ด๋‹ค.
    • BEFORE_COMMIT์€ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๊ฐ€ ๋ฉ”์ธ ํŠธ๋žœ์žญ์…˜์— ์–ฝํžˆ๊ฒŒ ๋˜๋ฏ€๋กœ ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋„๋ฉ”์ธ ๊ฐ„ ๊ฒฐํ•ฉ๋„๊ฐ€ ์ƒ๊ธด๋‹ค.

๋ฌด๊ฒฐ์„ฑ๋งŒ ๊ณ ๋ คํ•˜๋ฉด BEFORE_COMMIT๋„ ์œ ํšจํ•œ ์„ ํƒ์ผ ์ˆ˜ ์žˆ์ง€๋งŒ ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„ ๋ถ„๋ฆฌ, ๋„๋ฉ”์ธ ๊ฒฐํ•ฉ๋„ ์ตœ์†Œํ™”, ์‹คํŒจ ์‹œ ์œ ์—ฐํ•œ ๋ณต๊ตฌ๋ฅผ ๊ณ ๋ คํ•œ๋‹ค๋ฉด ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜์ด๋‚˜ Outbox ํŒจํ„ด์„ ํ†ตํ•œ ์ ‘๊ทผ์ด ๋” ์ ์ ˆํ•œ ์„ ํƒ์ผ ์ˆ˜ ์žˆ๋‹ค.


๋ณด์ƒ ํŠธ๋žœ์žญ์…˜(Compensating Transaction)

๋ณด์ƒ ํŠธ๋žœ์žญ์…˜์€ ๋ถ„์‚ฐ ์‹œ์Šคํ…œ๊ณผ ๊ฐ™์€
์›๋ž˜ ์ž‘์—…์„ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†๋Š” ํ™˜๊ฒฝ ์—์„œ ์‹คํŒจํ•œ ์ž‘์—…์— ๋Œ€ํ•ด ๋ฐ˜๋Œ€ ๋ฐฉํ–ฅ์˜ ๋™์ž‘์„ ์‹คํ–‰ํ•ด ์ƒํƒœ๋ฅผ ๋ณต๊ตฌํ•˜๋Š” ํŒจํ„ด์ด๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด ์‚ฌ์šฉ์ž ์ €์žฅ ํ›„ ํ”„๋กœํ•„ ์ €์žฅ์— ์‹คํŒจํ•œ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž ๋“ฑ๋ก์„ ์ทจ์†Œํ•˜๊ฑฐ๋‚˜ ๋ฌดํšจํ™”ํ•˜๋Š” ๋“ฑ์˜ ๋ณด์ƒ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ๋งž์ถ˜๋‹ค.


Outbox ํŒจํ„ด

Outbox ํŒจํ„ด์€ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ์—์„œ ์ด๋ฒคํŠธ ๋ฐœํ–‰๊ณผ ๋ฐ์ดํ„ฐ ์ €์žฅ์„ ๋™์ผํ•œ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ํŒจํ„ด์ด๋‹ค.

๋ฉ”์ธ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ๋„๋ฉ”์ธ ๋ฐ์ดํ„ฐ์™€ ํ•จ๊ป˜ ์ด๋ฒคํŠธ ๋ ˆ์ฝ”๋“œ(Outbox ํ…Œ์ด๋ธ”) ๋ฅผ ํ•จ๊ป˜ ์ €์žฅํ•˜๊ณ  ์Šค์ผ€์ค„๋Ÿฌ๋‚˜ Kafka ๋“ฑ ๋ณ„๋„์˜ ํ”„๋กœ์„ธ์„œ๊ฐ€ Outbox ํ…Œ์ด๋ธ”์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜์—ฌ ํ•ด๋‹น ์ด๋ฒคํŠธ๋ฅผ ๋ฉ”์„ธ์ง€ ํ(Message Queue)๋กœ ๋น„๋™๊ธฐ ์ „์†กํ•œ๋‹ค.
์ด๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ ์œ ์‹ค์„ ๋ฐฉ์ง€ํ•˜๊ณ  DB์— ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋งŒ ์ด๋ฒคํŠธ๋กœ ๋ฐœํ–‰๋˜๋„๋ก ๋ณด์žฅํ•ด ๋ฐ์ดํ„ฐ์™€ ์ด๋ฒคํŠธ ๊ฐ„ ์ •ํ•ฉ์„ฑ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ๋‹ค.


๋ณด์ƒ ํŠธ๋žœ์žญ์…˜์„ ํ™œ์šฉํ•œ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋ฌด๊ฒฐ์„ฑ ๋ณด์™„

๋‚˜๋Š” ์œ„์™€ ๊ฐ™์€ ๋ฌด๊ฒฐ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜ ๋ฐฉ์‹์„ ๋„์ž…ํ•˜์—ฌ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ๋ฆฌํŒฉํ† ๋งํ•˜์˜€๋‹ค.

@Transactional(propagation = Propagation.REQUIRES_NEW)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handlerUserRegisteredEvent(UserRegisteredEvent event) {
	try {
        Profile profile = Profile.fromUserEvent(
            event.getUserId(), 
            event.getEmail(), 
            event.getName()
        );	
        profileRepository.save(profile);
	} catch (Exception e) {
		log.info("ํ”„๋กœํ•„ ์ƒ์„ฑ ์‹คํŒจ: userId={}, error={}", event.getUserId(), e.getMessage());
		eventPublisher.publishEvent(UserSaveFailedEvent.of(event.getUserId()));
	}
}

์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋Š” try-catch๋ฅผ ํ†ตํ•ด ์˜ˆ์ƒ ๊ฐ€๋Šฅํ•œ ์˜ˆ์™ธ๋ฅผ ๋ฐฉ์–ดํ•˜๊ณ  ์‹คํŒจ ์‹œ UserSaveFailedEvent๋ผ๋Š” ๋ณด์ƒ ์ด๋ฒคํŠธ๋ฅผ
๋ฐœํ–‰ํ•˜์—ฌ ํ›„์†์กฐ์น˜๊ฐ€ ์ด์–ด์งˆ ์ˆ˜ ์žˆ๋„๋ก ์œ ์—ฐํ•˜๊ฒŒ ์„ค๊ณ„ํ–ˆ๋‹ค.

@EventListener
	public void handlerUserSaveFailedEvent(UserSaveFailedEvent event) {
		try {
			authRepository.deleteByAuthId(event.getAuthId());
		} catch (Exception e) {
			log.error("Auth ๋กค๋ฐฑ ์‹คํŒจ : authId={}", event.getAuthId());
		}
	}

๋ณด์ƒ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ์—์„œ๋Š” ์‚ฌ์šฉ์ž ๋“ฑ๋ก์„ ์ทจ์†Œํ•˜๋Š” ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜์—ฌ Profile ์ €์žฅ ์‹คํŒจ๋กœ ์ธํ•ด ๋ถˆ์™„์ „ํ•˜๊ฒŒ ๋‚จ์€ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ(User)๋ฅผ ์ œ๊ฑฐํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์ค‘ ์‹คํŒจ ์ƒํ™ฉ์„ ๊ฐ์ง€ํ•˜๊ณ  ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜์„ ๋ฐœํ–‰ํ•จ์œผ๋กœ์จ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜์˜ ์žฅ์ ์€ ์œ ์ง€ํ•˜๋ฉฐ ํŠธ๋žœ์žญ์…˜ ๋ถ„๋ฆฌ๋กœ ์ธํ•œ ๋ฌด๊ฒฐ์„ฑ ๋ฌธ์ œ๋ฅผ ํ˜„์‹ค์ ์ธ ๋ฐฉ์‹์œผ๋กœ ๋ณด์™„ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.


๋งˆ์น˜๋ฉฐ

์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์•„ํ‚คํ…์ฒ˜์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ๋ฌธ์ œ์™€ ๊ทธ ํ•ด๊ฒฐ์ฑ…์œผ๋กœ ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜ ํŒจํ„ด์„ ์‚ดํŽด๋ดค๋‹ค.
ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„๊ฐ€ ๋ถ„๋ฆฌ๋œ ํ™˜๊ฒฝ์—์„œ๋„ ์•ˆ์ •์ ์œผ๋กœ ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„  ์‹คํŒจ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ์ ์ ˆํžˆ ๋ณต๊ตฌํ•˜๋Š” ์„ค๊ณ„๊ฐ€ ๋ฐ˜๋“œ์‹œ ํ•„์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐ๋œ๋‹ค.
๋‹ค์Œ์—๋Š” Outbox ํŒจํ„ด์„ ์‹ค์ œ ์ ์šฉํ•ด๋ณด๊ณ  ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜๊ณผ ์–ด๋–ป๊ฒŒ ์กฐํ•ฉํ• ์ง€ ๊ณ ๋ฏผํ•ด๋ณด๋Š” ๊ฑธ ๋ชฉํ‘œ๋กœ ํ•˜๊ฒ ๋‹ค.

profile
์ฐจ๊ทผ์ฐจ๊ทผ

0๊ฐœ์˜ ๋Œ“๊ธ€