이벤트 자동 발행EventPublishingRepositoryProxyPostProcessorspring-data-commons 라이브러리의 EventPublishingRepositoryProxyPostProcessor는 CrudRepository.save(Object)와 CrudRepository.delet(Object) 메서드를 인터셉트하여 @DomainEvent를 발행하고 @AfterDomainEventPublication 어노테이션이 붙은 메서드를 실행하는 MethodInterceptor를 등록합니다.@DomainEvent와 @AfterDomainEventPublication를 직접 선언하거나 AbstractAggregateRoot를 상속하는 방식으로 이벤트를 다루는 객체는 자유롭게 만들 수 있지..
이벤트 모듈 설계 다이어그램Eventabstract class Event( val eventId: String, val eventTime: LocalDateTime, )eventId와 eventTime은 필수로 가질 수 있도록 정의하였습니다. @EventDetails@Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) @DomainEvent annotation class EventDetails( val outBox: Boolean = false, )Event 클래스가 비즈니스 로직을 처리하는 과정에서 필요하지 않은 정보를 위한 어노테이션입니다.outBox: 해당 이벤트가 외부까지 전달되어야 하는..
메시지 큐와 이벤트 스트림메시지 큐와 이벤트 스트림의 차이용어메시지 큐에서는 주로 데이터를 생성하는 쪽을 생산자(producer)로, 데이터를 수신하는 쪽을 소비자(consumer)로 지칭한다.이벤트 스트림에서는 데이터를 생성하는 쪽을 발행자(publisher)로, 데이터를 조회하는 쪽을 구독자(subscriber)로 지칭한다. 방향성메시지 큐의 생산자는 소비자의 큐로 데이터를 직접 푸시한다.2개의 서비스에 같은 메시지를 보내야 할 때 메시지 큐를 이용한다면 생산자는 2개의 각각 다른 메시지 큐에 각각 데이터를 푸시해야 한다.반면 스트림을 이용한다면 생산자는 스트림의 특정 저장소에 하나의 메시지를 보낼 수 있고, 메시지를 읽어가고자 하는 소비자들은 스트림에서 같은 메시지를 풀(pull) 해갈 수 있기 때..
더미 객체(Dummy)더미는 테스트 대상 클래스에 전달되지만 절대 사용되지 않는 객체다.객체가 필요할 뿐 객체의 기능까지는 필요하지 않은 경우 사용한다. 페이크 객체(Fake)페이크 객체는 시뮬레이션하려는 클래스 같이 실제로 동작하는 구현체를 가진다.하지만 대개 똑같이 동작하는 것은 아니고 훨씬 단순한 방법으로 동작한다. 스텁(Stub)스텁은 테스트 과정에서 수행된 호출에 대해 하드 코딩된 응답을 제공한다.페이크 객체와는 달리 스텁은 실제로 동작하는 구현체가 없다. 모의 객체(Mock)모의 객체는 메서드의 응답을 설정할 수 있다는 점에서 스텁 같은 역할을 한다.하지만 모의 객체는 모든 상호작용을 저장해서 나중에 단언문에 활용할 수 있도록 해준다. 모의 객체의 단점모의 객체를 사용한 테스트는 자연스럽게 모..
MC/DC 커버리지란?각 개별 조건식이 다른 개별 조건식에 영향을 받지 않고 전체 조건식의 결과에 독립적으로 영향을 주도록 함으로써, 조건 커버리지와 분기 커버리지를 보완해서 만든 테스트 커버리지 접근 방법이다.다른 상태들의 변동이 없고 자신의 상태가 변경되었을 때 결과 값에 영향을 미치는 경우 해당 상태는 MC/DC를 만족한다고 할 수 있다. MC/DC의 특징결과에 독립적Condition / Decision 커버리지를 만족하고 결정에서 각 조건들은 결과에 독립적이어야 한다. N+1 테스트 케이스일반적으로 N개 입력을 가진 결정에서 N+1 개의 테스트 케이스 필요하다.모든 테스트 케이스 작성하는 것이 아닌 N+1의 보다 적은 테스트 케이스 작성을 통해 효율적으로 테스트를 진행할 수 있다. MC/DC 결정..
동시성 문제 - 중복 저장@Componentclass SubscribeWorkbookUseCase( private val subscriptionDao: SubscriptionDao, private val applicationEventPublisher: ApplicationEventPublisher,) { @Transactional fun execute(useCaseIn: SubscribeWorkbookUseCaseIn) { /** 워크북 구독 히스토리를 조회한다. */ val workbookSubscriptionHistory = subscriptionDao.selectTopWorkbookSubscriptionStatus() when { ..