Skip to content

Implement wallet transfer service - #109

Open
sumiyapuletipalli wants to merge 1 commit into
Robustrade:mainfrom
sumiyapuletipalli:solution/sumiya
Open

Implement wallet transfer service#109
sumiyapuletipalli wants to merge 1 commit into
Robustrade:mainfrom
sumiyapuletipalli:solution/sumiya

Conversation

@sumiyapuletipalli

Copy link
Copy Markdown

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:

  • POST /transfers endpoint
  • Transactional wallet transfers
  • Idempotency handling
  • Double-entry ledger
  • Row-level locking for concurrency
  • Transfer state management (PENDING, PROCESSED, FAILED)
  • Docker and Docker Compose support
  • Unit and integration tests

AI disclosure

  1. AI tool used:

    • ChatGPT
  2. How I used the tool

  • Discussed alternative design approaches during development.
  • Clarified concepts related to PostgreSQL transactions, idempotency, and concurrency.
  • Reviewed parts of the implementation to identify possible improvements.

The implementation, testing, debugging, and final verification were performed manually before submission.

3.Transcript / prompts

  • ChatGPT was used as a development assistant for discussion and review purposes. I can provide the prompts or conversation history if required.

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.

  • A unique idempotency record is created for every new request.
  • If the same request is received again with the same key, the original result is returned.
  • If the same key is used with a different request payload, the request is rejected with a conflict response.
  • This prevents duplicate transfers and ensures exactly-once behavior.

Concurrency Strategy

To prevent race conditions and double spending:

  • All transfer operations are executed inside a single PostgreSQL transaction.
  • The source and destination wallet rows are locked using SELECT ... FOR UPDATE.
  • Wallet balances, ledger entries, and transfer status are updated atomically.
  • If any step fails, the transaction is rolled back, ensuring the database remains consistent.

How to Run

docker compose up --build

How to Test

go 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

  • PostgreSQL is used as the primary persistence layer.
  • Wallet balances are stored directly for faster reads, while ledger entries provide the audit trail.
  • Business failures such as insufficient balance are stored as FAILED transfers.
  • Infrastructure/database errors are rolled back and can be safely retried by the client using the same idempotency key.

Checklist

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

Copilot AI review requested due to automatic review settings July 7, 2026 13:25

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 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
}
@sumiyapuletipalli

Copy link
Copy Markdown
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!

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