Skip to content

[REFACTOR] notification transaction flow - #79

Merged
Obebe-creator merged 2 commits into
devfrom
refactor/notification-transaction-flow
Aug 3, 2026
Merged

Obebe-creator merged 2 commits into
devfrom
refactor/notification-transaction-flow

Conversation

@Obebe-creator

@Obebe-creator Obebe-creator commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

📌 작업 내용

견적/리뷰 도메인의 알림 호출부를 변경 예정인 NotificationService 구조에 맞춰 선반영했습니다.

✅ 변경 사항

  • 견적 도착 알림 저장을 견적 생성 트랜잭션 내부로 이동
  • 견적 요청 반려 알림 저장을 반려 생성 트랜잭션 내부로 이동
  • 견적 확정 알림 저장을 견적 확정 트랜잭션 내부로 이동
  • 리뷰 도착 알림 저장을 리뷰 생성 및 기사 리뷰 통계 갱신 트랜잭션 내부로 이동
  • 각 트랜잭션 성공 이후 notificationService.sendNotification()으로 SSE 전송을 호출하도록 변경
  • 견적 확정 응답에 내부 알림 객체가 포함되지 않도록 반환값 분리

🧪 테스트

  • 서버 실행 확인
  • API 동작 확인
  • DB 연동 확인
  • 로그 확인
  • 기타:

테스트 방법

  1. 이번 PR은 변경 예정인 공통 NotificationService 인터페이스에 맞춘 호출부 선반영 작업입니다.

📷 스크린샷 (선택)

  • 없음

🔥 체크리스트

  • 코드 컨벤션을 준수했습니다.
  • 불필요한 console.log를 제거했습니다.
  • ESLint 오류가 없습니다.
  • 변경 사항을 직접 테스트했습니다.
  • 관련 문서를 업데이트했습니다. (필요 시)

🙏 To Reviewer

  • 이번 PR은 공통 notification.service.ts를 수정하지 않고, 견적/리뷰 도메인 호출부만 변경합니다.
  • 현재 dev 기준 build 오류는 예상된 상태이며, 알림 담당자의 NotificationService 구조 변경 PR이 반영되면 해소될 예정입니다.

Summary by CodeRabbit

  • 개선 사항
    • 견적 확정 시 담당 기사에게 알림이 정상적으로 발송됩니다.
    • 견적 전송 및 반려 시 관련 알림이 안정적으로 처리됩니다.
    • 리뷰 작성 완료 후 리뷰 등록 알림이 발송됩니다.
    • 이동일을 기준으로 견적 확정 정보의 만료 시각이 설정됩니다.
    • 알림 정보가 최종 응답에 불필요하게 포함되지 않아 기존 응답 형식이 유지됩니다.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

견적 확정, 견적 전송·반려, 리뷰 생성 과정에서 알림 생성을 트랜잭션 내부로 이동했습니다. 트랜잭션이 완료된 뒤 알림을 전송하고, 서비스 응답에는 기존 도메인 객체만 반환합니다.

Changes

트랜잭션 기반 알림 처리

Layer / File(s) Summary
견적 확정 알림 처리
src/modules/estimate/customer/customer-estimate.service.ts
견적 확정 트랜잭션에서 수신자, 유형, 내용, 링크, 만료 시각을 포함한 알림을 생성합니다. 커밋 후 알림을 전송하고 응답에서 알림을 제외합니다.
견적 전송·반려 알림 처리
src/modules/estimate/mover/mover-estimate.service.ts
견적 전송과 반려 트랜잭션에서 고객 알림을 생성합니다. 트랜잭션 결과의 알림을 커밋 후 전송합니다.
리뷰 알림 처리
src/modules/review/review.service.ts
리뷰 생성과 통계 갱신에 알림 생성을 포함합니다. 트랜잭션 결과에서 리뷰와 알림을 분리하고, 커밋 후 알림을 전송합니다.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Service
  participant Transaction
  participant Notification
  Service->>Transaction: 도메인 작업 및 알림 생성
  Transaction-->>Service: 도메인 객체와 알림 반환
  Service->>Notification: 트랜잭션 완료 후 알림 전송
  Service-->>Service: 응답에서 알림 제외
Loading

Possibly related PRs

Suggested reviewers: wkdalswn11, soooob43

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 알림 저장을 트랜잭션 내부로 이동하고 전송 흐름을 변경한 PR의 핵심 내용을 간결하게 설명합니다.
Description check ✅ Passed 변경 사항과 테스트 방법, 체크리스트, 리뷰 요청 사항을 포함해 템플릿을 대부분 충족합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/notification-transaction-flow

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/modules/estimate/customer/customer-estimate.service.ts`:
- Line 448: Update the notification content near the estimate creation flow to
be a complete sentence, including the customer name and a clear statement that
the estimate has been confirmed, rather than ending with the possessive particle
in isolation.
- Around line 443-453: customer-estimate.service.ts 443-453,
mover-estimate.service.ts 259-269 및 359-369, review.service.ts 256-266의
createNotification 호출에 동일한 tx를 전달하세요. NotificationService.createNotification이
DbClient를 받아 notificationRepository.create에 전달하고 도메인 변경과 같은 트랜잭션으로 저장하도록 수정하며,
SSE 전송은 커밋 이후 수행하세요. 공유 notificationService export에 호출부가 사용하는 sendNotification
메서드도 포함하세요.

In `@src/modules/review/review.service.ts`:
- Line 261: Update the notification payload’s content near the review creation
flow so it stores the complete user-facing notification sentence rather than
only the fragment "고객님이". Include all wording and dynamic values required for
the client to display a complete message, using the surrounding payload data and
existing notification conventions.
- Around line 256-279: Update the review transaction around createdReview to
call createNotification with only the CreateNotificationInput argument, removing
the unsupported tx parameter. Use the supported notification creation and
post-commit delivery APIs instead of notificationService.sendNotification,
ensuring the notification is persisted within the review transaction and its SSE
event is emitted only after the transaction commits.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8cf0c63a-304a-48d8-bb9b-ee2b0c6544e5

📥 Commits

Reviewing files that changed from the base of the PR and between 75c7e69 and 29d399a.

📒 Files selected for processing (3)
  • src/modules/estimate/customer/customer-estimate.service.ts
  • src/modules/estimate/mover/mover-estimate.service.ts
  • src/modules/review/review.service.ts

Comment on lines +443 to +453
const notification = await notificationService.createNotification(
{
userId: confirmedEstimate.mover.id,
type: "ESTIMATE_CONFIRMED",
title: "견적 확정",
content: `${estimate.estimateRequest.customer.name}님의`,
linkUrl: "/estimate/received-requests",
expiresAt: getKstEndOfDay(estimate.estimateRequest.moveDate),
},
tx,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a '^notification\.service\.ts$' src -x ast-grep outline {} --items all
fd -a '^notification\.service\.ts$' src -x rg -n -C 8 'createNotification|sendNotification' {}
rg -n -C 6 'createNotification\(' \
  src/modules/estimate/customer/customer-estimate.service.ts \
  src/modules/estimate/mover/mover-estimate.service.ts

Repository: 4roro-moving/moving-backend

Length of output: 5511


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- notification service ---'
sed -n '1,230p' src/modules/notification/notification.service.ts

printf '%s\n' '--- notification repository ---'
sed -n '1,180p' src/modules/notification/notification.repository.ts

printf '%s\n' '--- transaction utility and notification types ---'
sed -n '1,220p' src/utils/transaction.ts
rg -n -C 5 'CreateNotificationInput|NotificationItem|PrismaClient|TransactionClient' \
  src/modules/notification src/modules/estimate

printf '%s\n' '--- all createNotification call shapes ---'
rg -n -U -P 'createNotification\s*\([^;]*?\)' src --glob '*.ts' || true

Repository: 4roro-moving/moving-backend

Length of output: 21480


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- estimate and review transaction contexts ---'
sed -n '220,280p' src/modules/estimate/mover/mover-estimate.service.ts
sed -n '330,380p' src/modules/estimate/mover/mover-estimate.service.ts
sed -n '410,465p' src/modules/estimate/customer/customer-estimate.service.ts
sed -n '230,275p' src/modules/review/review.service.ts

printf '%s\n' '--- notification repository create implementation ---'
sed -n '235,275p' src/modules/notification/notification.repository.ts

printf '%s\n' '--- static call/signature verifier ---'
python3 - <<'PY'
from pathlib import Path
import re

service = Path("src/modules/notification/notification.service.ts").read_text()
match = re.search(
    r"const\s+createNotification\s*=\s*async\s*\((.*?)\)\s*:\s*Promise",
    service,
    re.S,
)
if not match:
    raise SystemExit("createNotification declaration not found")

params = [p.strip() for p in match.group(1).split(",") if p.strip()]
print(f"notificationService.createNotification declared parameters: {len(params)}")
print("\n".join(f"  {i + 1}: {p}" for i, p in enumerate(params)))

for path in Path("src/modules").rglob("*.service.ts"):
    text = path.read_text()
    for m in re.finditer(r"notificationService\.createNotification\s*\(", text):
        start = m.start()
        depth = 0
        end = None
        for i in range(text.find("(", start), len(text)):
            if text[i] == "(":
                depth += 1
            elif text[i] == ")":
                depth -= 1
                if depth == 0:
                    end = i + 1
                    break
        if end is None:
            continue
        args = text[text.find("(", start) + 1:end - 1]
        # Top-level commas count arguments without executing repository code.
        level = 0
        commas = 0
        for ch in args:
            if ch in "({[":
                level += 1
            elif ch in ")}]":
                level -= 1
            elif ch == "," and level == 0:
                commas += 1
        print(f"{path}:{text[:start].count(chr(10)) + 1}: call arguments={commas + 1}")
PY

Repository: 4roro-moving/moving-backend

Length of output: 7560


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- estimate and review transaction contexts ---'
sed -n '220,280p' src/modules/estimate/mover/mover-estimate.service.ts
sed -n '330,380p' src/modules/estimate/mover/mover-estimate.service.ts
sed -n '410,465p' src/modules/estimate/customer/customer-estimate.service.ts
sed -n '230,275p' src/modules/review/review.service.ts

printf '%s\n' '--- notification repository create implementation ---'
sed -n '235,275p' src/modules/notification/notification.repository.ts

printf '%s\n' '--- static call/signature verifier ---'
python3 - <<'PY'
from pathlib import Path
import re

service = Path("src/modules/notification/notification.service.ts").read_text()
match = re.search(
    r"const\s+createNotification\s*=\s*async\s*\((.*?)\)\s*:\s*Promise",
    service,
    re.S,
)
if not match:
    raise SystemExit("createNotification declaration not found")

params = [p.strip() for p in match.group(1).split(",") if p.strip()]
print(f"notificationService.createNotification declared parameters: {len(params)}")
print("\n".join(f"  {i + 1}: {p}" for i, p in enumerate(params)))

for path in Path("src/modules").rglob("*.service.ts"):
    text = path.read_text()
    for m in re.finditer(r"notificationService\.createNotification\s*\(", text):
        start = m.start()
        depth = 0
        end = None
        for i in range(text.find("(", start), len(text)):
            if text[i] == "(":
                depth += 1
            elif text[i] == ")":
                depth -= 1
                if depth == 0:
                    end = i + 1
                    break
        if end is None:
            continue
        args = text[text.find("(", start) + 1:end - 1]
        level = 0
        commas = 0
        for ch in args:
            if ch in "({[":
                level += 1
            elif ch in ")}]":
                level -= 1
            elif ch == "," and level == 0:
                commas += 1
        print(f"{path}:{text[:start].count(chr(10)) + 1}: call arguments={commas + 1}")
PY

Repository: 4roro-moving/moving-backend

Length of output: 7560


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- notification service exports and send calls ---'
rg -n -C 4 'sendNotification|export const notificationService|notificationService' \
  src/modules/notification/notification.service.ts \
  src/modules/estimate/mover/mover-estimate.service.ts \
  src/modules/estimate/customer/customer-estimate.service.ts \
  src/modules/review/review.service.ts

printf '%s\n' '--- imports of notification service ---'
rg -n -C 3 'notificationService' \
  src/modules/estimate/mover/mover-estimate.service.ts \
  src/modules/estimate/customer/customer-estimate.service.ts \
  src/modules/review/review.service.ts

Repository: 4roro-moving/moving-backend

Length of output: 16649


NotificationService의 트랜잭션 및 SSE 계약을 함께 반영하세요.

createNotification은 현재 인자 하나만 받지만, 다음 네 위치는 tx를 전달하므로 컴파일 오류가 발생합니다.

  • src/modules/estimate/customer/customer-estimate.service.ts:443
  • src/modules/estimate/mover/mover-estimate.service.ts:259
  • src/modules/estimate/mover/mover-estimate.service.ts:359
  • src/modules/review/review.service.ts:256

notificationRepository.create가 지원하는 DbClientNotificationService까지 전달하세요. 알림 저장은 도메인 변경과 같은 트랜잭션에서 수행해야 합니다. SSE 전송은 트랜잭션 커밋 후 수행하세요.

또한 호출부의 notificationService.sendNotification(...) 사용에 맞게 공유 서비스가 해당 메서드를 export해야 합니다. 현재 notificationService export에는 이 메서드가 없습니다.

📍 Affects 2 files
  • src/modules/estimate/customer/customer-estimate.service.ts#L443-L453 (this comment)
  • src/modules/estimate/mover/mover-estimate.service.ts#L259-L269
  • src/modules/estimate/mover/mover-estimate.service.ts#L359-L369
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/estimate/customer/customer-estimate.service.ts` around lines 443
- 453, customer-estimate.service.ts 443-453, mover-estimate.service.ts 259-269 및
359-369, review.service.ts 256-266의 createNotification 호출에 동일한 tx를 전달하세요.
NotificationService.createNotification이 DbClient를 받아
notificationRepository.create에 전달하고 도메인 변경과 같은 트랜잭션으로 저장하도록 수정하며, SSE 전송은 커밋 이후
수행하세요. 공유 notificationService export에 호출부가 사용하는 sendNotification 메서드도 포함하세요.

Source: Path instructions

userId: confirmedEstimate.mover.id,
type: "ESTIMATE_CONFIRMED",
title: "견적 확정",
content: `${estimate.estimateRequest.customer.name}님의`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

알림 내용을 완전한 문장으로 설정하세요.

현재 알림 본문은 홍길동님의처럼 조사로 끝납니다. 수신자는 알림 본문만으로 이벤트를 이해할 수 없습니다. 예를 들어 "${estimate.estimateRequest.customer.name}님의 견적이 확정되었습니다."처럼 완료된 내용을 저장하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/estimate/customer/customer-estimate.service.ts` at line 448,
Update the notification content near the estimate creation flow to be a complete
sentence, including the customer name and a clear statement that the estimate
has been confirmed, rather than ending with the possessive particle in
isolation.

Comment on lines +256 to +279
const notification = await notificationService.createNotification(
{
userId: estimate.moverId,
type: "REVIEW_RECEIVED",
title: "리뷰 도착",
content: "고객님이",
linkUrl: null,
expiresAt: null,
},
tx,
);

return {
review: createdReview,
notification,
};
},
{
// 같은 기사님에게 여러 리뷰가 동시에 등록될 때 통계 재계산 결과가 덮어써지는 것을 방지
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
},
);

await notificationService.createNotification({
userId: estimate.moverId,
type: "REVIEW_RECEIVED",
title: "리뷰 도착",
content: "고객님이",
linkUrl: null,
expiresAt: null,
});
notificationService.sendNotification(estimate.moverId, result.notification);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# NotificationService 정의와 호출 계약을 확인합니다.
fd -t f '^notification\.service\.ts$' src | while IFS= read -r file; do
  ast-grep outline "$file" --items all
  rg -n -C 8 '\b(createNotification|sendNotification)\s*\(' "$file"
done

# 서비스 호출부와 전달 인자를 확인합니다.
rg -n -C 6 '\b(createNotification|sendNotification)\s*\(' src

Repository: 4roro-moving/moving-backend

Length of output: 11215


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- notification.service.ts ---'
cat -n src/modules/notification/notification.service.ts | sed -n '1,235p'

printf '%s\n' '--- notification types and repository ---'
fd -t f . src/modules/notification | sort
rg -n -C 5 'CreateNotificationInput|NotificationItem|notificationService|sendNotification|createNotification' src/modules/notification src/modules/review/review.service.ts

printf '%s\n' '--- review imports and transaction context ---'
cat -n src/modules/review/review.service.ts | sed -n '1,40p;210,290p'

Repository: 4roro-moving/moving-backend

Length of output: 27822


NotificationService 호출 계약을 수정하세요.

createNotificationCreateNotificationInput 하나만 받으며 tx를 지원하지 않습니다. notificationService에는 sendNotification도 없습니다. 현재 코드는 컴파일되지 않습니다. 또한 알림을 리뷰 트랜잭션에 포함하고 커밋 후 SSE를 전송하도록 호출 구조를 수정해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/review/review.service.ts` around lines 256 - 279, Update the
review transaction around createdReview to call createNotification with only the
CreateNotificationInput argument, removing the unsupported tx parameter. Use the
supported notification creation and post-commit delivery APIs instead of
notificationService.sendNotification, ensuring the notification is persisted
within the review transaction and its SSE event is emitted only after the
transaction commits.

userId: estimate.moverId,
type: "REVIEW_RECEIVED",
title: "리뷰 도착",
content: "고객님이",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

완전한 알림 본문을 저장하세요.

Line 261의 content"고객님이"만 저장합니다. 이 payload에는 클라이언트가 문장을 완성할 값이 없습니다. 사용자는 불완전한 알림을 받습니다.

수정 예시
-              content: "고객님이",
+              content: "고객님이 리뷰를 남겼습니다.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
content: "고객님이",
content: "고객님이 리뷰를 남겼습니다.",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/review/review.service.ts` at line 261, Update the notification
payload’s content near the review creation flow so it stores the complete
user-facing notification sentence rather than only the fragment "고객님이". Include
all wording and dynamic values required for the client to display a complete
message, using the surrounding payload data and existing notification
conventions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

견적 확정과 알림 DB 저장을 동일 트랜잭션에 포함하고,
생성된 알림을 반환한 뒤 커밋 이후 sendNotification()으로 SSE를 전송하도록
의도한 구조에 맞게 잘 수정된 것 같습니다.

고생하셨습니다¡

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

견적 제안·반려 모두 알림 저장을 핵심 작업과 같은 트랜잭션에 포함하고,
커밋 이후 sendNotification()으로 SSE를 전송하도록
의도한 구조에 맞게 잘 수정된 것 같습니다.

고생하셨어요¡

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알림 저장과 SSE 전송 구조는 의도한 방식대로 잘 적용된 것 같습니다.

리뷰 생성·기사 리뷰 통계 갱신·알림 DB 저장을 동일 트랜잭션으로 처리하고,
커밋 이후 sendNotification()으로 SSE를 전송하도록 정상적으로 분리되어 있습니다.

고생하셨어용¡

@Obebe-creator
Obebe-creator merged commit 852df28 into dev Aug 3, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants