Skip to content

Refactor/25/matching consistency 매칭 정합성 보강 및 주문 처리 흐름 문서화#26

Merged
ohhalim merged 11 commits into
developfrom
refactor/25/matching-consistency
May 16, 2026
Merged

Refactor/25/matching consistency 매칭 정합성 보강 및 주문 처리 흐름 문서화#26
ohhalim merged 11 commits into
developfrom
refactor/25/matching-consistency

Conversation

@ohhalim
Copy link
Copy Markdown
Owner

@ohhalim ohhalim commented May 16, 2026

작업 내용

  • 주문 매칭/정산 흐름의 DB-오더북 정합성 보강
  • DB commit 이후 인메모리 오더북 반영 구조 적용
  • 오더북 반영 실패 시 DB 기준 재빌드 및 cancelOnly 전환 처리
  • 주문 취소 경로의 row lock 및 rollback 불일치 보강
  • 지갑 정산 시 lock 순서 정렬로 deadlock 위험 완화
  • zero-quote 체결 방지 및 dust maker 자동 취소 처리
  • 주문/체결/fill/원장 조회 API의 limit, depth, orderId 필터 보강
  • 로컬 개발용 docker-compose 및 Kafka KRaft 설정 추가
  • 주문 생성 → 매칭 → 정산 → 원장 → 오더북 반영 흐름 문서 추가

관련 이슈

closes #이슈번호

Summary by CodeRabbit

Release Notes

  • New Features

    • Order book depth control—specify market depth (1–100 levels) for orderbook queries.
    • Price-level aggregation—bids/asks now grouped by price with summed quantities.
    • Pagination for orders, trades, and wallet ledgers with configurable limits.
    • Fill filtering by market and order ID.
    • Automatic dust-maker cancellation—unlocks wallets when remaining fill quantity yields zero value.
    • Order book recovery with cancel-only fallback on critical failures.
  • Bug Fixes

    • Improved validation error responses for malformed requests.
  • Chores

    • Docker Compose setup for MySQL and Kafka.
    • Database schema updates for event tracking.

Review Change Stack

ohhalim added 11 commits May 14, 2026 16:56
1. 오더북 롤백 불일치 — planMatch/applyMatchPlan 분리
   DB 롤백 시 in-memory 큐가 이미 변경된 상태로 남는 문제 해소.
   afterCommit() 훅으로 applyMatchPlan() 호출을 커밋 이후로 보장.

2. 오더북 동시 읽기 — marketLock 재사용
   REST 오더북 조회 시 marketLock 없이 PriorityQueue를 stream()하면
   ConcurrentModificationException 발생. MarketController에서 기존
   OrderService.marketLock을 획득한 뒤 조회하도록 수정.

3. Maker order row lock 누락 — SELECT FOR UPDATE 적용
   settle() 내 maker 조회를 findByIdWithLock()으로 교체,
   fill 이전 isCancelable() 상태 재검증 추가.

4. DepositRequest 입력 검증 누락 — @notblank / @pattern 추가
   null·빈 문자열 입력 시 NPE/NumberFormatException 방지.
   WalletController에 @Valid 추가.
- DomainEventRecorder.save(): payload를 schemaVersion+occurredAt 엔벨로프로 래핑
- DomainEventRepository: findAllByPublishedFalseOrderByIdAsc() 추가 (발행순서 보장)
- V5__create_processed_events.sql: 컨슈머별 중복 처리 방지용 테이블 생성
- OrderController: @validated + limit(@min(1)@max(200))/offset(@min(0)) 파라미터 추가
- OrderService.getOrders(): PageRequest.of(offset/limit, limit) 적용
- OrderRepository: 목록 조회 메서드 Pageable 파라미터 추가
- TradeRepository: userId 조회를 keyset 페이지네이션으로 변경 (lastFillId > :lastFillId ORDER BY id ASC)
- TradeController.getFills(): lastFillId/limit 파라미터 추가
- MatchingSettlementTest/DomainEventTest @beforeeach: domain_events → wallet_ledgers → trades → orders → wallets → users 순서로 deleteAllInBatch() 호출
- 테스트 간 데이터 간섭 제거, 외래 키 제약 위반 방지
- docker-compose.yml: MySQL 8.0 + Zookeeper + Kafka(confluentinc/cp-kafka:7.6.0) 구성 추가
- application.properties: DB_PASSWORD 기본값을 빈 문자열에서 coinflow로 변경 (docker-compose 설정과 일치)
- cancelOrder() 오더북 변경을 afterCommit()으로 이동 — DB 롤백 시 in-memory 불일치 방지
- OffsetBasedPageRequest 도입으로 주문 목록 offset 처리 수정
- TradeController limit/lastFillId 파라미터 유효성 검증 추가 (@Min/@max)
- GlobalExceptionHandler에 ConstraintViolationException 핸들러 추가
- 테스트 3종 @beforeeach에 DB 전체 클린업 추가 (FK 순서 준수)
- docker-compose MYSQL_PASSWORD → DB_PASSWORD 통일
Zero-quote 체결 — DB 제약 위반 가능성 정리

rounding 후 quoteAmount가 0인 체결이 생성되면 trades.quote_amount > 0 제약을 위반하는 문제를 문서화.

Dust maker 잔존 — 체결 불가능 주문이 오더북에 남는 문제 정리

체결 후 maker 잔량의 quote value가 0이 되는 경우 자동 취소가 필요함을 명시.

Deposit API 운영 노출 — MVP 범위와 보안 위험 정리

입출금은 MVP 제외 범위인데 운영 프로필에서 deposit endpoint가 열려 있는 문제를 BLOCKER로 분류.

API/락/복구/인프라 불일치 — 후속 정리 항목 문서화

Market/Fills/OrderBook 응답 명세, cancel row lock, wallet lock ordering, afterCommit 복구, Kafka KRaft compose 불일치를 우선순위별로 정리.
Zero-quote 체결 — planMatch 단계에서 매칭 중단

DOWN(price * matchedQuantity, amountScale) 결과가 0이면 MatchResult를 생성하지 않고 매칭을 중단하도록 수정.

Dust maker 잔존 — 자동 취소 및 locked balance 해제

체결 후 maker 잔량의 quote value가 0이면 maker를 CANCELED 처리하고 남은 locked balance를 ORDER_CANCEL_RELEASE로 반환.

Cancel order row lock — SELECT FOR UPDATE 적용

cancelOrder() 트랜잭션 내부 주문 조회를 findByIdAndUserIdWithLock()으로 교체해 취소 대상 order row를 잠그도록 수정.

Wallet lock ordering — (userId, asset) 정렬 적용

정산 중 여러 wallet을 잠글 때 (userId, asset) 오름차순으로 잠그도록 lockWalletsInOrder() 추가.

afterCommit 실패 복구 — DB 기반 오더북 재빌드 추가

applyMatchPlan() 실패 시 failure metric을 증가시키고 OPEN/PARTIALLY_FILLED 주문으로 오더북을 재빌드.

Dead code — MemoryOrderBook.match() 제거

planMatch/applyMatchPlan 흐름으로 대체된 stateful match() 메서드 제거.
Deposit API 운영 노출 — prod 프로필 제외

POST /api/v1/wallets/deposit을 WalletController에서 분리하고 DevWalletController에 @Profile("!prod") 적용.

MVP 범위 불일치 — seed API를 개발/테스트 용도로 제한

입출금은 MVP 제외 범위이므로 운영 환경에서 인증 사용자가 임의로 잔액을 증가시킬 수 없도록 수정.

Ledger 조회 limit — Pageable 조회 적용

GET /api/v1/wallets/ledgers?limit=50 명세에 맞게 repository와 service에 Pageable 기반 조회 추가.

회귀 테스트 — ledger limit 검증 추가

원장 조회 시 limit 값만큼 결과가 제한되는지 테스트 추가.
Market response 필드 불일치 — market/amountScale/cancelOnly 추가

API.md 명세에 맞게 symbol 응답을 market으로 변경하고 amountScale, cancelOnly 필드를 추가.

OrderBook 가격 레벨 — 같은 가격 주문 수량 합산

개별 주문을 그대로 노출하던 응답을 가격별 합산 수량으로 변경.

OrderBook depth — 가격 레벨 개수 제한 추가

GET /api/v1/markets/{market}/orderbook?depth=10 파라미터를 지원하도록 수정.

Fill response 명세 불일치 — side/settled/M·T liquidity 적용

사용자 관점 체결 응답에 side와 settled=true를 추가하고 liquidity 값을 MAKER/TAKER에서 M/T로 변경.

Fill orderId 필터 — 사용자 주문 검증 추가

GET /api/v1/fills?orderId=... 필터를 추가하고 본인 주문이 아니면 ORDER_NOT_FOUND를 반환하도록 수정.

회귀 테스트 — API 명세 정합 검증 추가

market 응답 필드, orderbook 합산/depth, fill 응답 필드, orderId 필터 테스트 추가.
Kafka 구성 불일치 — Zookeeper 기반 compose 제거

v2 문서는 KRaft Kafka를 전제로 하지만 docker-compose는 Zookeeper 기반으로 구성되어 있던 문제 수정.

KRaft 단일 노드 — broker/controller 통합 설정 추가

KAFKA_PROCESS_ROLES, KAFKA_CONTROLLER_QUORUM_VOTERS, KAFKA_CONTROLLER_LISTENER_NAMES 등 KRaft 필수 설정 추가.

Local state — kafka_data volume 추가

Kafka 로컬 데이터 보존을 위한 kafka_data volume 추가.

Compose 검증 — docker compose config 통과

변경된 compose 파일이 정상 파싱되는지 확인.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e58304ff-0ba8-4583-a4af-85fdf3118f28

📥 Commits

Reviewing files that changed from the base of the PR and between 852ad08 and b89585e.

📒 Files selected for processing (32)
  • .docs/ISSUES.md
  • docker-compose.yml
  • src/main/java/com/coinflow/common/exception/GlobalExceptionHandler.java
  • src/main/java/com/coinflow/common/pagination/OffsetBasedPageRequest.java
  • src/main/java/com/coinflow/event/repository/DomainEventRepository.java
  • src/main/java/com/coinflow/event/service/DomainEventRecorder.java
  • src/main/java/com/coinflow/market/api/MarketController.java
  • src/main/java/com/coinflow/market/domain/Market.java
  • src/main/java/com/coinflow/market/dto/MarketResponse.java
  • src/main/java/com/coinflow/market/dto/OrderBookResponse.java
  • src/main/java/com/coinflow/order/api/OrderController.java
  • src/main/java/com/coinflow/order/matching/MatchingEngine.java
  • src/main/java/com/coinflow/order/matching/MemoryOrderBook.java
  • src/main/java/com/coinflow/order/matching/OrderBookRecoveryService.java
  • src/main/java/com/coinflow/order/repository/OrderRepository.java
  • src/main/java/com/coinflow/order/service/OrderService.java
  • src/main/java/com/coinflow/trade/api/TradeController.java
  • src/main/java/com/coinflow/trade/dto/FillResponse.java
  • src/main/java/com/coinflow/trade/repository/TradeRepository.java
  • src/main/java/com/coinflow/wallet/api/DevWalletController.java
  • src/main/java/com/coinflow/wallet/api/WalletController.java
  • src/main/java/com/coinflow/wallet/dto/DepositRequest.java
  • src/main/java/com/coinflow/wallet/repository/WalletLedgerRepository.java
  • src/main/java/com/coinflow/wallet/service/WalletService.java
  • src/main/resources/application.properties
  • src/main/resources/db/migration/V5__create_processed_events.sql
  • src/test/java/com/coinflow/integration/DomainEventTest.java
  • src/test/java/com/coinflow/integration/MatchingSettlementTest.java
  • src/test/java/com/coinflow/order/OrderApiTest.java
  • src/test/java/com/coinflow/order/matching/MemoryOrderBookTest.java
  • src/test/java/com/coinflow/query/QueryApiTest.java
  • src/test/java/com/coinflow/wallet/WalletApiTest.java

📝 Walkthrough

Walkthrough

This PR implements a two-phase order matching architecture with market-level locking, dust maker auto-cancellation during settlement, comprehensive API contract alignment (endpoints, DTOs, repositories), wallet deposit endpoint restructuring, and Docker Compose infrastructure for MySQL/Kafka. All referenced priority issues (1–9) from the issues log are addressed via code refactoring and new features.

Changes

Order Matching, Settlement, and Market Locking

Layer / File(s) Summary
Two-phase matching engine (planMatch/applyMatchPlan)
src/main/java/com/coinflow/order/matching/MatchingEngine.java, src/main/java/com/coinflow/order/matching/MemoryOrderBook.java, src/test/java/com/coinflow/order/matching/MemoryOrderBookTest.java
MatchingEngine refactored from single-step match(...) to planMatch(...) + applyMatchPlan(...) + rebuildBook(...). MemoryOrderBook implements simulation (stops on zero quote) and plan application with remaining-quantity tracking. New unit test verifies zero-quote termination.
OrderService match-plan execution with afterCommit hooks
src/main/java/com/coinflow/order/service/OrderService.java
OrderService integrates planMatch/applyMatchPlan via afterCommit transaction synchronization to ensure order-book mutations occur after DB commit. Acquires per-market ReentrantLock via new getMarketLock(...) accessor. On apply failure, triggers OrderBookRecoveryService rebuild with metric emission.
Dust maker auto-cancellation and wallet locking
src/main/java/com/coinflow/order/service/OrderService.java
settle(...) detects zero-quote dust after fills: unlocks wallet, cancels maker, records ledger entries, emits events. New lockWalletsInOrder(...) helper and WalletKey record (Comparable) ensure deterministic multi-wallet locking by (user_id, asset) order.
OrderBookRecoveryService with cancel-only fallback
src/main/java/com/coinflow/order/matching/OrderBookRecoveryService.java, src/main/java/com/coinflow/market/domain/Market.java
New service handles applyMatchPlan failures: emits counter metric, rebuilds order book in REQUIRES_NEW transaction, and falls back to market.enableCancelOnly() if rebuild fails.
OrderService cancelOrder with pessimistic locking and afterCommit
src/main/java/com/coinflow/order/service/OrderService.java
cancelOrder(...) uses findByIdAndUserIdWithLock for pessimistic locking and defers matching-engine cancellation to afterCommit hook with error logging.

API Endpoints, DTOs, and Repository Updates

Layer / File(s) Summary
OrderBookResponse price aggregation and depth limiting
src/main/java/com/coinflow/market/dto/OrderBookResponse.java
OrderBookResponse.of(...) accepts depth parameter and aggregates entry quantities by price using TreeMap, returning only top depth levels as PriceLevel objects.
MarketResponse and MarketController updates
src/main/java/com/coinflow/market/dto/MarketResponse.java, src/main/java/com/coinflow/market/api/MarketController.java
MarketResponse: symbolmarket, added amountScale and cancelOnly. MarketController /orderbook endpoint: accepts validated depth (1–100), verifies market existence, wraps retrieval in per-market ReentrantLock.
OrderController pagination and validation
src/main/java/com/coinflow/order/api/OrderController.java
@Validated annotation added. getOrders endpoint extended with limit (1–200) and offset (0+) parameters passed to OrderService.getOrders(userId, market, limit, offset).
TradeController getFills/getTrades and FillResponse updates
src/main/java/com/coinflow/trade/api/TradeController.java, src/main/java/com/coinflow/trade/dto/FillResponse.java, src/main/java/com/coinflow/trade/repository/TradeRepository.java
TradeController: @Validated, getTrades limit 1–100, getFills now supports optional market/orderId filters, lastFillId pagination, bounded limit (1–200), with orderId ownership validation. FillResponse adds side and settled; liquidity changed to M/T. TradeRepository introduces findFills(...) with JPQL query and cursor-based pagination.
OrderRepository pessimistic locking and pagination
src/main/java/com/coinflow/order/repository/OrderRepository.java
Adds findByIdWithLock(...) and findByIdAndUserIdWithLock(...) using @Lock(PESSIMISTIC_WRITE). Introduces pageable variants for user and user+marketSymbol listing, replacing non-pageable versions.
WalletLedgerRepository and WalletService ledger pagination
src/main/java/com/coinflow/wallet/repository/WalletLedgerRepository.java, src/main/java/com/coinflow/wallet/service/WalletService.java
WalletLedgerRepository: adds Pageable parameter to find methods. WalletService: getLedgers(...) extended with limit parameter, using PageRequest.of(0, limit).
WalletController deposit removal and DevWalletController addition
src/main/java/com/coinflow/wallet/api/WalletController.java, src/main/java/com/coinflow/wallet/api/DevWalletController.java, src/main/java/com/coinflow/wallet/dto/DepositRequest.java
WalletController: /deposit endpoint removed, getLedgers updated with limit (1–200). New DevWalletController (under !prod profile) exposes /deposit. DepositRequest adds @NotBlank on asset, @NotBlank + @Pattern (positive decimal) on amount.
DomainEventRecorder envelope wrapping, pagination, and validation
src/main/java/com/coinflow/event/service/DomainEventRecorder.java, src/main/java/com/coinflow/common/pagination/OffsetBasedPageRequest.java, src/main/java/com/coinflow/common/exception/GlobalExceptionHandler.java, src/main/java/com/coinflow/event/repository/DomainEventRepository.java
DomainEventRecorder wraps payload in envelope (schemaVersion, occurredAt, payload). OffsetBasedPageRequest: Pageable impl with offset/limit navigation. GlobalExceptionHandler: handles ConstraintViolationException → ErrorCode.INVALID_REQUEST. DomainEventRepository: findAllByPublishedFalseOrderByIdAsc() method added.

Infrastructure, Configuration, and Database

Layer / File(s) Summary
docker-compose.yml MySQL and Kafka services
docker-compose.yml
New docker-compose defines mysql:8.0 (port 3306, ENV credentials, mysql_data volume) and confluentinc/cp-kafka:7.6.0 (port 9092, KRaft config, kafka_data volume).
V5__create_processed_events.sql table creation
src/main/resources/db/migration/V5__create_processed_events.sql
Creates processed_events table: event_id (BIGINT), consumer (VARCHAR(60)), processed_at (DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6)), composite PK on (event_id, consumer).
application.properties DB password default
src/main/resources/application.properties
spring.datasource.password: ${DB_PASSWORD:}${DB_PASSWORD:coinflow}.

Test Infrastructure, Cleanup, and Coverage

Layer / File(s) Summary
Test @BeforeEach cleanup across repositories
src/test/java/com/coinflow/integration/DomainEventTest.java, src/test/java/com/coinflow/integration/MatchingSettlementTest.java, src/test/java/com/coinflow/order/OrderApiTest.java, src/test/java/com/coinflow/query/QueryApiTest.java, src/test/java/com/coinflow/wallet/WalletApiTest.java
All integration/API tests now inject and clear OrderRepository, TradeRepository, DomainEventRepository, WalletLedgerRepository in @BeforeEach setup for proper test isolation.
New integration and unit tests
src/test/java/com/coinflow/integration/MatchingSettlementTest.java, src/test/java/com/coinflow/order/matching/MemoryOrderBookTest.java, src/test/java/com/coinflow/query/QueryApiTest.java, src/test/java/com/coinflow/wallet/WalletApiTest.java
MatchingSettlementTest: new DUST_MAKER_001 test verifies zero-quote dust auto-cancellation with lock release. MemoryOrderBookTest: new unit test confirms planMatch stops on zero quote. QueryApiTest: new orderbook depth and fill filtering tests. WalletApiTest: new ledger limit test.
Test assertion updates for contract changes
src/test/java/com/coinflow/query/QueryApiTest.java, src/test/java/com/coinflow/wallet/WalletApiTest.java
QueryApiTest: assertions updated for MarketResponse amountScale/cancelOnly, orderbook depth aggregation, FillResponse side/settled/liquidity (M/T). Test helpers refactored: createOrder returns ResponseEntity, getFills accepts orderId.

Issue Documentation

Layer / File(s) Summary
Comprehensive priority issue documentation (1–9)
.docs/ISSUES.md
Structured documentation of all 9 priority issues (zero-quote matching, dust maker auto-cancellation, deposit profile separation, API spec mismatches, orderbook aggregation, order locking, wallet lock ordering, afterCommit recovery, docker-compose KRaft) with problem statement, root cause, solution, validation plan, and code changes. Summary table confirms all items addressed.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • ohhalim/CoinFlow#22: This PR directly addresses the exact refactorings and bug fixes specified in issue #22 (planMatch/applyMatchPlan, afterCommit order-book updates, market locking, pessimistic order locking, DepositRequest validation).
  • ohhalim/CoinFlow#25: This PR fully implements the features and fixes outlined in issue #25 (two-phase matching, OrderService afterCommit wiring, market-level locking, pessimistic locking, and input validation).

Possibly related PRs

  • ohhalim/CoinFlow#21: Overlaps in wallet deposit endpoint handling (removed from main WalletController vs. added in retrieved PR) and DepositRequest validation updates at the code level.
  • ohhalim/CoinFlow#9: Main PR's matching refactoring (planMatch/applyMatchPlan, recovery/cancel-only) directly evolves the matching-engine foundation introduced in retrieved PR #9 (MatchingEngine, MemoryOrderBook, MatchResult, Trade settlement).
  • ohhalim/CoinFlow#11: Both PRs modify OrderService settlement logic (main PR extends settle with dust auto-cancellation, retrieved PR refines wallet ledger recording), overlapping at the settlement function level.

Poem

🐰 Two-phase matching, a rabbit's delight,
Plan and apply, now locking feels right,
Dust makers cancelled when quote equals zero,
Market-locked trades make each order a hero,
From afterCommit to recovery's care—
A matching refactor beyond compare! 🎯

✨ 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/25/matching-consistency

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.

@ohhalim ohhalim merged commit 23cd73a into develop May 16, 2026
1 of 2 checks passed
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