Post

04 Saga&kafka

04 Saga&kafka

1. 사용자 포인트 부여

1. memo

  • 게시글 작성전 포인트차감 -> 게시글 작성 -> 활동점수 부여 서비스 로직
  • 카프카를 이용해서 동기와 비동기 어떻게 나눌 것인가가 중요한 포인트
    • 사용자가 AP에 접근하여 액션을 취하고 그 결과에 대한 작업을 처리하는 과정
    • 사용자가 진행한 이벤트에 대한 결과를 제공해야하고,
    • 사용자 이벤트로 인한 추가 작업을 적용해줘야함.
  • 지금 작업에서는
    • 활동 점수를 부여한다는 것은 사용자 입장에서는 상대적으로 크게 중요하지 않은 부분이라 판단,
    • 그래서 활동점수 부여를 카프카로 넘겨서, 추후에 작업할 수 있도록하고
    • 작업 자체를 완료하면 완료했다고 사용자에게 알림을 보냄.
    • 만약 활동점수 부여의 실패는 게시글 작성과 무관하므로 추후에 수동으로 처리가 가능한 부분
  • 사용자의 입장과 전체 비즈니스로직의 흐름을 구분하여 동기/비동기 작업 구분이 필요함.

2. 상황

내 그림

3. 자바소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
 public void create(CreateBoardRequestDto createBoardRequestDto) {
    // 게시글 저장을 성공했는 지 판단하는 플래그
    boolean isBoardCreated = false;
    Long savedBoardId = null;

    // 포인트 차감을 성공했는 지 판단하는 플래그
    boolean isPointDeducted = false;

    try {
      // 게시글 작성 전 100 포인트 차감
      pointClient.deductPoints(createBoardRequestDto.getUserId(), 100);
      isPointDeducted = true; // 포인트 차감 성공 플래그
      System.out.println("포인트 차감 성공");

      // 게시글 작성
      Board board = new Board(
          createBoardRequestDto.getTitle(),
          createBoardRequestDto.getContent(),
          createBoardRequestDto.getUserId()
      );

      Board savedBoard = this.boardRepository.save(board);
      savedBoardId = savedBoard.getBoardId();
      isBoardCreated = true; // 게시글 저장 성공 플래그
      System.out.println("게시글 저장 성공");

      // 게시글 작성 시 작성자에게 활동 점수 10점 부여
      userClient.addActivityScore(createBoardRequestDto.getUserId(), 10);
      System.out.println("활동 점수 적립 성공");
      
      // '게시글 작성 완료' 이벤트 발행
      BoardCreatedEvent boardCreatedEvent = new BoardCreatedEvent(createBoardRequestDto.getUserId());
      this.kafkaTemplate.send("board.created", toJsonString(boardCreatedEvent));
      System.out.println("게시글 작성 완료 이벤트 발행");

    } catch (Exception e) {
      if (isBoardCreated) {
        // 게시글 작성 보상 트랜잭션 => 게시글 삭제
        this.boardRepository.deleteById(savedBoardId);
        System.out.println("[보상 트랜잭션] 게시글 삭제");
      }
      if (isPointDeducted) {
        // 포인트 차감 보상 트랜잭션 => 포인트 적립
        pointClient.addPoints(createBoardRequestDto.getUserId(), 100);
        System.out.println("[보상 트랜잭션] 포인트 적립");
      }

      // 실패 응답으로 처리하기 위해 예외 던지기
      throw e;
    }
 

2. MSA환경의 서비스간 소통

1. memo

  • 게시글을 조회할때 유저서비스와 커넥션해서 데이터를 가져와서 사용해야함.
  • 그러면 게시글을 조회할때 유저서비스와 통신을 반복적으로 해야하는 상황이 생김
  • 따라서
    • 게시글 서비스에 게시글에 필요한 유저정보를 저장함.
    • 보드서비스에서는 카프카로 유저정보를 계속 확인해서 저장하는 형태로 변경.
    • 유저서비스에서는 신규 생성/변경이 되면 카프카로 유저정보에 관한 정보를 계속 보내둠
    • 그러면 따로 계속 소통할 필요는 없고, 게시글에 관한 최소한의 정보만 보드서비스에서 가지고 있는 형태가됨.
    • 이렇게 되면, 보드서비스 외에 다른 곳에서 유저ID와 관련된 내용이 필요하면 그 카프카정보만 가져다가 쓰면됨!
    • 상세한 정보는 유저서비스에서 직접 보는걸 유지함!
    • 그러면 유저서비스에 트래픽이 몰리는게 막아짐!!!!

2. 상황

내 그림

3. 자바소스

1. 유저 서비스(MSA AP)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  @Transactional
  public void signUp(SignUpRequestDto signUpRequestDto) {
    User user = new User(
      signUpRequestDto.getEmail(),
      signUpRequestDto.getName(),
      signUpRequestDto.getPassword()
    );

    User savedUser = this.userRepository.save(user);

    // 회원가입하면 포인트 1000점 적립
    pointClient.addPoints(savedUser.getUserId(), 1000);

    // '회원가입 완료' 이벤트 발행
    UserSignedUpEvent userSignedUpEvent = new UserSignedUpEvent(
        savedUser.getUserId(),
        savedUser.getName()
    );
    // 카프카에 지속적으로 신규 유저에 대한 정보를 보냄.
    this.kafkaTemplate.send(
        "user.signed-up",
        toJsonString(userSignedUpEvent)
    );
  }

2. 보드 서비스(MSA AP)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  @KafkaListener(
      topics = "user.signed-up",
      groupId = "board-service"
  )
  public void consume(String message) {
    UserSignedUpEvent userSignedUpEvent = UserSignedUpEvent.fromJson(message);

    // 사용자 정보 저장
    // 여기서 서비스는 보드서비스 내에서 유저정보를 저장하는 서비스
    SaveUserRequestDto saveUserRequestDto
        = new SaveUserRequestDto(
            userSignedUpEvent.getUserId(),
            userSignedUpEvent.getName()
        );
    userService.save(saveUserRequestDto);
  }  
This post is licensed under CC BY 4.0 by the author.