Skip to content

[Volume 3] 도메인 모델링 및 구현#122

Open
dd-jiny wants to merge 3 commits intoLoopers-dev-lab:dd-jinyfrom
dd-jiny:volume-3
Open

[Volume 3] 도메인 모델링 및 구현#122
dd-jiny wants to merge 3 commits intoLoopers-dev-lab:dd-jinyfrom
dd-jiny:volume-3

Conversation

@dd-jiny
Copy link

@dd-jiny dd-jiny commented Feb 27, 2026

📌 Summary

  • 배경: 이커머스 백엔드의 전체 도메인(회원, 브랜드, 상품, 좋아요, 장바구니, 주문, 통계)을 요구사항 명세에 따라 설계하고 구현해야 한다.
  • 목표: "좋아요 → 장바구니 → 주문(결제 대기)" 핵심 흐름과 관리자 운영 API를 완성하고, TDD 기반으로 전 레이어에 걸친 테스트를 확보한다.
  • 결과: 276개 파일 변경(+24,847 / -3,028), 8개 도메인 전체 구현 완료. 도메인 단위 테스트부터 E2E 테스트까지 포괄적 테스트 커버리지를 확보하였다.

아키텍처 구성도

멘토링 이전

image image

멘토링 이후

image
graph TB
    subgraph INTERFACES["1. 인터페이스 레이어 (interfaces)"]
        direction LR
        API["<b>고객 API</b><br/>UserV1Controller<br/>BrandV1Controller<br/>ProductV1Controller<br/>LikeV1Controller<br/>CartV1Controller<br/>OrderV1Controller"]
        ADMIN["<b>관리자 API</b><br/>AdminBrandV1Controller<br/>AdminProductV1Controller<br/>AdminOrderV1Controller<br/>AdminCartV1Controller<br/>AdminStatsV1Controller<br/>AdminAuthInterceptor"]
        DTO["<b>공통</b><br/>ApiResponse&lt;T&gt;<br/>PageResponse&lt;T&gt;<br/>ApiControllerAdvice<br/>WebMvcConfig<br/>*V1Dto (Request/Response)"]
    end

    subgraph APPLICATION["2. 애플리케이션 레이어 (application)"]
        direction LR
        FACADE["<b>Facade (복잡한 도메인)</b><br/>ProductFacade<br/>CartFacade<br/>OrderFacade"]
        APPSERVICE["<b>AppService (단순 도메인)</b><br/>UserAppService<br/>BrandAppService<br/>LikeAppService<br/>StatsAppService<br/><b>AppService (복잡한 도메인 보조)</b><br/>ProductAppService<br/>CartAppService<br/>OrderAppService"]
        INFO["<b>Info DTO</b><br/>UserInfo<br/>BrandInfo<br/>ProductInfo / ProductRevisionInfo<br/>LikeInfo<br/>CartInfo<br/>OrderInfo<br/>StatsInfo"]
    end

    subgraph DOMAIN["3. 도메인 레이어 (domain)"]
        direction LR
        SERVICE["<b>Service</b><br/>UserService<br/>BrandService<br/>ProductService<br/>StockService<br/>LikeService<br/>CartService<br/>OrderService<br/>StatsService"]
        MODEL["<b>Model (Entity)</b><br/>UserModel<br/>BrandModel<br/>ProductModel<br/>ProductStockModel<br/>ProductRevisionModel<br/>LikeModel<br/>CartItemModel<br/>OrderModel<br/>OrderItemModel<br/>OrderCartRestoreModel"]
        REPO_IF["<b>Repository (Interface)</b><br/>UserRepository<br/>BrandRepository<br/>ProductRepository<br/>ProductStockRepository<br/>ProductRevisionRepository<br/>LikeRepository<br/>CartItemRepository<br/>OrderRepository<br/>OrderItemRepository<br/>OrderCartRestoreRepository<br/>StatsRepository"]
    end

    subgraph INFRA["4. 인프라스트럭처 레이어 (infrastructure)"]
        direction LR
        REPO_IMPL["<b>RepositoryImpl</b><br/>UserRepositoryImpl<br/>BrandRepositoryImpl<br/>ProductRepositoryImpl<br/>ProductStockRepositoryImpl<br/>ProductRevisionRepositoryImpl<br/>LikeRepositoryImpl<br/>CartItemRepositoryImpl<br/>OrderRepositoryImpl<br/>OrderItemRepositoryImpl<br/>OrderCartRestoreRepositoryImpl<br/>StatsRepositoryImpl"]
        JPA_REPO["<b>JpaRepository</b><br/>UserJpaRepository<br/>BrandJpaRepository<br/>ProductJpaRepository<br/>ProductStockJpaRepository<br/>(CAS UPDATE @Query)<br/>ProductRevisionJpaRepository<br/>LikeJpaRepository<br/>CartItemJpaRepository<br/>OrderJpaRepository<br/>(CAS 상태 전이 @Query)<br/>OrderItemJpaRepository<br/>OrderCartRestoreJpaRepository"]
    end

    subgraph SUPPORT["5. 서포트 (support)"]
        direction LR
        ERR["CoreException<br/>ErrorType (enum)<br/>GlobalExceptionHandler"]
        ENUM["DisplayStatus<br/>ProductSaleStatus<br/>ProductSortType<br/>OrderType / OrderStatus<br/>ProductRevisionAction<br/>UnavailableReason<br/>RestoreReason / RestoreTriggerSource"]
        UTIL["ProductAvailabilityChecker"]
    end

    subgraph BATCH["6. 배치 (batch)"]
        SCHED["OrderExpiryScheduler<br/>(1분 주기 만료 처리)"]
    end

    %% 의존 방향 (depends on)
    API -->|depends on| APPSERVICE
    API -->|depends on| FACADE
    ADMIN -->|depends on| APPSERVICE
    ADMIN -->|depends on| FACADE
    DTO -->|depends on| INFO
    APPSERVICE -->|depends on| SERVICE
    FACADE -->|depends on| SERVICE
    SERVICE -->|depends on| REPO_IF
    SERVICE -->|depends on| MODEL
    BATCH -->|depends on| FACADE

    %% 구현 관계 (implements)
    REPO_IMPL -.->|implements| REPO_IF
    JPA_REPO -.->|delegates| REPO_IMPL

    %% 인프라 → 도메인 Model 의존
    REPO_IMPL -->|depends on| MODEL
    JPA_REPO -->|depends on| MODEL

    %% 스타일
    style INTERFACES fill:#4a90d9,color:#fff,stroke:#2c5f8a
    style APPLICATION fill:#7bc96f,color:#fff,stroke:#4a8a3f
    style DOMAIN fill:#f5a623,color:#fff,stroke:#c47d12
    style INFRA fill:#9b59b6,color:#fff,stroke:#6c3483
    style SUPPORT fill:#95a5a6,color:#fff,stroke:#7f8c8d
    style BATCH fill:#e74c3c,color:#fff,stroke:#c0392b
Loading

리뷰 포인트

저번 멘토링 이후, 제가 지금까지 익혀 온 구현 방식과 표현 방식이 적절한지 다시 점검해보는 시간을 가졌습니다.

제가 이해한 애플리케이션 레이어는 “사용자의 업무 시나리오(UseCase)를 실행시키는 오케스트레이터 레이어”입니다.
그리고 단순한 로직의 경우에는 굳이 애플리케이션 레이어를 두지 않고, 인터페이스(Controller)에서 도메인 서비스를 호출한 뒤 응답 DTO로 변환해 반환해도 된다고 생각했었습니다.

이 관점을 가지고 프로젝트를 구현해보니, 아래 고민이 생겼습니다.

  1. 단순 로직의 경우에도 애플리케이션 레이어(AppService)를 반드시 만들어야 하는가?
  • 단순 조회/간단한 처리에서 “도메인 서비스 1~2줄 호출만 하는 AppService”를 별도로 두는 것이 과연 효율적인지 의문이 들었습니다.
  • 그래서 초기에는 단순 로직은 Controller → Domain Service 호출 → DTO 변환/반환 흐름으로 구현하기도 했습니다.
    (이때 변환 DTO를 도메인 쪽에 두는 방식도 일부 사용했습니다.)

멘토링 이후에는, 애플리케이션 레이어를 “애플리케이션 레이어답게” 설계해보자는 방향으로 개선해보았습니다.

  • 변환 DTO(Info/Result 등)를 애플리케이션 레이어로 이동
  • 단순 로직들도 가능한 한 애플리케이션 레이어의 AppService로 묶어서 처리
  • 컨트롤러는 요청/응답 변환과 호출만 담당하도록 얇게 유지

다만 이렇게 구조를 바꿔 구현을 진행하다 보니, 다시 다음 궁금증이 생겼습니다.

  • 제가 개선한 방향이 “과하게 형식적인 구조”가 된 것은 아닌지
  • 단순 로직까지 모두 AppService로 감싸는 것이 실제 실무에서도 적절한지
  • 애플리케이션 레이어를 도입/생략하는 판단 기준을 어떻게 잡는 것이 좋은지

이번 멘토링에서는 위 고민을 중심으로, 제가 구현한 애플리케이션 레이어 소스코드를 기준으로
현재 구조의 장단점과 개선 방향(필요시 유지/분리/축소 기준) 을 리뷰받고 싶습니다.

감사합니다.

🧭 Context & Decision

문제 정의

  • 현재 동작/제약: 기존 템플릿은 Example 도메인만 존재하며, 멀티모듈 구조와 인프라 설정(JPA, Redis, Kafka)만 갖춰진 상태. 실질적인 비즈니스 로직이 없다.
  • 문제(또는 리스크): 이커머스 핵심 흐름(좋아요 → 장바구니 → 주문)을 전체 레이어에 걸쳐 구현해야 하며, 재고 동시성·주문 상태 전이·장바구니 복원 멱등성 등 경합 시나리오에 대한 안전한 설계가 필요하다.
  • 성공 기준(완료 정의): 8개 도메인(User, Brand, Product, ProductStock, Like, Cart, Order, Stats)이 4-Layer 아키텍처 규칙을 준수하여 동작하고, 고객 API·관리자 API·배치 스케줄러가 모두 정상 동작한다.

선택지와 결정

1. 재고 관리 동시성 전략

  • 고려한 대안:
    • A: 비관적 락 (SELECT … FOR UPDATE) — 단순하지만 락 대기로 처리량 저하 우려. 트랜잭션이 길어질수록 병목이 심해진다.
    • B: CAS(Compare-And-Set) 조건부 UPDATE — 락 없이 WHERE 절 조건 검증으로 오버셀 방지. 실패 시 즉시 예외 발생.
    • C: Redis 분산 락 (Redisson 등) — DB 락 없이 분산 환경에서도 동작하지만 인프라 복잡도 증가.
  • 최종 결정: CAS 방식(B안) 채택.
    • hold: UPDATE stock SET reserved = reserved + :qty WHERE product_id = :id AND (on_hand - reserved) >= :qty
    • release: UPDATE stock SET reserved = reserved - :qty WHERE product_id = :id AND reserved >= :qty
    • affectedRows = 0이면 CoreException(STOCK_NOT_ENOUGH) 즉시 발생
  • 트레이드오프: CAS 실패 시 재시도 로직 없이 즉시 실패 처리 (MVP 단계에서 단순성 우선). 고빈도 경합 상황에서는 실패율이 높아질 수 있으나, MVP 트래픽 수준에서는 문제없다.
  • 데드락 방지: 다건 주문 시 productId 오름차순 정렬 후 순차적으로 hold → 항상 같은 순서로 row-level lock 획득.

2. Application 레이어 패턴 (AppService vs Facade)

  • 고려한 대안:
    • A: 모든 도메인에 Facade — 일관성은 있지만 단순 도메인(User, Brand 등)에서 pass-through 코드가 발생하여 불필요한 추상화 레이어가 된다.
    • B: Facade 없이 Controller → Service 직접 — 레이어가 줄어들지만 Model이 interfaces 레이어에 노출되어 아키텍처 원칙 위반.
    • C: AppService(단순) + Facade(복잡) 병행 — 단순 도메인은 AppService가 Model → Info 변환만 수행, 복잡한 도메인은 Facade가 여러 서비스를 오케스트레이션.
  • 최종 결정: C안 채택.
    • 단순 도메인(User, Brand, Like, Stats): Controller → AppService → Service
    • 복잡한 도메인(Product, Cart, Order): Controller → Facade(생성/취소 등) + AppService(조회/삭제 등) → 여러 Service
    • 모든 도메인에 AppService가 존재하여 Model이 interfaces 레이어에 절대 노출되지 않는다.
  • 트레이드오프: 복잡한 도메인은 Facade와 AppService가 공존하여 진입점이 두 개지만, 역할 분리가 명확하다 (오케스트레이션 vs 단순 위임).

3. 엔티티 PK 전략 (BaseStringIdEntity)

  • 고려한 대안:
    • A: Long auto-increment PK (기존 BaseEntity) — 단순하지만 ID 예측 가능, 분산 환경 확장 어려움.
    • B: UUID String PK (BaseStringIdEntity 신규) — 분산 환경에서 충돌 없는 ID 생성, 외부 노출 시 보안성 향상.
  • 최종 결정: B안 채택. 신규 도메인 전체(User, Brand, Product, Order 등)에 BaseStringIdEntity 적용. @UuidGenerator로 36자 UUID 자동 생성. PK 컬럼명은 서브클래스에서 직접 정의(user_id, brand_id, product_id, order_id).
  • 트레이드오프: UUID는 Long 대비 인덱스 크기가 크고 JOIN 성능이 약간 떨어지지만, MVP 규모에서는 무시할 수 있는 수준이다.

4. 주문 상태 머신 설계

  • 고려한 대안:
    • A: 전체 상태 구현 (PENDING → PAID → SHIPPED → DELIVERED → CANCELLED) — 완전하지만 결제 연동이 없는 MVP에서는 과도한 복잡도.
    • B: MVP 최소 상태 (PENDING_PAYMENT → CANCELLED / EXPIRED) — 결제 대기까지만 구현하고, 결제 완료 이후 상태는 Phase2에서 확장.
  • 최종 결정: B안 채택. OrderStatus enum에 PENDING_PAYMENT, CANCELLED, EXPIRED 3개 상태만 정의.
    • 상태 전이는 CAS UPDATE로 경쟁 조건 방지: UPDATE orders SET status = :to WHERE order_id = :id AND status = :from
    • canCancel() 헬퍼로 전이 가능 여부를 enum에 캡슐화.
    • 만료 처리: OrderExpiryScheduler가 1분 주기로 expiresAt < NOW() 대상을 조회하여 EXPIRED로 전이.
  • 추후 개선: Phase2에서 PAID, PAYMENT_FAILED 상태를 추가하고, PG 웹훅 연동 시 SHIPPED, DELIVERED 확장 가능.

5. 장바구니 복원 멱등성

  • 문제: 주문 취소/만료 시 장바구니를 복원해야 하는데, 동일 주문에 대해 복원이 중복 실행되면 수량이 2배로 늘어나는 문제가 발생한다.
  • 고려한 대안:
    • A: try-catch DataIntegrityViolationException — 중복 INSERT 시 예외를 잡아 무시. DB 예외에 의존하므로 의도가 불명확하다.
    • B: order_cart_restore 테이블 existsById 명시적 확인 — 복원 전 이력 존재 여부를 먼저 확인하고, 없으면 복원 + 이력 INSERT.
  • 최종 결정: B안 채택. OrderCartRestoreModel의 PK를 orderId로 설정하여 주문당 1회 복원을 DB 레벨에서 보장. DIRECT 주문만 복원 대상 (CART 주문은 장바구니 유지).
  • 트레이드오프: 별도 테이블이 필요하지만, 의도가 명확하고 디버깅이 용이하다. RestoreReason(USER_CANCELLED, EXPIRED 등)과 RestoreTriggerSource(CANCEL_API, EXPIRE_JOB 등)를 기록하여 추적성도 확보했다.

6. 소프트 삭제와 연쇄 처리

  • 문제: 브랜드 삭제 시 소속 상품을 어떻게 처리할 것인가.
  • 최종 결정: 브랜드 softDelete() 시 해당 브랜드의 모든 상품도 연쇄 소프트 삭제. del_yn='Y' + deletedAt 이중 관리로 삭제 여부를 명확히 표현. 복구(restore())는 멱등 처리.
  • 고객 조회 조건: 항상 del_yn='N' AND display_status='ACTIVE'로 필터링하여 삭제/숨김 상품이 노출되지 않도록 보장.

7. 테스트 전략

  • 결정: 4-Layer 각 레이어별로 적합한 테스트 유형을 적용.
    • 도메인 단위 테스트 (*Test): Mockito + AssertJ. 비즈니스 규칙 검증에 집중.
    • Repository 통합 테스트 (*RepositoryImplTest): @DataJpaTest + Testcontainers MySQL. 실제 SQL 동작 검증.
    • E2E 테스트 (*E2ETest): @SpringBootTest + MockMvc + @Transactional. 전체 레이어 관통 테스트.
    • 동시성 테스트 (StockConcurrencyTest): ExecutorService + CountDownLatch로 CAS 재고 경합 시나리오 검증.
    • 통합 테스트 (FullOrderFlowIntegrationTest): 좋아요 → 장바구니 → 주문 → 취소 전체 흐름을 하나의 시나리오로 검증.
  • 추후 개선 여지: 결제 연동(PAID/PAYMENT_FAILED 상태), 분산 락(Redis) 기반 재고 관리, 이벤트 기반 비동기 처리

🏗️ Design Overview

변경 범위

  • 영향 받는 모듈/도메인:
    • apps/commerce-api — 132 파일 신규/수정 (프로덕션), 54 파일 (테스트)
    • modules/jpa, modules/redis, modules/kafka — 20 파일 수정 (인프라 설정, BaseEntity 개선)
    • supports/jackson, supports/logging, supports/monitoring — 13 파일 수정
    • docs/design/ — 설계 문서 8 파일 추가/수정
  • 신규 추가:
    • 8개 도메인 전체 레이어 (Model, Repository, Service, AppService/Facade, Controller, DTO)
    • BaseStringIdEntity — UUID 기반 String PK 엔티티 베이스 클래스
    • 관리자 API (/api-admin/v1) — 브랜드·상품·장바구니·주문·통계 관리
    • OrderExpiryScheduler — PENDING_PAYMENT 주문 만료 배치
    • ProductAvailabilityChecker — 장바구니 항목 주문 가능 여부 판별 유틸
  • 제거/대체: 없음 (기존 Example 도메인 유지)

주요 컴포넌트 책임

도메인 모델 & 서비스

  • UserModel / UserService: 회원가입, 인증, 비밀번호 변경. 이름 마스킹, 비밀번호 규칙 검증을 도메인 모델에 캡슐화
  • BrandModel / BrandService: 브랜드 CRUD, 소프트 삭제. display_status 기반 노출 관리
  • ProductModel / ProductService: 상품 CRUD, sale_status 관리, 수정/삭제/복구 시 ProductRevision 이력 자동 기록
  • ProductStockModel / StockService: CAS 기반 재고 hold/release. 오버셀 방지 조건부 UPDATE
  • LikeModel / LikeService: 좋아요 등록/취소 (멱등). 복합 PK(userId + productId)
  • CartItemModel / CartService: 장바구니 CRUD, 수량 제한(최대 10개). 복합 PK(userId + productId)
  • OrderModel / OrderService: 주문 생성(DIRECT/CART), 취소, 만료. 상태 머신(PENDING_PAYMENT → CANCELLED/EXPIRED)
  • StatsService: 주문 현황·인기 상품 집계 쿼리 (Projection → Info 변환)

애플리케이션 레이어

  • UserAppService, BrandAppService, LikeAppService, StatsAppService: 단순 도메인 — 단일 서비스 호출 + Model → Info 변환
  • ProductFacade, CartFacade, OrderFacade: 복잡한 도메인 — 여러 서비스 조합(재고 hold/release, 장바구니 복원 등)

인프라스트럭처

  • *RepositoryImpl*JpaRepository 위임. 도메인 Repository 인터페이스 구현
  • BCryptPasswordEncoder: 도메인 PasswordEncoder 인터페이스의 BCrypt 구현체 (DIP)

지원 모듈

  • ErrorType enum 확장: 도메인별 에러 코드 29종 정의
  • UnavailableReason enum: 장바구니 항목 주문 불가 사유를 정적 팩토리로 판별
  • OrderStatus / ProductSaleStatus: 상태 전이 규칙을 enum에 캡슐화 (canCancel(), isOrderable())

🔁 Flow Diagram

Main Flow — 주문 생성 (DIRECT)

sequenceDiagram
  autonumber
  participant Client
  participant Controller as OrderV1Controller
  participant Facade as OrderFacade
  participant OrderSvc as OrderService
  participant StockSvc as StockService
  participant ProductSvc as ProductService
  participant DB

  Client->>Controller: POST /api/v1/orders (DIRECT)
  Controller->>Facade: createOrder(command)
  Facade->>ProductSvc: findById(productId)
  ProductSvc->>DB: SELECT product
  DB-->>ProductSvc: ProductModel
  Facade->>StockSvc: hold(productId, qty) [CAS]
  StockSvc->>DB: UPDATE stock SET reserved += qty WHERE (on_hand - reserved) >= qty
  DB-->>StockSvc: affectedRows (1 or 0)
  Facade->>OrderSvc: create(order + orderItems)
  OrderSvc->>DB: INSERT order, order_items
  DB-->>OrderSvc: OrderModel
  Facade-->>Controller: OrderInfo
  Controller-->>Client: ApiResponse<OrderResponse>
Loading

Main Flow — 주문 취소 (장바구니 복원 포함)

sequenceDiagram
  autonumber
  participant Client
  participant Controller as OrderV1Controller
  participant Facade as OrderFacade
  participant OrderSvc as OrderService
  participant StockSvc as StockService
  participant CartSvc as CartService
  participant DB

  Client->>Controller: POST /api/v1/orders/{id}/cancel
  Controller->>Facade: cancelOrder(orderId, userId)
  Facade->>OrderSvc: cancel(orderId) [CAS 상태 전이]
  OrderSvc->>DB: UPDATE order SET status = CANCELLED WHERE status = PENDING_PAYMENT
  DB-->>OrderSvc: OrderModel
  Facade->>StockSvc: release(productId, qty) [CAS]
  StockSvc->>DB: UPDATE stock SET reserved -= qty WHERE reserved >= qty
  Facade->>CartSvc: restore (DIRECT 주문만, 멱등)
  CartSvc->>DB: INSERT cart_item (if not exists in order_cart_restore)
  Facade-->>Controller: void
  Controller-->>Client: ApiResponse<success>
Loading

좋아요 → 장바구니 → 주문 전체 흐름

flowchart LR
  A[좋아요 등록] --> B[장바구니 담기]
  B --> C{주문 유형}
  C -->|DIRECT| D[바로 주문]
  C -->|CART| E[장바구니 주문]
  D --> F[재고 hold - CAS]
  E --> F
  F --> G[PENDING_PAYMENT]
  G -->|사용자 취소| H[CANCELLED]
  G -->|배치 만료| I[EXPIRED]
  H --> J[재고 release + 장바구니 복원]
  I --> J
Loading

요구사항, 시퀀스다이어그램, 클래스다이어그램, ERD 개선
기존 Example/Member 도메인을 제거하고, 이커머스 핵심 도메인(User, Brand, Product, Like, Cart, Order, Stats)을
  Layered Architecture(interfaces → application → domain ← infrastructure) 패턴으로 구현.

주요 변경사항:
  - User: 회원 가입/로그인/비밀번호 변경 (BaseStringIdEntity 기반 UUID PK)
  - Brand: 브랜드 CRUD, 소프트 삭제, display_status 관리
  - Product: 상품 CRUD, revision 이력 관리, sale_status, 재고(CAS 기반 hold/release)
  - Like: 상품 좋아요 등록/취소 (멱등, 복합 PK)
  - Cart: 장바구니 CRUD, 주문 연계 복원 (멱등성 보장)
  - Order: 주문 생성(DIRECT/CART), 취소, 만료 스케줄러 (상태 머신 패턴)
  - Stats: 운영 통계 (주문 현황, 인기 상품)
  - Admin API: 관리자 전용 엔드포인트 (AdminAuthInterceptor 기반 인증)
  - 설계 문서: 아키텍처, 클래스 다이어그램, ERD, 레이어 구성 문서 추가
  - 전체 레이어별 단위/통합/E2E 테스트 작성
@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant