Solution/prasenjit - #117
Open
Prasen1 wants to merge 9 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements a Go-based wallet transfer service backed by PostgreSQL, focusing on idempotent POST /transfers, concurrency-safe balance updates, and double-entry ledger persistence, plus local tooling (migrations/Makefile/Docker) and test coverage.
Changes:
- Added PostgreSQL schema/migrations for wallets, idempotency records, transfers, and ledger entries (plus seed + down migration).
- Implemented HTTP API, service/domain layers, and Postgres repository/transaction layer for transfers and wallet reads.
- Added unit + integration tests (including concurrent debit scenarios) and developer tooling/docs (Makefile, docker-compose, README, invariant checker).
Reviewed changes
Copilot reviewed 34 out of 36 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updated run/test instructions and API examples for the Go service |
| migrations/001_init.sql | Introduces core tables, constraints, and indexes |
| migrations/002_seed.sql | Seeds two wallets for local/dev and integration tests |
| migrations/999_down.sql | Adds down migration to drop tables |
| Makefile | Adds common dev targets (run, migrate, lint, tests, invariant check) |
| internal/service/wallet_service.go | Wallet read + ledger listing service logic |
| internal/service/transfer_service.go | Idempotent transfer creation + processing workflow |
| internal/service/transfer_service_test.go | Service unit tests with an in-memory fake store |
| internal/service/transfer_integration_test.go | Postgres-backed integration tests incl. concurrency |
| internal/repository/postgres/tx.go | Transaction-scoped Postgres operations (locks, writes) |
| internal/repository/postgres/store.go | Postgres store with transaction helper + read methods |
| internal/repository/postgres/scan.go | Row scanning helpers + domain error mapping |
| internal/repository/interfaces.go | Store/Tx interfaces used by services |
| internal/infra/config.go | Environment-based config loading |
| internal/domain/wallet.go | Wallet domain model + debit/credit eligibility |
| internal/domain/wallet_test.go | Unit tests for wallet domain rules |
| internal/domain/transfer.go | Transfer model + state transitions |
| internal/domain/transfer_test.go | Unit tests for transfer transitions/validation |
| internal/domain/money.go | Money validation + currency normalization |
| internal/domain/money_test.go | Unit tests for money validation/defaults |
| internal/domain/ledger_entry.go | Ledger entry constructors for debit/credit legs |
| internal/domain/idempotency.go | Idempotency record model |
| internal/domain/errors.go | Shared domain errors |
| internal/api/wallets_handler.go | Wallet HTTP handlers (wallet + ledger endpoints) |
| internal/api/transfers_handler.go | Transfer HTTP handlers (create + get) |
| internal/api/server.go | HTTP routing + request logging middleware |
| internal/api/server_test.go | Handler-level tests for status codes and error mapping |
| internal/api/respond.go | JSON/error response helpers and domain error mapping |
| IMPLEMENTATION_PLAN.md | Documents intended architecture and milestones for the solution |
| go.mod | Declares module and dependencies |
| go.sum | Adds dependency checksums |
| docker-compose.yml | Local Postgres container for development/testing |
| cmd/server/main.go | Service entrypoint wiring API + services + Postgres |
| cmd/check-invariants/main.go | CLI to validate ledger/balance invariants in DB |
| .gitignore | Ignores Go build/cache directories |
| .github/workflows/ci.yml | Fixes golangci-lint install step sequencing |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…t-transfer-assignment into solution/prasenjit
Prasen1
marked this pull request as ready for review
July 20, 2026 08:22
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.
Summary
Go implementation of the wallet transfer assignment. The service supports
idempotent wallet-to-wallet transfers, stored balances, and a double-entry ledger.
AI disclosure
Used Claude Sonnet for researching and planning the design of the project based on requirements and then passed the spec to Codex for creating the implementation plan (attached). From there, the prompts were just to implement the project step by step with my review at every step.
At work, I use Claude code (Opus for research and design and Sonnet for execution).
Schema Design
The implementation introduces a normalized PostgreSQL schema that separates wallet state, transfer intent, idempotency tracking, and accounting history.
Tables
walletsPRIMARY KEYonidCHECK (opening_balance >= 0)CHECK (balance >= 0)CHECK (status IN ('ACTIVE', 'FROZEN', 'CLOSED'))ix_wallets_owneronowner_ididempotency_recordsPRIMARY KEYonidempotency_keyCHECK (status IN ('IN_PROGRESS', 'COMPLETED'))CHECKrequiring a completed record to have bothtransfer_idandresponse_snapshottransfersPRIMARY KEYonidFOREIGN KEYtoidempotency_records(idempotency_key)FOREIGN KEYto source and destination walletsCHECK (amount > 0)CHECK (status IN ('PENDING', 'PROCESSED', 'FAILED'))CHECK (from_wallet_id <> to_wallet_id)ux_transfers_idempotency_keyonidempotency_keyledger_entriesPRIMARY KEYonidFOREIGN KEYtotransfers(id)and walletsCHECK (entry_type IN ('DEBIT', 'CREDIT'))CHECK (amount > 0)UNIQUE (transfer_id, wallet_id, entry_type)ix_ledger_walleton(wallet_id, created_at, id)ix_ledger_transferontransfer_idIdempotency Strategy
Idempotency is enforced using a unique
idempotency_keyplus a deterministic request fingerprint.Request handling
The request is normalized and a fingerprint is built from:
The service tries to insert an
idempotency_recordsrow:INSERT ... ON CONFLICT (idempotency_key) DO NOTHINGIf the insert succeeds:
If the insert fails because the key already exists:
COMPLETED, the service returns the cached response snapshotIN_PROGRESS, the request is treated as already being processedThis provides safe replay semantics: repeat requests with the same key return the same result instead of creating duplicate transfers.
Concurrency Strategy
The service prevents race conditions and double-spending by combining transactional row locks with atomic balance updates.
Protections in place
SELECT ... FOR UPDATEis used on the transfer row before processingSELECT ... FOR UPDATEis used on both wallets involved in the transferBalance updates are applied in a single SQL statement using
CASELedger entries are inserted within the same transaction as the wallet updates
The schema uniqueness constraint on
ledger_entriesThis prevents:
How to Run
Local setup
Start PostgreSQL:
Apply migrations and seed data:
Run the service:
Default configuration
8080Useful commands
make test make test-integration make fmt make fmt-check make lint make check-invariants make migrate-downExample endpoints
How to Test
Tradeoffs / Assumptions
currencydefaults toUSDfor assignment payload compatibility.concurrent requests. It can reduce throughput under request load on the same wallets.
Checklist