전자책 출간 알림 [마이크로서비스패턴 쉽게 개발하기]

티스토리 뷰

State Stored Aggregate는 Event Sourcing을 하지 않고 물리적 DB에 데이터를 직접 CUD(Create, Update, Delete)할 때 사용하는 Aggregate입니다. 

일반 Aggregate가 Event Replay, Event 발행, Event Store에 Event 추가를 하는 반면,

State Stored Aggregate는 데이터의 최종 상태를 DB에 바로 CUD합니다. 단, 다른 서비스나 다른 처리를 위해 Event는 발행할 수 있습니다. 

State Stored Aggregate로 만드는 방법은 Entity class에 Aggregate에 필요한 항목들을 추가하는 것입니다. 

  • @Aggregate 어노테이션을 붙여 Aggregate임을 표시
  • 필드 중 Primary Key 필드에 @AggregateIdentifier 어노테이션을 추가
  • 멤버 필드에 @AggregateMember 어노테이션을 추가
  • Command Handler 작성
    • 비즈니스 로직을 수행 후 필요 시에만 Event 발행
    • Entity상태를 수정. 즉, DB에 CUD 수행 
  • EventSoucingHandler는 없음

그럼 실습을 통해 State Stored Aggregate를 더 확실히 이해해 보겠습니다. 


우리가 만들 State Stored Aggregate는 코끼리가 냉장고에 들어간 횟수를 기록하는 Aggregate입니다. 

State Stored Aggregate 생성 

package 'org.axon.entity'에 EnterCount라는 class를 만듭니다. 

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.axonframework.modelling.command.AggregateIdentifier;
import org.axonframework.modelling.command.AggregateMember;
import org.axonframework.spring.stereotype.Aggregate;

import java.io.Serial;
import java.io.Serializable;

@Aggregate
@Data
@Entity
@Slf4j
@Table(name="enter_count")
public final class EnterCount implements Serializable {
    /*
    Serialize(DB에 저장 시 binary로 변환)와 Deserialize(DB에 저장된 binary 데이터를 원래 값으로 변환)시 사용할 UID
    'serialVersionUID에 마우스를 올려놓고 전구 아이콘 메뉴에서 <Randomly change 'serialVersionUID' initializer> 선택
    */
    @Serial
    private static final long serialVersionUID = 1182092962825835024L;

    @AggregateIdentifier
    @Id     //Primary key 필드를 나타냄
    @Column(name="count_id", nullable = false, length = 10)
    private String countId;

    @AggregateMember
    @Column(name="elephant_id", nullable = false, length = 3)
    private String elephantId;

    @AggregateMember
    @Column(name="count", nullable = false)
    private int count;

    public EnterCount() { }

}
중요) Aggregate 간에 동일한 primary key 필드를 공유할 수 없음
EnterCount Aggregate의 Key필드는 기존 코끼리ID가 아닌 별도ID입니다. 
Key 필드를 코끼리ID로 하면 제대로 동작하지 않습니다. 

 

Command Handler 작성 

1) Entity 생성 Command Handler 

새로운 Entity를 생성하는 Command Handler를 작성 합니다. 

  • 이후 별도로 처리할 게 없으므로 Event는 발행하지 않습니다. 
  • Aggregate필드의 값을 셋팅하면 자동으로 연결된 테이블인 enter_count에 record가 생성 됩니다. 
    Axon Framework이 JPA를 이용하여 수행 합니다. 
    @CommandHandler
    private EnterCount(CreateEnterCountCommand cmd) {
        log.info("[@CommandHandler] CreateEnterCountCommand for Id: {}", cmd.getElephantId());

        //-- 새로운 Entity 등록(테이블에 바로 등록됨)
        this.countId = cmd.getCountId();
        this.elephantId = cmd.getElephantId();
        this.count = cmd.getCount();
    }

package 'org.axon.command' 밑에 Command class 'CreateEnterCountCommand' 를 작성 합니다. 

import lombok.Builder;
import lombok.Value;
import org.axonframework.modelling.command.TargetAggregateIdentifier;

@Value
@Builder
public class CreateEnterCountCommand {
    @TargetAggregateIdentifier
    String countId;
    String elephantId;
    int count;
}

 


2) Entity 업데이트 Command Handler: 코끼리 넣은 횟수 증가 처리

'EnterCount' Aggregate에 Command Handler를 추가 합니다. 

역시 Event 발행은 하지 않고 넣은 횟수 증가만 합니다. 

    @CommandHandler
    private void handle(UpdateEnterCountCommand cmd) {
        log.info("[@CommandHandler] UpdateEnterCountCommand for Id: {}", cmd.getElephantId());

        //-- Entity 최종 상태 갱신(테이블에 바로 저장됨)
        log.info("current count: {} + {}", this.count, cmd.getCount());
        this.count += cmd.getCount();
    }

package 'org.axon.command'에 'UpdateEnterCountCommand'도 추가 합니다. 

import lombok.Builder;
import lombok.Value;
import org.axonframework.modelling.command.TargetAggregateIdentifier;

@Value
@Builder
public class UpdateEnterCountCommand {
    @TargetAggregateIdentifier
    String countId;
    String elephantId;
    int count;
}

 


ElephantEventHandler에 Command 발송 추가 

- CreatedElephantEvent Handler에 추가

    @EventHandler
    private void on(CreatedElephantEvent event) {
    ...
        try {
            elephantRepository.save(elephant);

            //-- Count 생성 command
            commandGateway.send(CreateEnterCountCommand.builder()
                    .countId("COUNT_"+event.getId())
                    .elephantId(event.getId()).count(0).build());

        } catch(Exception e) {
            log.info(e.getMessage());
        }
    }

 

- EnteredElephantEvent Handler에 추가

    @EventHandler
    private void on(EnteredElephantEvent event) {
		...
		if(elephant != null) {
			...

            //-- Count 갱신 command
            commandGateway.send(UpdateEnterCountCommand.builder()
                    .countId("COUNT_"+event.getId())
                    .elephantId(event.getId()).count(1).build());

        }
    }

 


테스트

어플리케이션을 재시작하고 Swagger에서 테스트 합니다. 

새로운 코끼리를 만들고, 넣고 꺼내기를 몇번 반복한 후 DBeaver에서 enter_count 테이블의 데이터를 확인합니다. 

count가 늘어나면 정상 처리 된 겁니다. 


이상으로 데이터의 최종 상태를 DB에 직접 CUD하는 State Stored Aggregate에 대해 실습을 통해 이해해 봤습니다. 

다음 편은 Axon Framework 이해의 마지막인 Event Replay를 이용한 조회DB 데이터 복구를 실습하겠습니다. 


마이크로서비스 패턴 쉽게 개발하기 목차

  1. 마이크로서비스 패턴 이해: Saga, Event Sourcing, API Composition, CQRS 이해
  2. 실습환경 준비
  3. 주문 서비스 테스트 
  4. Axon Framework 이해 
    1. 코끼리 냉장고에 넣기 프로젝트 시작
    2. CommandGateway와 Event Sourcing
    3. EventGateway
    4. QueryGateay
    5. Snapshot
    6. State Stored Aggregate
    7. Event Replay로 조회DB 데이터 복구

아래 주제들은 '마이크로서비스패턴 쉽게 개발하기'라는 제 책에서 만나실 수 있습니다. 

  • 멀티 모듈 프로젝트 작성
  • 신규 주문 정상처리 프로세스 구현 
  • 배송 상태 변경 및 재고 증감 처리
  • 신규 주문 보상처리 프로세스 구현
  • 주문 수정 정상처리 프로세스 구현
  • 주문 수정 보상처리 프로세스 구현
  • 주문 삭제 정상처리 프로세스 구현
  • 주문 삭제 보상처리 프로세스 구현
  • API Composition 패턴과 CQRS 패턴

책 한번 내겠다는 평소의 꿈을 실현하기 위해 이번에 전자책을 내게 되었습니다. 

제 꿈을 응원하신다는 마음으로 전체 내용을 공유하지 않는것을 양해해 주시고 

구매까지 해 주시면 더욱 감사하겠습니다. 

https://happycloud-lee.tistory.com/notice/291

 

[마이크로서비스패턴 쉽게 개발하기] 전자책 출간

안녕하세요? 온달입니다. 그동안 몇 번 시도하다 포기했던 책내기에 드디어 성공 했습니다. 책 제목은 '마이크로서비스패턴 쉽게 개발하기'입니다. 2024년 2월 19일 부터 교보문고, Yes24, 알라딘 등

happycloud-lee.tistory.com

 

댓글

전자책 출간 알림 [마이크로서비스패턴 쉽게 개발하기]