V3MutationService and V3CompatibleTableBinding#199
Conversation
Closes #195 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…code - V3CompatibleTableBinding: extract decodeCurrentState() and buildHBaseMutations() private helpers (~65 lines dedup) - V3MutationService: extract resolveMutationContext(), writeCdc(), and handleMutationError() private helpers (~50 lines dedup) - Bug fix: add missing LockAcquisitionFailedException handling to mutateMultiEdge (errors now return ERROR status instead of propagating and failing the entire request) Closes #195 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Review Round 1by claude code (opus 4.6) Issues Found & Fixed
ResultAll tests passing ( Changes Summary
|
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comparison Noteby opencode (gpt-5.3-codex)
Bottom line: main behavioral difference is non-lock error propagation in |
Comparison: PR #197 (codex 5.3) vs PR #199 (opus 4.6)by claude code (opus 4.6) Stats
V3CompatibleTableBinding — Nearly identical
V3MutationService — Different architecture
Behavioral difference in error handling
Issue states: "errors propagate and fail the entire request instead of returning ERROR status" — opus approach matches this intent (return ERROR status for all errors, identical to existing |
My take: PR #199 (opus) wins, but #197 has a valid counter-argumentby claude code (opus 4.6) Why #199: Smaller diff (38% less), preserves original code structure via destructuring, simpler helpers (one Counter-argument from #197 (codex): Rebuttal: The original Both are valid scope interpretations. #197 is more conservative; #199 matches the bulk mutation design intent. |
Follow-up Verdictby opencode (gpt-5.3-codex) After reconsidering my earlier comparison and your rebuttal, I agree the better winner for issue #195 is PR #199. Why:
My PR #197 is more conservative on non-lock error propagation, but that is less aligned with the stated issue direction. |
ConclusionBoth PRs successfully completed the task. Here's how I evaluated them: Process:
Result:
Why #199:
|
Expanding Scope: EdgeMutationBuilder Strategy Pattern RefactoringThe current PR successfully eliminated duplicate code in ProblemThe only difference between Edge and MultiEdge variants is how source/target are resolved:
Plus two subtle behavioral differences:
Plan
No Impact on Callers
|
Extract EdgeMutationStrategy sealed interface to encapsulate the four behavioral differences between Edge and MultiEdge mutation building: source/target resolution, DELETE count source, and UPDATE count logic. This eliminates 6 duplicated ForMultiEdge methods and 5 extension functions, reducing EdgeMutationBuilder from 429 to 239 lines while keeping the public API (build/buildForMultiEdge) unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…code Extract mutateInternal in V3CompatibleTableBinding and executeMutationPipeline/writeWal in V3MutationService to deduplicate Edge/MultiEdge mutation paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…records, and V3CompatibleTableBinding Cover three gaps in PR #199 refactoring: - EdgeMutationStrategy: direct unit tests for directedSource, directedTarget, countRecordOnDelete, countRecordsOnUpdate - EdgeMutationBuilder: group record tests for COUNT/SUM with CREATED/DELETED/UPDATED transitions - V3CompatibleTableBinding: companion method tests for mergeQualifiers and specialStateValueToNull Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… test fixtures Replace mutable var accumulation pattern with direct EdgeMutationRecords construction in each when branch. Extract duplicated edgeRecord() and multiEdgeRecord() test helpers into EdgeMutationTestFixtures. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changes Summaryby claude code (opus 4.6)
What changed
Public API unchanged. All tests pass. --- UPDATED NOTE: This is as far as the AI goes. I’ll take it from here with AI Assist, and I expect significant improvements. We’re currently about halfway to the desired quality. The previous limitations were likely due to the strict constraints I set to ensure safety without review. Now that I’m reviewing every step, let's allow more flexible and extensive changes for a better outcome. |
…nt creation Extract MutationEvent<K> interface with nested Source<E> interface to generalize EdgeEvent/MultiEdgeEvent and their creation in BulkMutationRequest. This eliminates the groupKey parameter from executeMutationPipeline and unifies eventFlux creation into a single createEventFlux helper. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nPipeline Introduce MutationStatus interface for common status fields (status, before, after, acc) shared by EdgeMutationStatus and MultiEdgeMutationStatus. Absorb sort/mutate/writeCdc/errorResume logic into executeMutationPipeline, replacing the executeGroup lambda with declarative mutate, stateToHashEdge, and errorStatus parameters. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add toResponse parameter to executeMutationPipeline so each caller declares its status-to-response mapping inline rather than chaining a separate .map after the pipeline call. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…teMutation Extract MutationStatus interface for common status fields. Add executeMutation that handles context resolution and wires createEventFlux + executeMutationPipeline, so mutateEdge and mutateMultiEdge become thin delegators passing only their type-specific lambdas (mutate, stateToHashEdge, errorStatus, queuedStatus, toResponse). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rity The HashEdge conversion is a backward compatibility layer for the v2 format. The new name makes this explicit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rename executeMutationPipeline to groupAndMutate returning Mono<List<S>>, so executeMutation reads as a clear pipeline: createEventFlux → groupAndMutate → map(toResponse) → timeout Reorder parameters to match the workflow: context → events → mutate → CDC → status → response Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ension Convert executeMutation to a Mono chain: resolveMutationContextMono → createEventFlux → groupAndMutate → map(toResponse) → timeout - Extract resolveMutationContextMono to wrap context resolution in Mono.fromCallable - Convert groupAndMutate to Flux<E> extension function (removes label/events params) - Remove pseudocode scratch from file Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ation - Inline groupAndMutate logic into executeMutation so the entire algorithm chain is visible in one place - Extract mutateGroupWithCdc as helper for the mutation block - Inline resolveMutationContextMono as Mono.fromCallable Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…no into chain Inline all helper functions into executeMutation so the entire mutation pipeline is visible as a single reactive chain. Remove createEventFlux, resolveMutationContextMono, and unused ModelSchema import. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All mutation pipeline steps are now visible in a single reactive chain within executeMutation. Remove mutateGroupWithCdc helper. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n it
Change writeWal signature to (ctx, event) -> Mono<E>, encapsulating
toTraceEdge/eventType extraction and thenReturn internally. Simplifies
the chain call site to `.flatMap { writeWal(ctx, it) }`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ationContext - Extract queue branch into enqueueGroup function - Extract mutate+CDC branch into mutateGroup function - Inline resolveMutationContext into executeMutation chain (getLabel is in-memory lookup, Mono.fromCallable is sufficient) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keep fromCallable block concise by delegating to resolveMutationContext. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ge callers
Move Mono.fromCallable { resolveMutationContext(...) } up to the public
methods so executeMutation receives MutationContext directly. This makes
executeMutation a pure pipeline without context resolution concerns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Arrange functions in logical sections: - public API (mutateEdge, mutateMultiEdge) - context (MutationContext, resolveMutationContext) - pipeline (executeMutation, writeWal, enqueueGroup, mutateGroup) - side effects (writeCdc, handleMutationError) - v2 compat helpers (toV2HashEdge) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- EdgeMutationStatus.of(key, count, status) / MultiEdgeMutationStatus.of(id, count, status) - EdgeMutationResponse.from(statuses) / MultiEdgeMutationResponse.from(statuses) - Simplify mutateEdge/mutateMultiEdge callers with method references Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename mutateInternal -> mutate for clarity - Rename decodeCurrentState -> decodeV2HashEdgeToState - Add v2/v3 boundary comments in mutation flow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add HBaseIndexedLabel.withLock(traceId, edge, bulk, action) extension - Encapsulates encodeLockEdge, acquireLock, releaseLock internally - Simplifies mutate() call site Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…iqueEdge Align naming with buildForMultiEdge for clarity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- MutationEvent: interface -> sealed interface for exhaustive when - EdgeEvent.id: computed property -> init property to avoid repeated Pair allocation - createEvent: unsafe cast -> require validation in EdgeBulkMutationRequest and MultiEdgeBulkMutationRequest - V3MutationService: inline enqueueGroup, remove unused function - V3MutationService: remove else branch in toV2TraceEdge (sealed interface makes it exhaustive) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- EdgeBulkMutationRequest: remove unnecessary edgeSchema alias (smart cast suffices) - MultiEdgeBulkMutationRequest: remove unnecessary multiEdgeSchema alias - EdgeMutationStrategy: replace !! with requireNotNull for clearer error messages Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update: AI-Assisted Review Round12 additional commits since the summary above, driven by human review with AI assist: Changes Made
Updated Stats
Key Improvements Over Initial AI-Only Phase
All tests pass. Ready for final review and merge. |
|
This is a large PR. I've thoroughly reviewed it, and it passes all tests, including internal ones. It's a good chance to clear some tech debt from the rushed v3. Merging. |
|
Post-merge update: Deployed to Kakao staging for shadow testing with real traffic. No issues found. |
Summary
Refactor
V3MutationServiceandV3CompatibleTableBindingto eliminate 95%+ duplicate code betweenmutateEdgeandmutateMultiEdgemethods, and fix a bug wheremutateMultiEdgeis missingLockAcquisitionFailedExceptionerror handling.Closes #195
Plan
Created by claude code (opus 4.6)
decodeCurrentState()private helper (identical state decoding from HBase result)buildHBaseMutations()private helper (100% identical Put/Increment/Delete construction)resolveMutationContext()helper (label validation + context initialization)writeCdc()helper (CDC message creation + publish)handleMutationError()helper and apply to both methodsonErrorResumeforLockAcquisitionFailedExceptiontomutateMultiEdge(currently missing, errors propagate and fail entire request)./gradlew :engine:test :server:test)Progress