Implement wallet transfer service - #109
Open
sumiyapuletipalli wants to merge 1 commit into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
Implements a PostgreSQL-backed wallet transfer API in Go with a layered architecture, focusing on transactional transfers, idempotency, concurrency control, and a double-entry ledger.
Changes:
- Added domain models (wallet, transfer state machine, ledger, idempotency) and a transfer service orchestrating transactional behavior.
- Implemented PostgreSQL Unit of Work / transaction adapter plus an initial schema migration.
- Added HTTP API (
POST /transfers,GET /healthz), Docker Compose setup, and unit/integration tests.
Reviewed changes
Copilot reviewed 24 out of 26 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/integration/transfer_test.go | Integration tests for idempotency, failures, ledger behavior, and concurrent debits. |
| README.md | Updated documentation describing architecture, API, and run/test instructions. |
| migrations/001_init.sql | Initial PostgreSQL schema for wallets, transfers, idempotency records, and ledger entries. |
| internal/service/transfer.go | Core transfer orchestration: idempotency claim/replay, wallet locking, balance updates, ledger writes, state transitions. |
| internal/service/transfer_test.go | Unit tests for service behavior (processed, failed, replay, validation). |
| internal/repository/repository.go | Repository/transaction contracts (UnitOfWork + Transaction interfaces). |
| internal/repository/postgres/transaction.go | PostgreSQL implementation of transaction operations (idempotency, locks, updates, ledger batch inserts). |
| internal/repository/postgres/postgres.go | PostgreSQL UnitOfWork transaction boundary implementation. |
| internal/platform/logging/logging.go | JSON slog logger factory. |
| internal/platform/database/postgres.go | pgxpool connection setup and ping. |
| internal/platform/config/config.go | Environment-based configuration loader and timeouts. |
| internal/handler/http/handler.go | HTTP routes, request decoding/validation wiring, error mapping, and logging middleware. |
| internal/handler/http/handler_test.go | Handler tests for success path, conflict mapping, and unknown-field rejection. |
| internal/domain/wallet.go | Wallet domain model. |
| internal/domain/transfer.go | Transfer domain model + state transition methods. |
| internal/domain/transfer_test.go | Tests for transfer state transitions. |
| internal/domain/ledger.go | Ledger entry domain model. |
| internal/domain/idempotency.go | Idempotency record domain model. |
| internal/domain/errors.go | Domain error definitions used across layers. |
| go.sum | Dependency checksums. |
| go.mod | Module definition and dependencies (pgx v5, etc.). |
| docker-compose.yml | Local dev orchestration for API + Postgres + migration init. |
| deployments/docker/Dockerfile | Multi-stage build for the API container image. |
| cmd/api/main.go | Application wiring: config, DB, service, handler, server lifecycle/shutdown. |
| .gitignore | Adds build/test artifacts and IDE config ignores. |
| .dockerignore | Docker build context exclusions. |
Comment on lines
+191
to
+208
| func validateCommand(command TransferCommand) error { | ||
| if strings.TrimSpace(command.IdempotencyKey) == "" || len(command.IdempotencyKey) > 255 { | ||
| return domain.ErrInvalidIdempotencyKey | ||
| } | ||
| if strings.TrimSpace(command.FromWalletID) == "" || | ||
| strings.TrimSpace(command.ToWalletID) == "" || | ||
| len(command.FromWalletID) > 255 || | ||
| len(command.ToWalletID) > 255 { | ||
| return domain.ErrInvalidWalletID | ||
| } | ||
| if command.FromWalletID == command.ToWalletID { | ||
| return domain.ErrSameWallet | ||
| } | ||
| if command.Amount <= 0 { | ||
| return domain.ErrInvalidAmount | ||
| } | ||
| return nil | ||
| } |
Comment on lines
+186
to
+195
| var exists bool | ||
| if err := pool.QueryRow( | ||
| context.Background(), | ||
| "SELECT to_regclass('public.wallets') IS NOT NULL", | ||
| ).Scan(&exists); err != nil { | ||
| t.Fatalf("check schema: %v", err) | ||
| } | ||
| if exists { | ||
| return | ||
| } |
Author
|
Hi, I have completed the assignment and submitted the implementation. Please let me know if any additional changes or clarifications are required. Thank you! |
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:
Implemented a wallet transfer service in Go using PostgreSQL, following a clean layered architecture with support for transactional transfers, idempotency, concurrency control, and a double-entry ledger.
Features implemented:
AI disclosure
AI tool used:
How I used the tool
The implementation, testing, debugging, and final verification were performed manually before submission.
3.Transcript / prompts
Schema Design
The solution uses PostgreSQL with the following tables:
wallets– Stores wallet information and current balance.transfers– Stores transfer details, amount, and transfer status.idempotency_records– Prevents duplicate processing of the same request.ledger_entries– Records debit and credit entries for every successful transfer.The schema includes primary keys, foreign keys, unique constraints, check constraints, and indexes to ensure data integrity and improve query performance.
Idempotency Strategy
Each transfer request includes an
idempotencyKey.Concurrency Strategy
To prevent race conditions and double spending:
SELECT ... FOR UPDATE.How to Run
docker compose up --build
How to Test
----> I also tested the API manually using Postman for successful transfer, duplicate idempotency key, invalid payload, missing wallet, and insufficient balance scenarios.
Tradeoffs / Assumptions
Checklist