Skip to content

Solution/aakash desale#118

Open
akashdesale98 wants to merge 10 commits into
Robustrade:mainfrom
akashdesale98:solution/aakash-desale
Open

Solution/aakash desale#118
akashdesale98 wants to merge 10 commits into
Robustrade:mainfrom
akashdesale98:solution/aakash-desale

Conversation

@akashdesale98

@akashdesale98 akashdesale98 commented Jul 22, 2026

Copy link
Copy Markdown

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 202 right away; a background
worker 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.md for details.

AI disclosure

  1. Tool: Claude Code (Claude Opus 4.8).
  2. How I use it: as a pair-programming and design-review partner. I ran the work as a
    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.
  3. Transcript / prompts: the starting prompt is in prompt.txt. The full session
    transcript 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/amount columns to transfer;
    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 current balance with CHECK (balance >= 0), plus is_active.
  • transfer: the request and the duplicate-check record. Has a unique idempotency_key,
    keeps its own from_wallet_id, to_wallet_id, amount, a state column, and
    attempts / claimed_at / worker_id for background processing. CHECK (from <> to)
    and CHECK (amount > 0).
  • ledger_entries: the permanent record. UNIQUE (transfer_id, type) means exactly one
    debit and one credit per transfer, which also makes reprocessing safe.
  • NUMERIC(20,4) money, TIMESTAMPTZ times, foreign keys, and small partial indexes for
    the 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 the
same transfer id: 202 first, 200 after. 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

  • Both wallets are locked with SELECT ... FOR UPDATE in the same order (by id), so there
    is no deadlock.
  • READ COMMITTED isolation; the row locks give the ordering, so there are no forced retries.
  • Workers claim with FOR UPDATE SKIP LOCKED, so each worker takes a different batch and
    they do not block each other (you can run many).
  • Double spending is stopped by the wallet lock plus CHECK (balance >= 0). Reprocessing is
    made safe by the unique ledger key. Ledger entries are written before balances, so a
    duplicate is caught before any balance changes.
  • Checked by an integration test: two transfers spending the same wallet at the same time,
    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.
  • Or locally: 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). Integration
    tests 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 the
failure 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 concurrent
double-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

  • Background processing (202 then poll) so the service scales and recovery of unfinished
    transfers is real; a synchronous mode that waits for the result could be added on top.
  • Wallets are seeded (no creation API; out of scope). The stored balance is the source of
    truth; a job that checks the ledger sum matches each balance would sit alongside it in
    production.
  • Business failures are saved as FAILED; temporary failures are undone and retried, and
    anything left unfinished is recovered by the reaper.

Checklist

  • Tests pass
  • Lint passes
  • Format check passes
  • README or notes updated
  • PR description explains schema, idempotency, and concurrency

For More details - Check https://github.com/akashdesale98/wallet-transfer-assignment/blob/solution/aakash-desale/assignment_readme.md

Copilot AI review requested due to automatic review settings July 22, 2026 11:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread internal/domain/transfer.go

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 65 out of 68 changed files in this pull request and generated 6 comments.

Comment thread go.mod
Comment thread internal/service/retry.go Outdated
Comment thread internal/handler/server.go
Comment thread internal/handler/dto.go
Comment thread internal/handler/transfer_handler.go
Comment thread internal/handler/server.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 65 out of 68 changed files in this pull request and generated 3 comments.

Comment thread internal/service/processor.go
Comment thread internal/handler/transfer_handler.go
Comment thread assignment_readme.md Outdated
@akashdesale98
akashdesale98 force-pushed the solution/aakash-desale branch from f058111 to 42ac4f9 Compare July 22, 2026 12:49
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.

2 participants