Solution/aakash desale#118
Open
akashdesale98 wants to merge 10 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
Implements a Go-based wallet transfer service with asynchronous processing, Postgres-backed idempotency, double-entry ledger recording, and observability (metrics + tracing), along with unit and integration test coverage.
Changes:
- Added Postgres schema/migrations for wallets, transfers, and ledger entries with constraints/indexes to enforce correctness and idempotency.
- Implemented layered runtime (handler/service/repository/worker) to enqueue transfers (202) and process them via a background worker + reaper.
- Added local/dev tooling and operational assets (Docker Compose, Makefile, Prometheus/Grafana/Jaeger wiring) plus comprehensive tests and proof artifacts.
Reviewed changes
Copilot reviewed 66 out of 69 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| README.md | Service architecture/design/run/test documentation. |
| prompt.txt | AI usage prompt transcript seed. |
| PR_DESCRIPTION.md | Prepared PR description template/content. |
| migrations/seed.sql | Seed wallets for local/dev testing. |
| migrations/migration_guide.md | Migration workflow documentation. |
| migrations/01_wallet_schema.up.sql | Core schema, constraints, indexes, triggers. |
| migrations/01_wallet_schema.down.sql | Teardown migration. |
| Makefile | Local dev/test/migrate helpers. |
| main.go | Dependency wiring and lifecycle management. |
| internal/worker/worker.go | Worker pool + reaper loop. |
| internal/worker/worker_unit_test.go | Worker unit tests with mocks. |
| internal/tests/testutils/db.go | Integration test DB helpers. |
| internal/tests/mock/store.go | Mock repository/store + TxStore fakes. |
| internal/tests/mock/misc.go | Mock recorder/processor helpers. |
| internal/tests/integration/worker_test.go | Worker integration tests. |
| internal/tests/integration/service_test.go | Service integration tests (idempotency/concurrency). |
| internal/tests/integration/repository_test.go | Repository integration tests. |
| internal/tests/integration/api_test.go | End-to-end HTTP + worker integration test. |
| internal/service/service.go | Transfer service (enqueue/get). |
| internal/service/service_unit_test.go | Transfer service unit tests. |
| internal/service/retry.go | Retry policy for transient DB errors. |
| internal/service/retry_test.go | Retry policy unit tests. |
| internal/service/recorder.go | Metrics recorder interface + nop impl. |
| internal/service/processor.go | Transfer processing transaction workflow. |
| internal/service/processor_unit_test.go | Processor unit tests. |
| internal/repository/repository.go | Store/TxStore interfaces and repo-level errors. |
| internal/repository/postgres/txstore.go | Postgres TxStore implementation. |
| internal/repository/postgres/store.go | Postgres Store implementation (enqueue/claim/reap/tx). |
| internal/repository/postgres/scan.go | Row scanning + pg error classification. |
| internal/observability/tracing/tracing.go | OpenTelemetry tracer setup. |
| internal/observability/tracing/tracing_test.go | Tracing tests. |
| internal/observability/metrics/metrics.go | Prometheus collectors + registry. |
| internal/observability/metrics/metrics_test.go | Metrics tests. |
| internal/logger/logger.go | Zap logger construction. |
| internal/logger/logger_test.go | Logger tests. |
| internal/handler/unit_test.go | Handler DTO/status mapping unit tests. |
| internal/handler/transfer_handler.go | POST/GET transfer HTTP handlers. |
| internal/handler/transfer_handler_test.go | Transfer handler unit tests. |
| internal/handler/server.go | HTTP server wiring, routing, shutdown. |
| internal/handler/middleware.go | Request ID, recovery, access log, metrics middleware. |
| internal/handler/health.go | Health/readiness/metrics endpoints. |
| internal/handler/health_test.go | Health endpoint tests. |
| internal/handler/errors.go | Domain/service error → HTTP status mapping. |
| internal/handler/dto.go | Request/response DTOs and JSON helpers. |
| internal/domain/wallet.go | Wallet domain rules. |
| internal/domain/transfer.go | Transfer domain types + request validation. |
| internal/domain/ledger.go | Ledger domain types. |
| internal/domain/errors.go | Domain error definitions. |
| internal/domain/domain_test.go | Domain validation/state machine tests. |
| internal/domain/domain_extra_test.go | Additional domain tests. |
| internal/db/db.go | Postgres pool creation/ping. |
| internal/db/db_test.go | DB pool tests. |
| internal/config/config.go | Viper-based configuration loading. |
| internal/config/config_test.go | Config tests. |
| go.sum | Dependency lock updates. |
| go.mod | Module definition and dependencies. |
| docs/proofs/test-output.txt | Captured test output. |
| docs/proofs/scenarios.txt | Captured scenario walkthrough. |
| docs/proofs/PROOFS.md | Proof mapping to requirements. |
| docs/proofs/app-logs-sample.log | Sample logs. |
| dockerfile | Container build/run image. |
| docker-compose.yaml | Local stack (Postgres/migrate/seed/app/obs). |
| deploy/prometheus/prometheus.yml | Prometheus scrape config. |
| deploy/grafana/provisioning/datasources/datasource.yml | Grafana datasource provisioning. |
| deploy/grafana/provisioning/dashboards/dashboards.yml | Grafana dashboard provisioning. |
| deploy/grafana/dashboards/wallet-transfer.json | Grafana dashboard definition. |
| config.example.yaml | Example config file. |
| .gitignore | Ignore local config/build artifacts. |
| .github/workflows/ci.yml | CI Go version update. |
akashdesale98
force-pushed
the
solution/aakash-desale
branch
from
July 22, 2026 12:49
f058111 to
42ac4f9
Compare
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
A service for moving money between wallets. It handles duplicate requests safely, records
every transfer as two ledger entries (one debit, one credit), and stays correct under
concurrent requests. The API accepts a transfer and returns
202right away; a backgroundworker does the money movement. Correctness is enforced by the database (constraints, row
locks, unique keys), not by application code. The code is layered (handler, service,
repository, domain) and includes structured logging, Prometheus metrics, a Grafana
dashboard, tracing, graceful shutdown, and a full test suite. See
README.mdfor details.AI disclosure
senior review: first a critical review of my own proposed design (which caught real
issues, listed below), then step-by-step implementation, one milestone at a time. Every
milestone was built, run, and checked against a real Postgres before moving on. I
reviewed and directed each step (for example: dropping Redis, choosing background
processing, keeping comments short, commit style) instead of accepting output blindly.
prompt.txt. The full sessiontranscript is available on request and can be added to the repo if preferred. Main things
the review changed from my first draft: removed Redis (it adds a second copy of the
same fact and a chance to disagree, without adding a guarantee; the unique key already
prevents duplicates); added the missing
from/to/amountcolumns totransfer;made the business vs. temporary failure handling explicit; and moved correctness rules
into the database instead of pre-checks in code.
Schema Design
wallet: the currentbalancewithCHECK (balance >= 0), plusis_active.transfer: the request and the duplicate-check record. Has a uniqueidempotency_key,keeps its own
from_wallet_id,to_wallet_id,amount, a state column, andattempts/claimed_at/worker_idfor background processing.CHECK (from <> to)and
CHECK (amount > 0).ledger_entries: the permanent record.UNIQUE (transfer_id, type)means exactly onedebit and one credit per transfer, which also makes reprocessing safe.
NUMERIC(20,4)money,TIMESTAMPTZtimes, foreign keys, and small partial indexes forthe worker queries. Seed data is kept out of the migrations so teardown always works.
Idempotency Strategy
INSERT ... ON CONFLICT (idempotency_key) DO NOTHING RETURNING. If the key already exists,read the existing transfer and return that (
created=false). Duplicate requests return thesame transfer id:
202first,200after. This survives restarts (it is a database rule)and is safe under concurrency (a duplicate waits on the unique index, then reads the saved
row). No Redis.
Concurrency Strategy
SELECT ... FOR UPDATEin the same order (by id), so thereis no deadlock.
FOR UPDATE SKIP LOCKED, so each worker takes a different batch andthey do not block each other (you can run many).
CHECK (balance >= 0). Reprocessing ismade safe by the unique ledger key. Ledger entries are written before balances, so a
duplicate is caught before any balance changes.
where only one can fit, end as exactly one PROCESSED and one FAILED, and the source is
debited exactly once.
How to Run
docker compose up -d --build: Postgres, migrations, seed, app, Prometheus, Grafana, Jaeger.make up && make migrate-up && make seed && make run.How to Test
make test: all tests (unit + integration).make test-unit: only the tests that need no database (in-memory fakes). Integrationtests skip without
TEST_DATABASE_URL.make cover: coverage of the main packages (about 90%; domain, logger, metrics at 100%).Unit tests use in-memory fakes (
internal/tests/mock) to check every path, including thefailure paths (not enough funds, inactive wallet, duplicate ledger entry, temporary-error
retry, giving up after too many tries, worker and reaper errors). Integration tests
(
internal/tests/integration) run the real stack against Postgres, including the concurrentdouble-spend test and reaper recovery.
Captured checks for every assignment scenario (test output, a live HTTP and SQL
walkthrough, and logs) are in
docs/proofs/.Tradeoffs / Assumptions
202then poll) so the service scales and recovery of unfinishedtransfers is real; a synchronous mode that waits for the result could be added on top.
truth; a job that checks the ledger sum matches each balance would sit alongside it in
production.
FAILED; temporary failures are undone and retried, andanything left unfinished is recovered by the reaper.
Checklist
For More details - Check https://github.com/akashdesale98/wallet-transfer-assignment/blob/solution/aakash-desale/assignment_readme.md