feat: implement wallet transfer service with postgres, double-entry l… - #124
Open
theshanky wants to merge 2 commits into
Open
feat: implement wallet transfer service with postgres, double-entry l…#124theshanky wants to merge 2 commits into
theshanky wants to merge 2 commits into
Conversation
…edger, idempotency, and concurrency safety
There was a problem hiding this comment.
Pull request overview
This PR introduces a Go + PostgreSQL wallet transfer service implementing wallet creation, idempotent transfers, and a double-entry ledger, exposed via HTTP handlers and backed by Postgres repositories.
Changes:
- Added service/repository layers to create wallets, execute transfers, and record ledger/idempotency data in PostgreSQL.
- Added HTTP routing + handlers for
/wallets,/transfers, and/health. - Added integration-style tests (including concurrency tests) and a solution documentation write-up.
Assessment (key risks):
- The current transaction/error flow in
TransferService.CreateTransferrolls back “durable failure” records (failed transfer + idempotency status) on business errors like insufficient funds, which undermines exactly-once/idempotency guarantees under failure. - The idempotency “lock” approach (
SELECT ... FOR UPDATE) does not prevent races when the idempotency row does not yet exist, which can lead to nondeterministic unique-violation failures under concurrency rather than clean conflict/replay behavior. - Some security/operational concerns exist around logging and returning raw internal errors.
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| SOLUTION_DOCUMENTATION.md | Added architecture/schema/idempotency/concurrency documentation and AI disclosure section. |
| internal/service/wallet_service.go | Implements wallet creation + audit and seeds initial ledger state. |
| internal/service/transfer_service.go | Implements idempotent, transactional transfer orchestration + ledger writes. |
| internal/service/transfer_service_test.go | Adds integration tests for transfer success, replay, mismatch, insufficient funds, validation. |
| internal/service/concurrency_test.go | Adds concurrency stress tests for reciprocal transfers + shared idempotency key. |
| internal/repository/schema.sql | Introduces SQL schema + indexes (and a seed row). |
| internal/repository/repository.go | Defines repository and TxManager interfaces. |
| internal/repository/postgres.go | Implements Postgres repositories + transaction manager. |
| internal/handler/wallet_handler.go | Adds HTTP endpoints for wallet create/get/audit. |
| internal/handler/transfer_handler.go | Adds HTTP endpoints for transfer create/get and error mapping. |
| internal/handler/router.go | Registers routes using http.ServeMux patterns. |
| internal/handler/handler_test.go | Adds handler-level integration tests against a real DB. |
| internal/domain/models.go | Adds domain models (wallet/transfer/ledger/idempotency) + request hashing. |
| internal/domain/errors.go | Adds domain error definitions for handlers/services. |
| internal/domain/domain_test.go | Adds unit tests for request hash determinism. |
| go.mod | Introduces module definition + dependencies (and Go version directive). |
| go.sum | Locks dependency checksums. |
| cmd/server/schema.sql | Adds embedded schema applied at server startup. |
| cmd/server/main.go | Adds server bootstrap: DB connect, schema apply, DI wiring, graceful shutdown. |
| .golangci.yml | Enables a baseline set of linters for the Go codebase. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| Transfer: failedTransfer, | ||
| IsReplayed: false, | ||
| } | ||
| return domain.ErrInsufficientFunds |
Comment on lines
+121
to
+124
| if err != nil { | ||
| s.recordFailedIdempotency(ctx, tx, idemRecord, err.Error()) | ||
| return err | ||
| } |
| if err != nil { | ||
| return fmt.Errorf("failed to fetch replayed transfer: %w", err) | ||
| } | ||
| ledgerEntries, _ := s.ledgerRepo.GetByTransferID(ctx, tx, t.ID) |
|
|
||
| err := s.txMgr.ExecTx(ctx, func(tx *sql.Tx) error { | ||
| // 1. Idempotency Check & Locking | ||
| idemRecord, err := s.idempotencyRepo.GetForUpdate(ctx, tx, input.IdempotencyKey) |
| } | ||
|
|
||
| // 2. Deterministic Row Locking (Order by wallet ID lexicographically via single FOR UPDATE query) | ||
| fromWallet, toWallet, err := s.walletRepo.GetTwoWalletsForUpdate(ctx, tx, input.FromWalletID, input.ToWalletID) |
Comment on lines
+94
to
+98
| entries := []domain.LedgerEntry{ | ||
| { | ||
| ID: creditID, | ||
| TransferID: initTransferID, | ||
| WalletID: walletID, |
| port = "8080" | ||
| } | ||
|
|
||
| logger.Info("connecting to PostgreSQL database", "url", dbURL) |
|
|
||
| 1. **Tool Used**: Antigravity AI Coding Assistant powered by Claude 3.7 Sonnet / Gemini models. | ||
| 2. **Usage Pattern**: Used for system architecture design, database lock ordering analysis, double-entry accounting schema design, table-driven unit test generation, and high-concurrency race condition testing. | ||
| 3. **Session Logs**: Conversation transcripts and decision logs are persisted under `<appDataDir>/brain/47cd1d3c-1daf-43af-952d-13b0706d8c3b/`. |
Comment on lines
+49
to
+51
| INSERT INTO wallets (id, balance, currency, created_at, updated_at) | ||
| VALUES ('system_treasury', 0, 'USD', NOW(), NOW()) | ||
| ON CONFLICT (id) DO NOTHING; |
| writeJSONError(w, http.StatusUnprocessableEntity, err.Error()) | ||
| return | ||
| } | ||
| writeJSONError(w, http.StatusInternalServerError, "failed to process transfer: "+err.Error()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…edger, idempotency, and concurrency safety
Summary
Describe your solution briefly.
AI disclosure
Detail how you used AI to help with your submission (including the tools you used, how
you used them and what your prompts were).
Include these points in detail
Schema Design
Describe the tables, constraints, and indexes you introduced.
Idempotency Strategy
Explain how duplicate requests are handled safely.
Concurrency Strategy
Explain how you prevent race conditions and double spending.
How to Run
How to Test
Tradeoffs / Assumptions
Checklist