λͺ©μ°¨
λ°°κ²½
νμ¬ μ ν¬ μλΉμ€μμ ν¬ν/μ½μμ‘κΈ° λ±μ μμ±νλ©΄ μ¬λμΌλ‘ μλ¦Όμ 보λ΄μ£Όλ λ‘μ§μ΄ μμ΅λλ€. μ΄λ¬ν μλ¦Ό λ‘μ§μ μν΄ ν¬ν/μ½μμ‘κΈ° μλΉμ€ κ³μΈ΅μμλ μλ¦Ό μλΉμ€ κ³μΈ΅μ μμ‘΄νκ³ , μμ± μ μλ¦Όμ 보λ΄λ λ‘μ§κΉμ§ ν¬ν¨λμ΄ μμ΅λλ€. μ¦ ν¬ν/μ½μμ‘κΈ° μμ±, μ¬λ μλ¦Όμ΄ νλμ λΉμ¦λμ€ λ‘μ§ λ΄μ μλ κ²μ΄μ£ . μ΄λ λ°κΏλ§νλ©΄ μμ‘΄λκ° λλ€κ³ ν μ μμ΅λλ€. νμ¬μ λ‘μ§μμ λ°μν μ μλ λ¬Έμ μ κ³Ό ν΄κ²° λ°©λ²μΌλ‘ μ¬μ©ν μ€νλ§ μ΄λ²€νΈ λ°©μμ λμ μ리λ₯Ό μμλ³΄κ² μ΅λλ€.
λ¬Έμ μ
νμ¬ ν¬νμ μμ± λ‘μ§μ λ¨μννλ©΄ λ€μκ³Ό κ°μ΅λλ€.
@Transactional
public PollResponse createPoll(String teamCode, Long memberId, PollCreateRequest request) {
Poll poll = request.toPoll(teamCode, memberId, systemTime.now());
Poll savedPoll = pollRepository.save(poll);
notificationService.notifyPollOpen(teamCode);
return PollResponse.from(memberId, savedPoll);
}
Java
볡μ¬
ν¬ν μμ± save(poll)κ³Ό μλ¦ΌnotifyPollOpen(teamCode)μ΄ νλμ λ‘μ§μΌλ‘ λ¬Άμ¬μλλ°μ, μ΄λ κ² λ€λ₯Έ 컨ν
μ€νΈκ° μλ‘ μμ‘΄λμ΄ μλ μν©μ κ°νκ² κ²°ν©λμ΄ μλ€κ³ ν μ μκ³ κ²°ν©λκ° λμ μμ λ‘μ§μ ν¬κ² μΈ κ°μ§μ λ¬Έμ μ μ κ°μ§λλ€.
1.
μ μ§, 보μμ μ΄λ €μ
μλ¦Ό μλΉμ€μ λ€μ΄λ°μ΄λ μκ·Έλμ³κ° λ°λλ©΄ ν¬ν μλΉμ€, μ½μμ‘κΈ° μλΉμ€ λ±μμ ν¨κ» λ³κ²½μ΄ λ°μνκ² λ©λλ€. ν¬ν μλΉμ€ μ
μ₯μμλ ν¬ν μμ±μ΄λΌλ μ£Ό κ΄μ¬μ¬κ° μλ μλ¦Ό μλΉμ€ νΈμΆ λ‘μ§μ΄ μ‘΄μ¬νκΈ°λλ¬Έμ κ°λ
μ± μΈ‘λ©΄μμλ λΆνΈν¨μ΄ μμ΅λλ€.
2.
μ½νμλ νΈλμμ
μλ¦Ό μλΉμ€μμ μμΈκ° λ°μνκ² λμμ λ, μλ¦Ό μλΉμ€μ νΈλμμ
μ΄ REQUIRED λΌλ©΄ μ μμ±λ ν¬νμ λν΄μλ λ‘€λ°±μ΄ λλ μν©μ΄ λ°μν©λλ€.
μ΄λ₯Ό νΌνκΈ° μν΄ μλ¦Ό λ‘μ§μμ REQUIRES_NEW λ‘ μ€μ νμ¬ μλ¦Ό μλΉμ€μμ μμΈ μ νκ° λμ§ μλλ‘ λ‘μ§μ ꡬμ±νλ λ°©λ²μ΄ μκΈ΄ νμ§λ§, REQUIRES_NEW λ 컀λ₯μ
μ μλ‘ κ°μ Έμ νΈλμμ
μ μλ‘ μμ±ν΄μΌ νκΈ°λλ¬Έμ, μμ² μ²λ¦¬ μκ°μ΄ κΈΈμ΄μ§ μλ°μ μμ΅λλ€.
λ§μ½ μλ¦Όμμ μ€ν¨ν μμ ν¬ν μμ±λ μ€ν¨ν΄μΌ νλ€λ λΉμ¦λμ€ μ μ±
μ΄ μλ€λ©΄ λ λ‘μ§μ λμΌν νΈλμμ
μ νμΌνμ§λ§, μ ν¬λ ν¬ν μμ±κ³Ό μλ¦Όμ κ°λ
μ λ€λ₯Έ κΈ°λ₯μ΄λΌκ³ νλ¨νμ¬ λ κΈ°λ₯ μ¬μ΄ νΈλμμ
μ΄ μ½νμμ§ μκ³ λ
립λ λ κ°μ νΈλμμ
μ΄ κ΅¬μ±λμ΄μΌ νλ€κ³ νλ¨νμμ΅λλ€.
3.
λΆνμν λκΈ°
μμΈκ° λ°μνμ§ μλλ€κ³ νλλΌλ, μ€νλ§μμ μμ²μ κΈ°λ³Έμ μΌλ‘ λ¨μΌ μ°λ λμμ λμνκ² λκ³ νλμ μ°λ λ λ΄λΆμ μ½λλ λκΈ°μ μΌλ‘ μ²λ¦¬λ©λλ€. νμ¬ μ½λλ ν¬ν μμ± λ‘μ§κ³Ό μλ¦Ό λ‘μ§μ΄ λͺ¨λ μλ£κ° λμ΄μΌ μλ΅ν©λλ€. μ΄ λ μλ¦Ό λ‘μ§μ΄ 무거μ μ€λ κ±Έλ¦°λ€λ©΄, ν¬ν μμ±κ³Ό λ³κ°μΈ λ‘μ§μΌλ‘ μΈν΄ ν΅μ¬ λ‘μ§μ λν μλ΅μ΄ λκΈ°νκ² λλ μν©μ΄ λ°μν©λλ€.
μκ°ν΄λ³Έ λμ
μ€νλ§ λ°°μΉ
β’
μ₯μ
μμ μΈκΈν λ¬Έμ μ λ€μ λͺ¨λ ν΄κ²°ν μ μμ΅λλ€. μλ¦Όμ λν λͺ¨λ λ‘μ§μ λ°°μΉ λ‘μ§μΌλ‘ λμκ°κΈ°λλ¬Έμ, ν¬ν μμ± λ‘μ§μμ μλ¦Όμ λν 물리μ , λ
Όλ¦¬μ μμ‘΄μ±μ λͺ¨λ λΌμ΄λΌ μ μμ΅λλ€. μ΄μ λ°λΌ ν¬ν μμ± λ‘μ§μμλ ν΅μ¬ λ‘μ§λ§ μ²λ¦¬νμ¬ λΆνμν λκΈ°λ₯Ό μ€μΌ μ μμΌλ©° λ λ‘μ§μ μμ ν λ
립λ νΈλμμ
μμ λμνκ² λ©λλ€. μ΄λ¬ν μ₯μ κ³Ό λλΆμ΄ λ°°μΉλ§μ μ₯μ μ΄ μλλ°μ, μμ ν λ
립λ λ‘μ§μΌλ‘ ꡬμ±λμ΄ μκΈ°λλ¬Έμ μλ¦Όμμ λ°μνλ μμΈμ λν΄ μμ μ μΌλ‘ μ²λ¦¬ν μ μκ³ , μ€ν¨ν μλ¦Όμ λν΄ μΆν μ¬μ²λ¦¬κ° κ°λ₯νλ€λ μ μ
λλ€.
β’
λ¨μ
μ무λλ λκ·λͺ¨ μ²λ¦¬λ₯Ό μν μμ
μ΄λ€λ³΄λ, μ€μΌμ€μ΄ μμ£Ό λμ§ μμ μ€μκ°μ±μ΄ λΆμ‘±ν©λλ€. λν λ°°μΉ μμ
μ μν΄ μ»¬λΌ, ν
μ΄λΈ λ± λΆκ°μ μΈ λ°μ΄ν° μ 보λ₯Ό DB μ μ μ₯ν΄μΌ ν©λλ€.
μ°λ λ μμ±
β’
μ₯μ
μ°λ λλ₯Ό μμ±νμ¬ λ€λ₯Έ μμμ μΆκ° μμ΄ μ½κ² μ μ©μ΄ κ°λ₯ν©λλ€.
private void notifyPollOpen(String teamCode) {
Thread thread = new Thread(() -> notificationService.notifyTeamPollOpen(teamCode));
thread.start();
}
Java
볡μ¬
λν λΉλκΈ°μ μΌλ‘ μ²λ¦¬κ° κ°λ₯νμ¬ λ©μΈ λ‘μ§μΈ ν¬ν μμ± λ‘μ§μ λΆνμν λκΈ°λ₯Ό μ€μΌ μ μμΌλ©°, λ©μΈ λ‘μ§κ³Ό λ€λ₯Έ μ°λ λμ΄κΈ°λλ¬Έμ λ
립λ νΈλμμ
μΌλ‘ λ‘μ§μ΄ μ§νλ©λλ€.
β’
λ¨μ
μλ¦Ό λ‘μ§μ μ§μ μ°λ λλ‘ μμ±νμ¬ μ§ννλ€λ³΄λ, μμ±ν μ°λ λ λ΄λΆμμ κ²°κ΅ μλ¦Ό λλ©μΈμ λν μμ‘΄μ±μ΄ μκΈΈ μλ°μ μμ΅λλ€. λν μλ¦Ό λ‘μ§μμ λ°μνλ μμΈμ λν΄ μ²λ¦¬κ° νλ€λ€λ λ¨μ μ΄ μμ΅λλ€. μλΉμ€ κ³μΈ΅ λ΄λΆμμ μλ‘ μμ±ν μ°λ λμ΄κΈ°λλ¬Έμ λμ€ν¨μ² μλΈλ¦Ώμ νμ§ μμ 컨νΈλ‘€λ¬ μ΄λλ°μ΄μ€μ κ°μ spring μ΄ μ 곡ν΄μ£Όλ μμΈ μ²λ¦¬ κΈ°λ₯μ μ΄μ©ν μ μμ΅λλ€. λ§μ½ μ»€λ° μμ μμ ν¬ν μμ± λ‘μ§μ΄ λ‘€λ°±λλ€λ©΄, μλ¦Ό λ‘μ§λ μ€νλμ§ μμμΌ νλλ° μ΄λ¬ν λΆλΆμ λν΄ κ΄λ¦¬νκΈ°κ° μ΄λ ΅μ΅λλ€.
μ€νλ§ μ΄λ²€νΈ λ°©μ
μ΄λ²€νΈλ νλ‘κ·Έλ¨μ μν΄ κ°μ§λκ³ μ²λ¦¬λ μ μλ λμμ΄λ μ¬κ±΄μ λ§νλλ°, μΌλ°μ μΌλ‘ μ΄λ²€νΈ κΈ°λ° μμ€ν
μ νλ‘κ·Έλ¨μμ μ²λ¦¬ν΄μΌ ν μΈλΆ νλμ΄ μμ λ μ¬μ©ν©λλ€. μ¬κΈ°μ νλ‘κ·Έλ¨μ μν΄ κ°μ§λλ μ¬κ±΄μ βν¬ν μμ±β μ΄κ³ , μ²λ¦¬ν΄μΌ ν μΈλΆ νλμ βμλ¦Ό μλΉμ€β λ‘ λ³Ό μ μμ΅λλ€. μ€νλ§μμλ μ€νλ§ μ΄λ²€νΈλ₯Ό μ§μνκ³ μμ΅λλ€.
β’
μ₯μ
μΈλΆ 리μμ€μμ΄ κ°νΈνκ² μ΄λ²€νΈ λ°©μμ ꡬνν μ μμ΅λλ€. λν λκΈ°/λΉλκΈ° λͺ¨λ μ§μ κ°λ₯νκ³ νΈλμμ
μ»€λ° μ±κ³΅ μ¬λΆμ λ°λΌ μλ¦Ό λ‘μ§ μ€ν μ¬λΆλ₯Ό λ°μ§ μ μμ΅λλ€. μ¦ μ°λ λλ₯Ό μ§μ μμ±νλ κ²λ³΄λ€ λ μ¬μΈνκ² λ‘μ§μ λ€λ£° μ μμ΅λλ€.
β’
λ¨μ
μ°λ λ μμ±κ³Ό λ§μ°¬κ°μ§λ‘ μμΈ λ°μμ λν μ¬μ²λ¦¬κ° νλλλ€. λν AOP λ± λ³΅μ‘ν λ΄λΆ λμ ꡬ쑰λ₯Ό νμ
ν΄μΌ ν νμκ° μμ΅λλ€.
μ ν¬λ μμ μΈ κ°μ§ μ€ μ€νλ§ μ΄λ²€νΈλ₯Ό μ μ©νκΈ°λ‘ νμ΅λλ€. μ΄μ λ λ€μκ³Ό κ°μ΅λλ€.
μ€νλ§ μ΄λ²€νΈ μ ν μ΄μ
1.
μ€μκ°μ±μ΄ 보μ₯λμ΄μΌ νλ€.
μ ν¬ μλΉμ€μ λΉμ¦λμ€ μ μ±
μ ν¬νκ° μμ±λλ©΄ λμμ μ¬λ μλ¦Όμ 보λ΄μΌ ν©λλ€. μ΄μ λ°λΌ λ°°μΉ λ°©μμ μ€μκ°μ±μ΄ λΆμ‘±νλ€κ³ νλ¨νμ¬ λ°°μ νμμ΅λλ€.
2.
μμμ±μ΄ 보μ₯λμ΄μΌ νλ€.
ν¬ν μμ±μ΄ μ±κ³΅νλ©΄ μ¬λ μλ¦Όμ΄ λ³΄λ΄μ ΈμΌ νκ³ , ν¬ν μμ±μ μ€ν¨νλ©΄ μ¬λ μλ¦Ό λ‘μ§μ΄ νΈμΆλμ§ μμμΌ ν©λλ€. νμ§λ§ μ°λ λ μμ± λ°©μμ ν¬ν μμ± μ»€λ° μμ μ΄μ μ μ¬λ μλ¦Ό λ‘μ§μ μ€νμν€κΈ°λλ¬Έμ μμμ±μ΄ μ§μΌμ§μ§ μμ μ°λ €κ° μμ κ²μ΄λΌκ³ νλ¨νμμ΅λλ€.
3.
κ΄μ¬μ¬κ° λΆλ¦¬λμ΄μΌ νλ€.
μ΄λ²€νΈ λ°©μμ μ¬μ©νλ©΄ μλ¦Ό λλ©μΈμ λν 물리μ μμ‘΄μ±μ΄ λΆλ¦¬κ° λ μ μμ΅λλ€. λΉλ‘ μ€νλ§ μ΄λ²€νΈ λΌλ λΌμ΄λΈλ¬λ¦¬μ μμ‘΄μ±μ΄ μκΈ°κΈ΄ νμ§λ§ μλ¦Ό λλ©μΈμ λν΄ μμ‘΄μ±μ λΆλ¦¬νλ©΄μ κ°μ Έμ¬ μ μλ μ μ§/보μμ ν¨μ¨μ΄ λ μ’μ κ²μ΄λΌκ³ μκ°νμμ΅λλ€.
μ΄μ μ€νλ§ μ΄λ²€νΈμ μ¬μ©λ²κ³Ό ν¨κ», μ€νλ§ μ΄λ²€νΈλ₯Ό μ μ©νλ κ³Όμ μμ λ§λ¬λ μ νμ§λ€κ³Ό μ ν μ΄μ μ λν΄ μμλ³΄κ² μ΅λλ€.
λ°ν
λ°νμ νλ‘κ·Έλ¨μ μν΄ κ°μ§λλ μ¬κ±΄μ΄ λ°μνλ€λ κ²μ μλ €μ€λλ€. λ°νν λ μ΄μ©νλ ν΄λμ€λ‘λ λνμ μΌλ‘ ApplicationEventPublisherμ AbstractAggregateRoot κ° μμ΅λλ€.
ApplicationEventPublisher
ApplicationEventPublisher λ μ€νλ§ λΉμΌλ‘ λ±λ‘λμ΄ μκΈ°λλ¬Έμ κ°μ²΄λ₯Ό μ£Όμ
λ°μ μ¬μ©ν μ μμ΅λλ€. μΌλ°μ μΌλ‘ μλΉμ€ κ³μΈ΅μμ ν΄λΉ κ°μ²΄λ₯Ό μ£Όμ
λ°μ λ€, publishEvent(event) λ©μλλ₯Ό ν΅ν΄ μ΄λ²€νΈλ₯Ό λ°νν©λλ€.
@RequiredArgsConstructor
@Service
public class PollService {
private final ApplicationEventPublisher applicationEventPublisher;
@Transactional
public PollResponse createPoll(String teamCode, Long memberId, PollCreateRequest request) {
Poll poll = request.toPoll(teamCode, memberId, systemTime.now());
Poll savedPoll = pollRepository.save(poll);
applicationEventPublisher.publishEvent(PollEvent.from(teamCode));
return PollResponse.from(memberId, savedPoll);
}
Java
볡μ¬
μΈμλ‘ λ³΄λ΄μ§λ PollEvent κ°μ²΄λ POJO κ°μ²΄μ΄κ³ , Dto μ²λΌ μλ¦Ό λ‘μ§μμ μ¬μ©ν λ³μλ₯Ό κ°μ²΄μ νμ 보λ΄λ©΄ μ΄λ₯Ό μ²λ¦¬νλ λ‘μ§μμ λ°μ μ¬μ©ν©λλ€.
AbstractAggregateRoot
ν΄λμ€λͺ
μ Abstract κ° μμ§λ§ μΆμ ν΄λμ€λ μλκ³ , λλ©μΈμμ μ΄λ²€νΈλ₯Ό κ°λ¨νκ² μ²λ¦¬νκΈ° μν ν
νλ¦ΏμΌλ‘ λ³Ό μ μμ΅λλ€. λλ©μΈ μ£Όλ κ΄μ μμ 보μμ λ, λ£¨νΈ λλ©μΈμμ ν΄λΉ ν΄λμ€λ₯Ό μμλ°κ³ ν΄λΉ ν΄λμ€μ protected λ©μλμΈ registerEvent(event) λ₯Ό ν΅ν΄ μ΄λ²€νΈλ₯Ό λ±λ‘ν©λλ€.
public class Poll extends AbstractAggregateRoot<Poll> {
// ...
Poll(Long id, String teamCode) {
// ...
registerEvent(PollEvent.from(teamCode));
}
}
Java
볡μ¬
νμ§λ§ registerEventλ μ΄λ²€νΈλ₯Ό λ±λ‘λ§ ν λΏ, λ°ννμ§λ μμ΅λλ€. λμ λ°ννλ μμ
μ AOP λ°©μμΌλ‘ λ°ννλλ°μ, Spring Data JPA Repositoryμ save, saveAll, delete, deleteAllμ΄ νΈμΆλ λ μν°ν°μ μμ¬ μλ μ΄λ²€νΈλ₯Ό λͺ¨λ λ°νν©λλ€.
@RequiredArgsConstructor
@Service
public class PollService {
@Transactional
public PollResponse createPoll(String teamCode, Long memberId, PollCreateRequest request) {
Poll poll = request.toPoll(teamCode, memberId, systemTime.now());
return PollResponse.from(memberId, pollRepository.save(poll));
}
Java
볡μ¬
μ ν¬λ ApplicationEventPublisher λ₯Ό μ£Όμ
λ°λ λμ AbstractAggregateRoot λ₯Ό μμλ°μ μ¬μ©νλ κ²μΌλ‘ κ²°μ νμμ΅λλ€.
λμ μ°¨μ΄λ₯Ό κ΄μ¬μ¬ μΈ‘λ©΄μμ μκ°ν΄λ³Ό μ μμ΅λλ€. βν¬ν μμ±β μ΄λΌλ μ¬κ±΄μ΄ λ°μν κ²μ λλ©μΈμμ μμ±λ κ²μ΄κΈ°μ λλ©μΈμμ κ΄λ¦¬ν΄μΌ νλ κ΄μ¬μ¬μ
λλ€. μ¦ βν¬ν μμ±β μ΄λΌλ μ΄λ²€νΈλ μλΉμ€ κ³μΈ΅ ν¬ν μμ± λ‘μ§μ κ΄μ¬μ¬λ μλλΌκ³ μκ°νμ΅λλ€. μλΉμ€ κ³μΈ΅ ν¬ν μμ± λ‘μ§μ κ΄μ¬μ¬λ κ·Έμ μμ² νλΌλ―Έν°λ₯Ό ν¬ν POJO κ°μ²΄λ‘ λ°κΎΌ λ€ μ΄λ₯Ό μ μ₯νλ κ² λΏμ
λλ€. μ΄μ λ°λΌ βν¬ν μμ± μβμ 무μΈκ°λ₯Ό νλ€λ©΄ μ΄λ ν¬ν λλ©μΈ κ°μ²΄μ κ΄μ¬μ¬μ΄κΈ°μ μ΄λ²€νΈ λ°ν λ‘μ§μ λλ©μΈ κ°μ²΄μ λ£λκ² μ μ νλ€κ³ νλ¨νμμ΅λλ€.
λ°ν λμ μ리
AbstractAggregateRoot μ λ°ννλ κ³Όμ μ μ‘°κΈ λ μμΈν λ€μ¬λ€ λ³΄κ² μ΅λλ€. Spring Data JPA repository μ λ©μλ μ€ save, saveAll, delete, deleteAll μ΄ νΈμΆλλ©΄ AOP λ‘ λ±λ‘λ μΈν°μ
ν° μ€ EventPublishingMethodInterceptor μμ μ΄λ²€νΈ κ΄λ ¨ λ‘μ§μ μνν©λλ€.
class EventPublishingMethodInterceptor implements MethodInterceptor {
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = invocation.proceed();
if (!isEventPublishingMethod(invocation.getMethod())) {
return result;
}
// ...
eventMethod.publishEventsFrom(arguments[0], publisher);
return result;
}
private static boolean isEventPublishingMethod(Method method) {
return method.getParameterCount() == 1 //
&& (isSaveMethod(method.getName()) || isDeleteMethod(method.getName()));
}
private static boolean isSaveMethod(String methodName) {
return methodName.startsWith("save");
}
private static boolean isDeleteMethod(String methodName) {
return methodName.equals("delete") || methodName.equals("deleteAll") || methodName.equals("deleteInBatch")
|| methodName.equals("deleteAllInBatch");
}
}
Java
볡μ¬
isEventPublishingMethod μμ save λλ delete μΈμ§ νμΈνμ¬ λ§λ€λ©΄ publishEventsFrom λ₯Ό νΈμΆν©λλ€. publishEventsFrom λ©μλ λ΄λΆμμλ μμ μΈκΈν ApplicationEventPublisher μ publishEvent() λ₯Ό νΈμΆνκ² λ©λλ€.
κ²°κ΅ ApplicationEventPublisher λ₯Ό μ£Όμ
λ°μ μ¬μ©νλ λ‘μ§μ΄λ AbstractAggregateRoot μμ AOP λ₯Ό νμ©ν΄ μ¬μ©νλ λ‘μ§μ΄ ν κ³³μμ λ§λκ² λλ κ²μ
λλ€.
κ·Έλ λ€λ©΄ λ°νν μ΄λ²€νΈλ μ΄λμμ 보κ΄λμ΄ μ΄λ»κ² μ²λ¦¬λ κΉμ?
ApplicationEventPublisher μ publishEvent λ©μλλ AbstractApplicationContext μΆμ ν΄λμ€μ ꡬνλμ΄ μμ΅λλ€. publishEvent λ©μλ λ΄λΆμμ SimpleApplicationEventMulticaster λ₯Ό κ±°μ³ TransactionListener μ λ±λ‘μ μμ²νλ©΄ TransactionalApplicationListenerMethodAdapter μ onApplicationEvent λ©μλμμ λ±λ‘μ ν©λλ€.
public class TransactionalApplicationListenerMethodAdapter extends ApplicationListenerMethodAdapter
implements TransactionalApplicationListener<ApplicationEvent> {
// ...
public void onApplicationEvent(ApplicationEvent event) {
if (TransactionSynchronizationManager.isSynchronizationActive() &&
TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.registerSynchronization(
new TransactionalApplicationListenerSynchronization<>(event, this, this.callbacks));
}
}
}
Java
볡μ¬
TransactionSynchronizationManager λ νΈλμμ
μ λκΈ°νλ₯Ό λμμ£Όλ κ°μ²΄μΈλ°μ, ν΄λΉ λ©μλμ ThreadLocal μ Set μλ£κ΅¬μ‘°λ‘ μ΄λ£¨μ΄μ§ λ³μμ λ΄κΈ°λ©΄ λ°νμ΄ μλ£λ©λλ€.
Set μλ£ κ΅¬μ‘°μ νμ
μ TransactionSynchronization μΈλ° ν΄λΉ κ°μ²΄λ λ°νμ μΈμλ‘ λ³΄λΈ Dto μ±κ²©μ κ°μ²΄μ 리μ€λλ₯Ό ν¨κ» λ΄κ³ μμ΅λλ€. μ¬κΈ°μ λ΄κΈ΄ TransactionSynchronization κ°μ²΄λ μΆνμ ꡬλ
νλ κ°μ²΄μμ κ°μ Έμ μ¬μ©νκ² λλλ°μ, μ΄λ λ€μ μ±ν°μμ μμ보λλ‘ νκ² μ΅λλ€.
ꡬλ
ꡬλ
μ μ΄λ
Έν
μ΄μ
λ°©μμΌλ‘ κ°λ¨νκ² μ μ©μ΄ κ°λ₯νλ©°@EventListener,Β @TransactionalEventListenerΒ μ΄λ
Έν
μ΄μ
μΌλ‘ μ΄λ²€νΈ ꡬλ
μ ν μ μμ΅λλ€.
1.
@EventListener : μ΄λ²€νΈκ° λ°νλμμ λ νμ μ΄λ₯Ό μμ ν©λλ€.
2.
@TransactionalEventListener : μ΄λ²€νΈλ₯Ό λ°ννλ νΈλμμ
μ΄ μ¬λ°λ₯΄κ² μνλμμ λ μμ ν©λλ€.
@TransactionalEventListener μ ν μ΄μ
μ ν¬ νλ‘μ νΈμμλ @EventListener λμ @TransactionalEventListener λ₯Ό μ¬μ©νμλλ°μ, κ·Έ μ΄μ λ‘λ ν¬ν μμ± λ‘μ§μ΄ μ¬λ°λ₯΄κ² μνλμμ λ μ¬λ μλ¦Όμ 보λ΄λ κ²μ΄ λΉμ¦λμ€ μ μ±
μ΄μκΈ° λλ¬Έμ
λλ€. μ¦, λ§μ½ ν¬ν μμ± μλμ μ΄μ μ λμ΄, μ΄λ¬ν μλκ° μμλλ§λ€ μ±κ³΅ μ¬λΆμ κ΄κ³μμ΄ μ΄λ₯Ό ꡬλ
νκ³ μΆλ€λ©΄ @EventListener λ₯Ό μ¬μ©νμμ κ²μ
λλ€. νμ§λ§ ν¬νκ° μ¬λ°λ₯΄κ² μμ±λμμ λλ§ μ΄λ₯Ό ꡬλ
νκ³ μλ¦Όμ 보λ΄μΌ νκΈ°λλ¬Έμ @TransactionalEventListenerλ₯Ό μ¬μ©νμμ΅λλ€.
쑰건 μ€μ
β’
condition
νμ¬ μ ν¬ λͺ¨λ½ μλΉμ€ μ μ±
μ, μ¬λ μλ¦Όμ ν¬νλ₯Ό μμ±ν λ λΏλ§ μλλΌ ν¬ν μ’
λ£ μμλ 보λ
λλ€. μ΄λ¬ν λ‘μ§λ€μ μ²λ¦¬νκΈ° μν΄, μ΄λ²€νΈλ₯Ό λ±λ‘ν λ μΈμλ‘ λ³΄λ΄μ§λ μ΄λ²€νΈ POJO κ°μ²΄μ isClosed λ³μλ₯Ό μ€μ νκ³ μμ±λ κ²μΈμ§, μ’
λ£ν κ²μΈμ§ μλ¦Ό λ‘μ§ λ΄μμ λΆκΈ° μ²λ¦¬νμ¬ νμΈν μ μμ΅λλ€.
@Transactional
public class NotificationService {
// ...
@TransactionalEventListener
public void notifyPoll(PollEvent event) {
if (!event.isClosed()) {
// ν¬ν μμ± μλ¦Ό
return
}
// ν¬ν μ’
λ£ μλ¦Ό
}
}
Java
볡μ¬
νμ§λ§ μ΄νμ ν¬ν μ λͺ©μ΄ μμ λμμ λμλ μλ¦Όμ 보λ΄μΌνλ λΉμ¦λμ€ λ‘μ§μ΄ μΆκ°κ° λλ€λ©΄ if λΆκΈ°λ¬Έμ΄ μΆκ°λμ΄μΌ ν©λλ€. μ΄λ κ² νλμ λ©μλ λ΄μ κ³μ λΆκΈ°λ¬Έμ΄ μΆκ°κ° λλ€λ©΄ μ μ§/보μμλ μ’μ§ μκ³ κ°λ
μ±λ λ¨μ΄μ§κ² λ©λλ€. κ·Έλμ @TransactionalEventListener λ μ΄λ¬ν λ¬Έμ λ₯Ό progmmatic νκ² μ²λ¦¬ν μ μλλ‘ condition μ΅μ
μ΄ μμ΅λλ€.
@Transactional
public class NotificationService {
// ...
@TransactionalEventListener(condition = "#event.isClosed() == false")
public void notifyPollOpen(PollEvent event) {
// ν¬ν μμ± μλ¦Ό
}
@TransactionalEventListener(condition = "#event.isClosed() == true")
public void notifyPollClosed(PollEvent event) {
// ν¬ν μ’
λ£ μλ¦Ό
}
}
Java
볡μ¬
μ΄μ²λΌ condition μΌλ‘ λ‘μ§ μ€ν 쑰건μ μ€μ νλ©΄ λ©μλ λ¨μλ‘ λ‘μ§μ λλ μ μμ΄ μμ μΈκΈν μ μ§/보μ λ¬Έμ μ μ ν΄κ²°ν μ μμ΅λλ€. condition μ νμΈνλ μμ μ, μ»€λ° μ΄ν listener(ApplicationListenerMethodAdapter) μ processEvent κ° νΈμΆλλλ°, ν΄λΉ λ©μλ λ΄λΆμμ condition μ κΊΌλ΄μ΄ νμΈν©λλ€(ν΄λΉ λ‘μ§μ μννλ μμ€ μ½λ).
λ νκ°μ§ μ₯μ μ΄ μλλ°, λ§μ½ condition 쑰건 μμ΄ λ¨μν λ©μλλ§ λλκ³ ν΄λΉ λ©μλ λ΄μμ λΆκΈ°λ¬ΈμΌλ‘ μ²λ¦¬νλ€λ©΄ λ€μκ³Ό κ°μ΄ ꡬμ±ν μ μμ΅λλ€.
@Transactional
public class NotificationService {
// ...
@TransactionalEventListener
public void notifyPollOpen(PollEvent event) {
if (!event.isClosed()) {
// ν¬ν μμ± μλ¦Ό
}
}
@TransactionalEventListener
public void notifyPollClosed(PollEvent event) {
if (event.isClosed()) {
// ν¬ν μ’
λ£ μλ¦Ό
}
}
}
Java
볡μ¬
μ΄ μνμμλ λ±λ‘λλ listener κ° λ κ°(notifyPollOpen, notifyPollClosed)κ° λκ³ λ λ©μλ λͺ¨λ μ€νμ΄ λκ³ if ꡬ문μΌλ‘ μλ¦Ό λ‘μ§μ μ²λ¦¬ν μ§ λ§μ§ νμΈν©λλ€. μ΄λ κ² λλ€λ©΄ λ λ©μλ λͺ¨λ νΈλμμ
μ μλ‘ μ»κ² λ©λλ€. μ¦ ν¬ν μμ± μμ ν¬ν μμ± μλ¦Ό λ‘μ§ μ νΈλμμ
κ³Ό ν¬ν μ’
λ£ μλ¦Ό λ‘μ§ μ νΈλμμ
μ΄ λͺ¨λ μ΄λ¦¬κ² λμ΄ λΆνμν νΈλμμ
μ μ»μ΄ λΉν¨μ¨μ μ
λλ€. νμ§λ§ condition μΌλ‘ μ²λ¦¬λ₯Ό νλ€λ©΄, condition μ νμΈνλ λ‘μ§μ μλ‘μ΄ μ°λ λλ₯Ό μ΄μ΄ μλ¦Ό λ‘μ§μ μ€ννκΈ° μ μ
λλ€. μ΄μ λ°λΌ λΆνμν νΈλμμ
μ μμ±νμ§ μμ μ μμ΅λλ€.
β’
phase
ꡬλ
νλ μ΄λ²€νΈ λ‘μ§μ΄ μΈμ μ²λ¦¬λ μ§ μ ν μ μλ μ΅μ
μ
λλ€. μ νν μ μλ phase λ‘λ BEFORE_COMMIT, BEFORE_COMPLETION, AFTER_COMMIT, AFTER_COMPLETION λ± μ΄ 4κ°μ phase κ° μμ΅λλ€. *_COMMIT μ λ©μΈ λ‘μ§μ΄ 컀λ°μ΄ λμμ λ, μ¦ status μ λ‘€λ°±μ΄ λ°μνμ§ μμμ λ λμνκ³ , *_COMPLETION μ 컀λ°/λ‘€λ°±μ μκ΄μμ΄ λμν©λλ€. κΈ°λ³Έκ°μ AFTER_COMMIT μ΄κ³ , μ΄μ λ°λΌ λ©μΈ λ‘μ§μΈ ν¬ν μμ± λ‘μ§μ΄ λ‘€λ°±λμ§ μκ³ μ¨μ ν 컀λ°λ μ΄νμ μλ¦Ό λ‘μ§μ΄ λμνκ² λ©λλ€.
ꡬλ λμ μ리
AbstractPlatformTransactionManager μ processCommit λ©μλλ ν΄λΉ μ°λ λμ νΈλμμ
μ»€λ° λ‘μ§μ μνν©λλ€. μ΄ λ 컀λ°νκΈ° μ κ³Ό νμ μνλμ΄μΌ ν λ‘μ§λ€μ μννλλ° phase μ μ€μ μ λ°λΌ μ΄λ²€νΈ λ‘μ§λ μ»€λ° μ /νμ μνλ©λλ€. μ΄ ν¬μ€ν
μμλ μ»€λ° μ΄νμ μ΄λ²€νΈ λ‘μ§ μνμ λν΄ μμλ³΄κ² μ΅λλ€.
processCommit λ©μλ λ΄μμ μ»€λ° λ‘μ§μ μννκ³ λ λ€, λ°νν λ λ±λ‘λ TransactionSynchronization λ€μ κ°μ Έμ¨ λ€(μμ€ μ½λ), TransactionSynchronizationUtils μμ κ°μ Έμ¨ synch λ€μ λ°λ³΅λ¬Έμ λλ©΄μ μ΄λ²€νΈ λ‘μ§μ΄ μνλ©λλ€(μμ€ μ½λ).
μ¬κΈ°μ μ£Όμνκ² λ€λ£¨μ΄μ§λ TransactionSynchronization κ°μ²΄λ νΈλμμ
μ»€λ° μ νμ μμ
μ μ€νμν€κΈ° μν μΈν°νμ΄μ€μ
λλ€. μμ phase λ‘ μ μ©ν μ μλ beforeCommit, beforeCompletion, afterCommit, afterCompletion λ€κ°μ§ λ©μλλ₯Ό ꡬνν μ μμ΅λλ€. ꡬν체μ λ°λΌ μ²λ¦¬ λ°©λ²μ΄ λ€λ₯΄λ©°, @TransactionalEventListener λΒ TransactionalApplicationListenerSynchronizationΒ λΌλ ꡬν체λ₯Ό μ¬μ©ν©λλ€. ν΄λΉ ꡬν체μλ listener(ꡬλ
λ©μλ)μ μΈμλ‘ λ°μ μ΄λ²€νΈ κ°μ²΄κ° μκ³ , ꡬλ
λ©μλλ₯Ό μ€νν¨μΌλ‘μ¨ κ΅¬λ
λ‘μ§μ΄ λμνκ² λ©λλ€.
AfterCompletionμ΄ μ κ±°κΈ°μ λμβ¦?
μμ€ μ½λλ₯Ό μ΄ν΄λ³΄λ©° κ°μ₯ κΆκΈνλ λΆλΆμ triggerAfterCompletion μμ μ€νλλ€λ κ²μ
λλ€.
μμ μμ€ μ½λλ₯Ό λ€μ νλ² λ³΄κ² μ΅λλ€.
public abstract class TransactionSynchronizationUtils {
public static void invokeAfterCommit(@Nullable List<TransactionSynchronization> synchronizations) {
if (synchronizations != null) {
for (TransactionSynchronization synchronization : synchronizations) {
synchronization.afterCommit();
}
}
}
public static void invokeAfterCompletion(@Nullable List<TransactionSynchronization> synchronizations,
int completionStatus) {
if (synchronizations != null) {
for (TransactionSynchronization synchronization : synchronizations) {
try {
synchronization.afterCompletion(completionStatus);
}
catch (Throwable ex) {
logger.debug("TransactionSynchronization.afterCompletion threw exception", ex);
}
}
}
}
}
Java
볡μ¬
@TransactionalEventListener phase μ΅μ
μΌλ‘ κΈ°λ³Έκ°μΈ after commit μΌλ‘ μ€μ νκ³ , triggerAfterCommit λ©μλλ μ‘΄μ¬νλλ° μ after completion μΌλ‘ μ€νμ΄ λμμκΉμ?
μ¬μ€ trggerCommit μμλ μ무κ²λ νμ§ μμ΅λλ€. ν΄λΉ λ©μλλ ꡬνλμ΄μμ§ μκ³ μΈν°νμ΄μ€μ λν΄νΈ λ©μλλ λΉμ΄μλ μνμ
λλ€. κ·Έλμ μ¬μ€ triggerAfterCommit κ³Ό triggerAfterCompletion λ‘μ§ λ λ€ νλλ°, μ€μ μ€νμ triggerAfterCompletion μμλ§ μ§νλλ κ²μ
λλ€.
afterCommit κ³Ό afterCompletion μ λλ‘ λλ μ΄μ λ₯Ό μμ보기 μν΄ TransactionSynchronization μΈν°νμ΄μ€μ μ£Όμμ μ½μ΄λ³΄μλλ°μ, κΈ°λ³Έμ μΈ μ€λͺ
μ λ©μλλͺ
μμ μ λλ¬λ©λλ€. afterCommit μ μ»€λ° μ΄ μ§νλ μ΄ν, afterCompletion μ 컀λ°/λ‘€λ°± μκ΄μμ΄ μ§ν λ©λλ€.
κ·Έλ°λ° μ¬μ€ νμ¬ λ‘μ§μ΄ μ§νλκ³ μλ μμ μ μ΄λ―Έ λ©μΈ λ‘μ§μ νΈλμμ
λ‘μ§μ΄ 컀λ°μ΄ λ μ΄ν μ
λλ€. μ¦ λ‘€λ°± μνκ° μκΈ° νλ μν©μ
λλ€. κ·Έλ λ€λ©΄ λμ μ€μ§μ μΈ μ°¨μ΄λ 무μμΌκΉμ?
# afterCommit
Throws:
RuntimeException β in case of errors; will be propagated to the caller (note: do not throw TransactionException subclasses here!)
# afterCompletion
Throws:
RuntimeException β in case of errors; will be logged but not propagated (note: do not throw TransactionException subclasses here!)
Plain Text
볡μ¬
μ£Όμμ Throws λΆλΆμ 보면, afterCommit μ μμΈκ° νΈμΆλ λ©μλμ μ νκ° λκ³ , afterCompletion μ μ νκ° λμ§ μμ΅λλ€. μ΄μ λ°λΌ afterCompletion μ ꡬν체 λ‘μ§μμ try/catch λ‘ κ°μΈμ Έ μκ³ μμΈμ λν΄ DEBUG λ‘λ§ λ‘κΉ
νκ² λ©λλ€.
νμ§λ§ μ ν¬ μλΉμ€ νΉμ±μ μλ¦Ό λ‘μ§μμ μμΈκ° λ°μνμ μ, DEBUG λ 벨 λμ WARN λ 벨 μ΄μμ λ‘κΉ
μ ν΄μΌ νκ³ , μ΄μ λν΄ ν΄κ²°μ±
μΌλ‘λ afterCommit μ μ§μ ꡬννκ±°λ μ¬λ μλ¦Ό λ‘μ§μμ try/catch λ‘ μμΈλ₯Ό νΈλ€λ§ν μλ μμ΅λλ€. μ΄ ν΄κ²°μ±
λ€λ μ’μ§λ§ μ무λλ κ°κ° μ₯λ¨μ μ΄ λλ ·ν΄ λ³΄μ
λλ€. κ·Έλμ μμ ν΄κ²°μ±
λ€ λμ λΉλκΈ°λ₯Ό μ μ©νμ¬ μμΈ μ²λ¦¬μ λν νΈλ€λ§μ΄ κ°λ₯νλλ‘ κ΅¬μ±ν΄λ³΄μμ΅λλ€.
λΉλκΈ° μ€μ
μ΄λ²€νΈ λ°ν/ꡬλ
λ°©μμΌλ‘ λ³κ²½νλ©΄μ μμ‘΄μ±μ λΌμ΄ λ΄κ³ , βν¬ν μμ±βμ΄λΌλ λ©μΈ λ‘μ§κ³Ό βμ¬λ μλ¦Όβ λΆκ° λ‘μ§μ νΈλμμ
μ λλμ΄ μλ¦Ό λ‘μ§μμ λ‘€λ°±μ΄ λλλΌλ ν¬ν μμ±μ 컀λ°μ΄ λλλ‘ νμκ³ , ν¬ν μμ± λ‘μ§μ 컀λ°μ΄ μ μ΄λ£¨μ΄μ‘μλλ§ μλ¦Ό λ‘μ§μ΄ λμνλλ‘ λ§λ€μ΄ 보μμ΅λλ€. νμ§λ§ μμ§ λ¬Έμ μ μ΄ λ¨μ μλλ°μ, νμ¬ λμνλ μ΄λ²€νΈ λ°©μμ μλ¦Ό λ‘μ§μ λκΈ°μ μΌλ‘ μ€νλμ΄ κ²°κ΅ μλ¦Ό λ‘μ§μ΄ λͺ¨λ μλ£ν λκΉμ§ ν¬ν μμ± API λ λκΈ°ν μλ°μ μκ³ μ΄λ μλ΅ latency λ₯Ό κΈΈκ² λ§λ€κ² λ©λλ€. μ£Ό κ΄μ¬μ¬λ μλ λ‘μ§μ κ΅³μ΄ κΈ°λ€λ¦¬μ§ μκ³ ν΄λΌμ΄μΈνΈμκ² ν¬ν μμ±μ λν μλ΅μ λλ €μ£Όλ κ²μ΄ λ μμ°μ€λ½κΈ°μ, λΉλκΈ°κ° νμν μν©μ΄λΌκ³ νλ¨νμ¬ μ μ©νμ΅λλ€.
Async μ°λ λ ν μ μ©νκΈ°
λΉλκΈ° μ΄λ²€νΈ μ²λ¦¬λ λ³λμ μ°λ λμμ λμν©λλ€. μ΄ λ, μ΄λ²€νΈ μ²λ¦¬λ§λ€ 무νν μ°λ λλ₯Ό μμ±νκΈ° 보λ€λ μ°λ λ νμ μ¬μ©νμ¬ κ΄λ¦¬νλ λ°©λ²μ μ νν μ μμ΅λλ€.
@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {
private static final int THREAD_COUNT = 4;
@Override
public Executor getAsyncExecutor() {
return Executors.newFixedThreadPool(THREAD_COUNT);
}
}
Java
볡μ¬
νμ§λ§ λΉλκΈ°λ‘ μλ¦Ό λ‘μ§μ μννλ©΄ ControllerAdvice μ μ‘νμ§ μμ΅λλ€. μ²μμλ μ μ‘νμ§ μμκΉ μ΄ν΄ν μ μμλλ°, μκ°ν΄λ³΄λ μ‘νμ§ μλ κ²μ΄ λΉμ°ν©λλ€. μμ²μ λν μλ΅μ DispatcherServlet μ mvc λ°©μμΌλ‘ μνμ΄ λκ³ νΈλ€λ§ μμ try catch λ‘ κ°μΈ μμΈκ° λ°μνλ©΄ μ΄λ₯Ό μ‘μ ControllerAdvice μμ μ²λ¦¬λ₯Ό ν©λλ€. νμ§λ§ λΉλκΈ°λ μλ‘μ΄ μ°λ λλ₯Ό μμ±νμ¬ λ‘μ§μ΄ μνλκΈ° λλ¬Έμ DispatcherServlet μμ μνλ λ‘μ§μ΄ μλκ³ , μ΄μ λ°λΌ μμΈκ° μ‘ν μ μλ ꡬ쑰μ
λλ€. μ΄μ λ°λΌ λΉλκΈ°λ‘ μνλλ λ‘μ§μμ λ°μνλ μμΈλ λ€λ₯Έ λ°©μμΌλ‘ νΈλ€λ§ν΄μΌ νκ³ κΈ°λ³Έμ μΌλ‘ try/catch ꡬ문μΌλ‘ μ²λ¦¬λ₯Ό ν μ μλλ°, μ΄λ₯Ό μ μμ μΌλ‘ λμμ£Όλ ν΄λμ€κ° μμ΅λλ€.
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(AsyncExceptionHandler.class);
@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
logger.warn("λΉλκΈ° μ²λ¦¬μ€ μμΈκ° λ°μνμ΅λλ€\n" +
"μμΈ λ©μΈμ§ : " + ex.getMessage() + "\n" +
"λ©μλ : " + method.getName() + "\n" +
"νλΌλ―Έν° : " + Arrays.toString(params));
}
Java
볡μ¬
@EnableAsync
@Configuration
public class AsyncConfig implements AsyncConfigurer {
// ...
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncExceptionHandler();
}
}
Java
볡μ¬
AsyncUncaughtExceptionHandler λ₯Ό μμλ°μ λΉλκΈ° μ°λ λμμ λ°μνλ μμΈλ₯Ό μ μμ μΌλ‘ μ²λ¦¬ν μ μμ΅λλ€.
κ°μ μ
μ΄λ²€νΈ λ°©μμΌλ‘ μ ννκΈ° μ μ λ¬Έμ μ μ λͺ¨λ ν΄κ²°ν΄λ³΄μλλ°μ, μμ§ μμΈ μ²λ¦¬μ λν λ¬Έμ κ° λ¨μ μμ΅λλ€. νμ¬λ μλ¦Ό λ‘μ§μ μμΈμ λν΄ λ‘κ·Έλ‘λ§ μΆλ ₯νκ³ μμ΅λλ€. μ΄λ₯Ό ν΄κ²°ν μ μλ λ°©λ²μΌλ‘λ, μλ¦Όμ λ°μ‘ν λ λ°°μΉμ± λ‘μ§μ μΆκ°νμ¬ μμ§ μλ¦Όμ 보λ΄μ§ μμ, μ¦ μμ ν¬ν μμ±μ μμΈλ‘ μΈν΄ μλ¦Όμ 보λ΄μ§ λͺ»ν λ μ½λμ λν΄ μΆκ°μ μΈ λ‘μ§μ μ€ννλλ‘ νλ λ°©λ²μ΄ μμ΅λλ€.
public void notifyPollOpen(PollEvent event) {
// findAllWhereNotnotified();
// μ±κ³΅νλ©΄ λͺ¨λ notified = true μ
λ°μ΄νΈ
}
Java
볡μ¬
μμΈλ‘ μΈν΄ μλ¦Όμ 보λ΄μ§ λͺ»νλ ν¬νλ€μ λν΄ μ¬μ²λ¦¬νλ λ‘μ§μ μνν μ μμ΅λλ€. νμ§λ§ μ΄ λ°©λ²λ κ²°κ΅μλ μ€μκ°μ±μ΄ λΆμ‘±νκ³ , μμΈ μν©μ λν μΆμ κ³Ό μ¦κ°μ μΈ λμμ ν μ μλ λ°©λ²μ
λλ€. κ²°κ΅μ MSA μ κ°μ΄ μλΉμ€λ₯Ό μμ λλμ΄ μλ¦Όμ λν μλ‘μ΄ API λ₯Ό ꡬμ±νκ³ , μλ¦Όμ λν΄ μμ²μ λ³΄λΌ μ μλλ‘ νμ¬ μ΄μ λν μλ΅μ λ°λ‘ κ΄λ¦¬νλ κ²μ΄ μ’μ λ°©λ²μ΄ λ κ²μ
λλ€.