Repository: capstone-mindmate/mindmate-backend
Pull request #49: [Update#47] 랜덤 매칭 로직 변경
Author: login: 1-J-1
Created: 2025-04-05T00:59:50Z | Merged: 2025-04-05T03:05:32Z

## 🛰️ Issue Number
#47 

## 🪐 작업 내용
- 가중치 계산 로직
- 가중치 기반 선택 로직
- 랜덤 매칭 로직

## 논의 내용
- 트랜잭션 관리를 개선하려고 구현 하고 있었는데 스프링 내장 시스템(또는 레디스)을 쓸지, 그냥 카프카를 쓸지 고민이 됩니다..


Diff:
diff --git a/src/main/java/com/mindmate/mindmate_server/matching/controller/MatchingController.java b/src/main/java/com/mindmate/mindmate_server/matching/controller/MatchingController.java
index 32c728fa..bff62dad 100644
--- a/src/main/java/com/mindmate/mindmate_server/matching/controller/MatchingController.java
+++ b/src/main/java/com/mindmate/mindmate_server/matching/controller/MatchingController.java
@@ -99,7 +99,7 @@ public ResponseEntity<Void> cancelWaiting(
     public ResponseEntity<Void> closeMatching(
             @AuthenticationPrincipal UserPrincipal userPrincipal,
             @PathVariable Long matchingId) {
-        matchingService.closeMatching(userPrincipal.getUserId(), matchingId);
+        matchingService.cancelMatching(userPrincipal.getUserId(), matchingId);
         return ResponseEntity.ok().build();
     }
 
diff --git a/src/main/java/com/mindmate/mindmate_server/matching/domain/Matching.java b/src/main/java/com/mindmate/mindmate_server/matching/domain/Matching.java
index 74b912ca..3fe619eb 100644
--- a/src/main/java/com/mindmate/mindmate_server/matching/domain/Matching.java
+++ b/src/main/java/com/mindmate/mindmate_server/matching/domain/Matching.java
@@ -84,8 +84,8 @@ public void setChatRoom(ChatRoom chatRoom){
         this.chatRoom = chatRoom;
     }
 
-    public void closeMatching() {
-        this.status = MatchingStatus.CLOSED;
+    public void cancelMatching() {
+        this.status = MatchingStatus.CANCELED;
     }
 
     public void acceptMatching(User acceptedUser) {
diff --git a/src/main/java/com/mindmate/mindmate_server/matching/domain/MatchingStatus.java b/src/main/java/com/mindmate/mindmate_server/matching/domain/MatchingStatus.java
index 1e21579c..56e218b0 100644
--- a/src/main/java/com/mindmate/mindmate_server/matching/domain/MatchingStatus.java
+++ b/src/main/java/com/mindmate/mindmate_server/matching/domain/MatchingStatus.java
@@ -7,9 +7,8 @@
 @RequiredArgsConstructor
 public enum MatchingStatus {
     OPEN("시작됨"),
-    REJECTED("거절됨"),
     MATCHED("매칭됨"),
-    CLOSED("완료됨"),
+//    CLOSED("완료됨"),
     CANCELED("취소됨");
 
     private final String title;
diff --git a/src/main/java/com/mindmate/mindmate_server/matching/domain/MatchingType.java b/src/main/java/com/mindmate/mindmate_server/matching/domain/MatchingType.java
index 76066b9f..19beb76c 100644
--- a/src/main/java/com/mindmate/mindmate_server/matching/domain/MatchingType.java
+++ b/src/main/java/com/mindmate/mindmate_server/matching/domain/MatchingType.java
@@ -7,8 +7,7 @@
 @RequiredArgsConstructor
 public enum MatchingType {
     AUTO_RANDOM("랜덤 매칭"),
-    AUTO_FORMAT("맞춤 매칭"),
-    MANUAL("직접 선택"); // 수동 매칭
+    MANUAL("수동 매칭");
 
     private final String title;
 }
\ No newline at end of file
diff --git a/src/main/java/com/mindmate/mindmate_server/matching/domain/WaitingUser.java b/src/main/java/com/mindmate/mindmate_server/matching/domain/WaitingUser.java
index f9469be8..1a87adc8 100644
--- a/src/main/java/com/mindmate/mindmate_server/matching/domain/WaitingUser.java
+++ b/src/main/java/com/mindmate/mindmate_server/matching/domain/WaitingUser.java
@@ -62,7 +62,7 @@ public boolean isOwner(User user) {
     }
 
     public boolean isAutoMatching() {
-        return this.matchingType == MatchingType.AUTO_FORMAT;
+        return this.matchingType == MatchingType.AUTO_RANDOM;
     }
 
 }
diff --git a/src/main/java/com/mindmate/mindmate_server/matching/service/MatchingService.java b/src/main/java/com/mindmate/mindmate_server/matching/service/MatchingService.java
index 7e1897f7..58b87e39 100644
--- a/src/main/java/com/mindmate/mindmate_server/matching/service/MatchingService.java
+++ b/src/main/java/com/mindmate/mindmate_server/matching/service/MatchingService.java
@@ -34,7 +34,7 @@ Page<MatchingResponse> getMatchings(Pageable pageable, MatchingCategory category
 
     Page<MatchingResponse> getUserMatchingHistory(Long userId, Pageable pageable, boolean asParticipant);
 
-    void closeMatching(Long userId, Long matchingId);
+    void cancelMatching(Long userId, Long matchingId);
 
     Matching findMatchingById(Long matchingId);
 }
diff --git a/src/main/java/com/mindmate/mindmate_server/matching/service/MatchingServiceImpl.java b/src/main/java/com/mindmate/mindmate_server/matching/service/MatchingServiceImpl.java
index ef55182e..9ed2b216 100644
--- a/src/main/java/com/mindmate/mindmate_server/matching/service/MatchingServiceImpl.java
+++ b/src/main/java/com/mindmate/mindmate_server/matching/service/MatchingServiceImpl.java
@@ -149,10 +149,14 @@ public Long acceptMatching(Long userId, Long matchingId, Long waitingId) {
     @Override @Transactional
     public Long autoMatchApply(Long userId, AutoMatchingRequest request) {
         InitiatorType userRole = request.getUserRole();
-
         User user = userService.findUserById(userId);
 
-        Long matchingId = redisMatchingService.getRandomMatching(userRole);
+        int activeRoomCount = redisMatchingService.getUserActiveMatchingCount(userId);
+        if (activeRoomCount >= 3) {
+            throw new CustomException(MatchingErrorCode.MATCHING_LIMIT_EXCEED);
+        }
+
+        Long matchingId = redisMatchingService.getRandomMatching(user, userRole);
 
         if (matchingId == null) {
             throw new CustomException(MatchingErrorCode.NO_MATCHING_AVAILABLE);
@@ -164,7 +168,7 @@ public Long autoMatchApply(Long userId, AutoMatchingRequest request) {
         // 자동 매칭 신청
         WaitingUser waitingUser = WaitingUser.builder()
                 .waitingUser(user)
-                .matchingType(MatchingType.AUTO_FORMAT)
+                .matchingType(MatchingType.AUTO_RANDOM)
                 .anonymous(request.isAnonymous())
                 .build();
 
@@ -317,7 +321,7 @@ public Page<MatchingResponse> getUserMatchingHistory(Long userId, Pageable pagea
     }
 
     @Override @Transactional
-    public void closeMatching(Long userId, Long matchingId) {
+    public void cancelMatching(Long userId, Long matchingId) {
         User user = userService.findUserById(userId);
 
         Matching matching = matchingRepository.findById(matchingId)
@@ -338,7 +342,7 @@ public void closeMatching(Long userId, Long matchingId) {
                     redisMatchingService.decrementUserActiveMatchingCount(app.getWaitingUser().getId());
                 });
 
-        matching.closeMatching();
+        matching.cancelMatching();
 
         redisMatchingService.decrementUserActiveMatchingCount(userId);
         redisMatchingService.removeMatchingFromAvailableSet(matchingId, matching.getCreatorRole());
diff --git a/src/main/java/com/mindmate/mindmate_server/matching/service/RedisMatchingService.java b/src/main/java/com/mindmate/mindmate_server/matching/service/RedisMatchingService.java
index df865a39..edeadb31 100644
--- a/src/main/java/com/mindmate/mindmate_server/matching/service/RedisMatchingService.java
+++ b/src/main/java/com/mindmate/mindmate_server/matching/service/RedisMatchingService.java
@@ -2,16 +2,24 @@
 
 import com.mindmate.mindmate_server.matching.domain.InitiatorType;
 import com.mindmate.mindmate_server.matching.domain.Matching;
+import com.mindmate.mindmate_server.matching.repository.MatchingRepository;
+import com.mindmate.mindmate_server.user.domain.Profile;
+import com.mindmate.mindmate_server.user.domain.User;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Service;
 
+import java.util.*;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 @Service
+@Slf4j
 @RequiredArgsConstructor
 public class RedisMatchingService {
     private final StringRedisTemplate redisTemplate;
+    private final MatchingRepository matchingRepository;
 
     private static final String MATCHING_SET_KEY = "matching:available:%s";
     private final static String USER_ACTIVE_MATCHING_COUNT = "user:%d:activeMatchings";
@@ -23,21 +31,21 @@ public void addMatchingToAvailableSet(Matching matching) {
         setExpiry(setKey, 24);
     }
 
-    public Long getRandomMatching(InitiatorType userRole) {
-        InitiatorType targetRole = (userRole == InitiatorType.SPEAKER)
-                ? InitiatorType.LISTENER
-                : InitiatorType.SPEAKER;
-
-        String setKey = String.format(MATCHING_SET_KEY, targetRole);
-
-        String matchingId = redisTemplate.opsForSet().randomMember(setKey);
-
-        if (matchingId.isEmpty()) {
-            return null; // 가능한 매칭 없음
-        }
-
-        return Long.valueOf(matchingId);
-    }
+//    public Long getRandomMatching(InitiatorType userRole) {
+//        InitiatorType targetRole = (userRole == InitiatorType.SPEAKER)
+//                ? InitiatorType.LISTENER
+//                : InitiatorType.SPEAKER;
+//
+//        String setKey = String.format(MATCHING_SET_KEY, targetRole);
+//
+//        String matchingId = redisTemplate.opsForSet().randomMember(setKey);
+//
+//        if (matchingId.isEmpty()) {
+//            return null; // 가능한 매칭 없음
+//        }
+//
+//        return Long.valueOf(matchingId);
+//    }
 
     public void removeMatchingFromAvailableSet(Long matchingId, InitiatorType creatorRole) {
         String setKey = buildKey(MATCHING_SET_KEY, creatorRole);
@@ -82,4 +90,103 @@ public void cleanupMatchingKeys(Matching matching) {
 
     // 사용자 로그아웃 or 세션 종료될 때 count 캐시 지우는 것도?
 
+    // 프로필 기반한 가중치 계산
+    // 가중치 계산 기반한 매칭방 선택
+    public Long getRandomMatching(User user, InitiatorType userRole) {
+        InitiatorType targetRole = (userRole == InitiatorType.SPEAKER)
+                ? InitiatorType.LISTENER
+                : InitiatorType.SPEAKER;
+
+        String setKey = buildKey(MATCHING_SET_KEY, targetRole);
+        Set<String> matchingIds = redisTemplate.opsForSet().members(setKey);
+
+        if (matchingIds == null || matchingIds.isEmpty()) {
+            return null;
+        }
+
+        // 매칭 수 많으면 랜덤하게 선택 백개만
+        List<String> candidateIds = new ArrayList<>(matchingIds);
+        if (candidateIds.size() > 100) {
+            Collections.shuffle(candidateIds);
+            candidateIds = candidateIds.subList(0, 100);
+        }
+
+        Map<Long, Double> scoredMatches = new HashMap<>();
+
+        for (String candidateId : candidateIds) {
+            try {
+                Long matchingId = Long.valueOf(candidateId);
+                Matching matching =  matchingRepository.findById(matchingId).orElse(null);
+
+                if (matching == null || !matching.isOpen() || !matching.isAllowRandom()) {
+                    redisTemplate.opsForSet().remove(setKey, candidateId);
+                    continue;
+                }
+
+                double score = calculateMatchingScore(user, matching);
+                scoredMatches.put(matchingId, score);
+            } catch (Exception e) {
+                log.error("계산 오류: {}", candidateId, e);
+            }
+        }
+
+        if (scoredMatches.isEmpty()) {
+            return null;
+        }
+
+        List<Map.Entry<Long, Double>> topMatches = scoredMatches.entrySet().stream()
+                .sorted(Map.Entry.<Long, Double>comparingByValue().reversed())
+                .limit(5)
+                .collect(Collectors.toList());
+
+        int randomIndex = (int)(Math.random() * topMatches.size());
+        return topMatches.get(randomIndex).getKey();
+    }
+
+    private double calculateMatchingScore(User user, Matching matching) {
+
+        double score = 0.0;
+        Profile userProfile = user.getProfile();
+
+        if (userProfile != null) {
+            User creator = matching.getCreator();
+            Profile creatorProfile = creator.getProfile();
+
+            if (creatorProfile != null) {
+                if (!userProfile.getDepartment().isEmpty() &&
+                        userProfile.getDepartment().equals(creatorProfile.getDepartment())) {
+                    score += 20.0;
+                }
+                else if (!userProfile.getDepartment().isEmpty() &&
+                        !creatorProfile.getDepartment().isEmpty() &&
+                        isSameCollege(userProfile.getDepartment(), creatorProfile.getDepartment())) {
+                    score += 15.0;
+                }
+
+                int yearDiff = Math.abs(userProfile.getEntranceTime() - creatorProfile.getEntranceTime());
+                score += Math.max(0, 20 - yearDiff * 5);
+            }
+        }
+
+        score += Math.random() * 10;
+
+        return score;
+    }
+
+    private boolean isSameCollege(String dept1, String dept2) {
+        String college1 = extractCollege(dept1);
+        String college2 = extractCollege(dept2);
+        return !college1.isEmpty() && college1.equals(college2);
+    }
+
+    // 학과 -> 단과대 - 이건 나중에 수정해야됨
+    private String extractCollege(String department) {
+        if (department.contains("공학") || department.contains("응용화학")) return "공과대학";
+        if (department.contains("경영") || department.contains("경제")) return "경영대학";
+        if (department.contains("소프트") || department.contains("미디어")) return "소프트웨어융합";
+        if (department.contains("사회") || department.contains("정치")) return "사회과학대학";
+        if (department.contains("국어") || department.contains("불어")) return "인문대학";
+        return "";
+    }
+
 }


Reviews:
user: login: unie12
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-05T01:25:41Z
user: login: 1-J-1
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-05T01:28:30Z
user: login: 1-J-1
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-05T01:30:21Z
user: login: unie12
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-05T01:45:18Z

Review comments:
diff_hunk: @@ -82,4 +90,103 @@ public void cleanupMatchingKeys(Matching matching) {
 
     // 사용자 로그아웃 or 세션 종료될 때 count 캐시 지우는 것도?
 
+    // 프로필 기반한 가중치 계산
+    // 가중치 계산 기반한 매칭방 선택
+    public Long getRandomMatching(User user, InitiatorType userRole) {
+        InitiatorType targetRole = (userRole == InitiatorType.SPEAKER)
+                ? InitiatorType.LISTENER
+                : InitiatorType.SPEAKER;
+
+        String setKey = buildKey(MATCHING_SET_KEY, targetRole);
+        Set<String> matchingIds = redisTemplate.opsForSet().members(setKey);
path: src/main/java/com/mindmate/mindmate_server/matching/service/RedisMatchingService.java
user: login: unie12
body: 매칭 id 가져올때 매칭방 상태는 고려 안하는거야? 아니면 고려되는건가?
created_at: 2025-04-05T01:22:42Z
author_association: COLLABORATOR
diff_hunk: @@ -82,4 +90,103 @@ public void cleanupMatchingKeys(Matching matching) {
 
     // 사용자 로그아웃 or 세션 종료될 때 count 캐시 지우는 것도?
 
+    // 프로필 기반한 가중치 계산
+    // 가중치 계산 기반한 매칭방 선택
+    public Long getRandomMatching(User user, InitiatorType userRole) {
+        InitiatorType targetRole = (userRole == InitiatorType.SPEAKER)
+                ? InitiatorType.LISTENER
+                : InitiatorType.SPEAKER;
+
+        String setKey = buildKey(MATCHING_SET_KEY, targetRole);
+        Set<String> matchingIds = redisTemplate.opsForSet().members(setKey);
+
+        if (matchingIds == null || matchingIds.isEmpty()) {
+            return null;
path: src/main/java/com/mindmate/mindmate_server/matching/service/RedisMatchingService.java
user: login: unie12
body: 에러코드 날려주느게 좋겠다
created_at: 2025-04-05T01:23:11Z
author_association: COLLABORATOR
diff_hunk: @@ -82,4 +90,103 @@ public void cleanupMatchingKeys(Matching matching) {
 
     // 사용자 로그아웃 or 세션 종료될 때 count 캐시 지우는 것도?
 
+    // 프로필 기반한 가중치 계산
+    // 가중치 계산 기반한 매칭방 선택
+    public Long getRandomMatching(User user, InitiatorType userRole) {
+        InitiatorType targetRole = (userRole == InitiatorType.SPEAKER)
+                ? InitiatorType.LISTENER
+                : InitiatorType.SPEAKER;
+
+        String setKey = buildKey(MATCHING_SET_KEY, targetRole);
+        Set<String> matchingIds = redisTemplate.opsForSet().members(setKey);
path: src/main/java/com/mindmate/mindmate_server/matching/service/RedisMatchingService.java
user: login: unie12
body: 아 아래에 있구나
근데 그러면 여기서는 일단 모든 매칭 아이디를 가져오고 아래에서 랜덤 여부, 방 상태 등을 확인하는데 100개를 뽑았다가 해당 조건을 만족하는 방이 급격히 떨어질 수도 있지 않나?
먼저 조건을 걸어서 가져오고 거기서 제한하는건 무리인가?
created_at: 2025-04-05T01:25:27Z
author_association: COLLABORATOR
diff_hunk: @@ -82,4 +90,103 @@ public void cleanupMatchingKeys(Matching matching) {
 
     // 사용자 로그아웃 or 세션 종료될 때 count 캐시 지우는 것도?
 
+    // 프로필 기반한 가중치 계산
+    // 가중치 계산 기반한 매칭방 선택
+    public Long getRandomMatching(User user, InitiatorType userRole) {
+        InitiatorType targetRole = (userRole == InitiatorType.SPEAKER)
+                ? InitiatorType.LISTENER
+                : InitiatorType.SPEAKER;
+
+        String setKey = buildKey(MATCHING_SET_KEY, targetRole);
+        Set<String> matchingIds = redisTemplate.opsForSet().members(setKey);
path: src/main/java/com/mindmate/mindmate_server/matching/service/RedisMatchingService.java
user: login: 1-J-1
body: 아 이건 혹시 몰라서 중복하는 거에요!
애초에 이 매칭풀에 추가되는 조건이 방이 랜덤 매칭을 허용한 방이 생성될 때입니다.
그리고 매칭이 취소되거나 매칭되었을 때 이 풀에서 제거해줍니다!
created_at: 2025-04-05T01:28:29Z
author_association: COLLABORATOR
diff_hunk: @@ -82,4 +90,103 @@ public void cleanupMatchingKeys(Matching matching) {
 
     // 사용자 로그아웃 or 세션 종료될 때 count 캐시 지우는 것도?
 
+    // 프로필 기반한 가중치 계산
+    // 가중치 계산 기반한 매칭방 선택
+    public Long getRandomMatching(User user, InitiatorType userRole) {
+        InitiatorType targetRole = (userRole == InitiatorType.SPEAKER)
+                ? InitiatorType.LISTENER
+                : InitiatorType.SPEAKER;
+
+        String setKey = buildKey(MATCHING_SET_KEY, targetRole);
+        Set<String> matchingIds = redisTemplate.opsForSet().members(setKey);
+
+        if (matchingIds == null || matchingIds.isEmpty()) {
+            return null;
path: src/main/java/com/mindmate/mindmate_server/matching/service/RedisMatchingService.java
user: login: 1-J-1
body: `Long matchingId = redisMatchingService.getRandomMatching(user, userRole);

        if (matchingId == null) {
            throw new CustomException(MatchingErrorCode.NO_MATCHING_AVAILABLE);
        }`
        
여기서 확인하고 null이면 매칭서비스에서 날려주는 걸로 설정했는데 여기에 추가하는 게 낫다고 생각하시나요??
created_at: 2025-04-05T01:30:21Z
author_association: COLLABORATOR
diff_hunk: @@ -82,4 +90,103 @@ public void cleanupMatchingKeys(Matching matching) {
 
     // 사용자 로그아웃 or 세션 종료될 때 count 캐시 지우는 것도?
 
+    // 프로필 기반한 가중치 계산
+    // 가중치 계산 기반한 매칭방 선택
+    public Long getRandomMatching(User user, InitiatorType userRole) {
+        InitiatorType targetRole = (userRole == InitiatorType.SPEAKER)
+                ? InitiatorType.LISTENER
+                : InitiatorType.SPEAKER;
+
+        String setKey = buildKey(MATCHING_SET_KEY, targetRole);
+        Set<String> matchingIds = redisTemplate.opsForSet().members(setKey);
+
+        if (matchingIds == null || matchingIds.isEmpty()) {
+            return null;
path: src/main/java/com/mindmate/mindmate_server/matching/service/RedisMatchingService.java
user: login: unie12
body: 해당 예외 처리가 아예 없는 줄 알았어
이미 처리하면 상관없겠다
created_at: 2025-04-05T01:44:50Z
author_association: COLLABORATOR
diff_hunk: @@ -82,4 +90,103 @@ public void cleanupMatchingKeys(Matching matching) {
 
     // 사용자 로그아웃 or 세션 종료될 때 count 캐시 지우는 것도?
 
+    // 프로필 기반한 가중치 계산
+    // 가중치 계산 기반한 매칭방 선택
+    public Long getRandomMatching(User user, InitiatorType userRole) {
+        InitiatorType targetRole = (userRole == InitiatorType.SPEAKER)
+                ? InitiatorType.LISTENER
+                : InitiatorType.SPEAKER;
+
+        String setKey = buildKey(MATCHING_SET_KEY, targetRole);
+        Set<String> matchingIds = redisTemplate.opsForSet().members(setKey);
path: src/main/java/com/mindmate/mindmate_server/matching/service/RedisMatchingService.java
user: login: unie12
body: 아 오키오키 이해했습니다
created_at: 2025-04-05T01:45:11Z
author_association: COLLABORATOR

PR comments:
user: login: unie12
created_at: 2025-04-05T01:26:13Z
body: 트랜잭션 관리 개선이 어떤거를 말하는거야? 이 랜덤 매칭 하는 과정?
author_association: COLLABORATOR
user: login: 1-J-1
created_at: 2025-04-05T01:35:03Z
body: > 트랜잭션 관리 개선이 어떤거를 말하는거야? 이 랜덤 매칭 하는 과정?

그 매칭 수락하는 부분이요! 
매칭 거절 할 때 오류 생기면 매칭 수락까지 실패할 수 있어서요

카프카를 그대로 쓰고 있었는데 다른 걸로 구현 해야할지 고민이 돼서요
author_association: COLLABORATOR
user: login: unie12
created_at: 2025-04-05T01:46:14Z
body: > > 트랜잭션 관리 개선이 어떤거를 말하는거야? 이 랜덤 매칭 하는 과정?
> 
> 그 매칭 수락하는 부분이요! 매칭 거절 할 때 오류 생기면 매칭 수락까지 실패할 수 있어서요
> 
> 카프카를 그대로 쓰고 있었는데 다른 걸로 구현 해야할지 고민이 돼서요

그럼 혹시 해당 내용을 어떻게 설계했는지 얘기해줄 수 있어? 동작 과정이나 고민되는거?
author_association: COLLABORATOR
user: login: 1-J-1
created_at: 2025-04-05T02:12:38Z
body: > > > 트랜잭션 관리 개선이 어떤거를 말하는거야? 이 랜덤 매칭 하는 과정?
> > 
> > 
> > 그 매칭 수락하는 부분이요! 매칭 거절 할 때 오류 생기면 매칭 수락까지 실패할 수 있어서요
> > 카프카를 그대로 쓰고 있었는데 다른 걸로 구현 해야할지 고민이 돼서요
> 
> 그럼 혹시 해당 내용을 어떻게 설계했는지 얘기해줄 수 있어? 동작 과정이나 고민되는거?

아 원래 카프카로 이벤트 발행했어서 트랜잭션이 커밋된 후에 이벤트 발행을 하는정도?로 고쳤는데,
이벤트 발행 자체를 카프카로할지 아니면 다른 걸 사용할지를 고민중이었습니다!
author_association: COLLABORATOR
user: login: unie12
created_at: 2025-04-05T02:20:19Z
body: 아하 매칭 수락 후의 과정들을 처리하기 위해 카프카 이벤트를 발행했는데, 이렇게 할지 다른 방식으로 할지 고민중이었다?
성사됐을 떄 나중에 포인트 트랜잭션까지 생각한다면 카프카로 처리해도 좋을 것 같다
알림도 보내야 하고, 이런저런 값도 변경해야 할 것 같고
author_association: COLLABORATOR
user: login: 1-J-1
created_at: 2025-04-05T02:21:21Z
body: 하핳ㅎ 넵!!
author_association: COLLABORATOR