Skip to content

Solution/alkajaiswal#94

Open
alka7321 wants to merge 3 commits into
Robustrade:mainfrom
alka7321:solution/alkajaiswal
Open

Solution/alkajaiswal#94
alka7321 wants to merge 3 commits into
Robustrade:mainfrom
alka7321:solution/alkajaiswal

Conversation

@alka7321

Copy link
Copy Markdown

Summary

This PR implements the core Wallet-to-Wallet Transfer Service backend. The solution is built with Spring Boot 3.x, Java 21, Spring Data JPA, and H2 Database (running in PostgreSQL compatibility mode). It provides reliable transactional boundaries, double-entry ledger entries, optimistic/pessimistic concurrency controls, and exactly-once API delivery.


AI disclosure

  1. What tool you used:
    • Antigravity (Advanced Agentic Coding assistant developed by the Google DeepMind team).
  2. How you generally use the tool for your work:
    • I used Antigravity as a senior developer pair-programmer to research the initial requirements, structure the project architecture, verify compilation logs, write developer logs, and perform end-to-end verification.
  3. A transcript of your entire session:

Schema Design

We introduced 4 primary tables in H2 (PostgreSQL Mode):

  1. wallets:
    • id (VARCHAR, PK)
    • balance (DECIMAL(19, 4), NOT NULL) - using high precision decimal to avoid floating-point issues
    • created_at / updated_at (TIMESTAMP)
    • Constraint: CHECK balance >= 0 to prevent negative balances at the database level.
  2. transfers:
    • id (VARCHAR, PK, UUID)
    • from_wallet_id / to_wallet_id (VARCHAR, FK to wallets)
    • amount (DECIMAL(19, 4), NOT NULL)
    • status (VARCHAR) - PENDING, PROCESSED, FAILED
    • error_message (VARCHAR)
    • idempotency_key (VARCHAR, UNIQUE)
  3. ledger_entries:
    • id (BIGINT, PK, Auto-Increment)
    • wallet_id (VARCHAR, FK to wallets)
    • transfer_id (VARCHAR, FK to transfers)
    • type (VARCHAR) - DEBIT, CREDIT
    • amount (DECIMAL(19, 4), NOT NULL)
  4. idempotency_records:
    • idempotency_key (VARCHAR, PK)
    • status_code (INT)
    • response_body (TEXT) - serialized JSON response

Idempotency Strategy

API idempotency is managed by the IdempotencyService and TransferService:

  • On receiving a request, the database is checked for the idempotencyKey in the idempotency_records table. If found, the cached status and serialized response body are returned immediately.
  • If not found, the transfer is executed. The transfers table holds a UNIQUE index constraint on idempotency_key.
  • Concurrent duplicate requests: If two identical requests execute simultaneously, the database's unique constraint on idempotency_key is violated by the second transaction. This throws a DataIntegrityViolationException, which we catch and resolve by fetching the completed transaction result of the first request.

Concurrency Strategy

  • Pessimistic Locking: When starting a transfer, we fetch the source and destination wallets using @Lock(LockModeType.PESSIMISTIC_WRITE) (SELECT ... FOR UPDATE at the database level). This blocks any other concurrent transfer from reading or modifying the balances until the current transaction commits.
  • Deadlock Avoidance: To prevent circular wait conditions when concurrent transfers happen in opposite directions between the same two accounts (e.g., A -> B and B -> A), we sort the wallet IDs lexicographically. The locks are always acquired in ascending order of wallet IDs.
  • Transaction boundary: Handled via Spring's @Transactional on the executor class to guarantee atomicity. If any step fails (e.g. database disconnect, insufficient funds), the transaction rolls back safely.

How to Run

  • Run the server from your terminal:
    .\mvnw.cmd spring-boot:run

@alka7321
alka7321 requested a review from amitlambakulu as a code owner June 19, 2026 17:02
Copilot AI review requested due to automatic review settings June 19, 2026 17:02

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 Spring Boot–based wallet-to-wallet transfer backend with a layered architecture (web/service/repository/domain), aiming to provide transactional transfers, idempotent request handling, and double-entry ledger recording on an H2 (PostgreSQL mode) in-memory database.

Changes:

  • Added transfer API (POST /transfers) with request validation and a response model reflecting transfer status.
  • Implemented core transfer execution with pessimistic wallet locking, transfer persistence, and double-entry ledger writes.
  • Added integration/concurrency tests and accompanying run/setup documentation.

Reviewed changes

Copilot reviewed 36 out of 37 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
steps_completed.md Implementation and verification step log for the solution branch.
src/test/resources/application.yml Test configuration placeholder for the test suite.
src/test/java/com/example/wallet/web/TransferControllerTest.java Controller-level integration tests for transfers and idempotency replay.
src/test/java/com/example/wallet/WalletApplicationTests.java Spring context smoke test.
src/test/java/com/example/wallet/support/TestWalletFactory.java Test helper to create wallets in the database.
src/test/java/com/example/wallet/service/TransferConcurrencyTest.java Multi-threaded test to validate concurrent debits/transfers.
src/main/resources/application.yml Main Spring Boot datasource/JPA/H2 console configuration.
src/main/java/com/example/wallet/web/TransferController.java REST controller exposing the transfer endpoint.
src/main/java/com/example/wallet/web/dto/TransferResponse.java DTO representing transfer outcomes returned by the API.
src/main/java/com/example/wallet/web/dto/TransferRequest.java DTO for incoming transfer requests with validation annotations.
src/main/java/com/example/wallet/web/dto/ErrorResponse.java DTO for validation/error responses from the global exception handler.
src/main/java/com/example/wallet/WalletApplication.java Spring Boot application entrypoint.
src/main/java/com/example/wallet/service/TransferService.java Orchestrates idempotency checks and delegates to the executor.
src/main/java/com/example/wallet/service/TransferExecutor.java Transactional transfer execution: locking, balance updates, ledger writes.
src/main/java/com/example/wallet/service/TransferExecutionResult.java Internal result wrapper for HTTP status code + response body.
src/main/java/com/example/wallet/service/IdempotencyService.java Persistence-based idempotency cache for status codes + serialized responses.
src/main/java/com/example/wallet/repository/WalletRepository.java Wallet repository with pessimistic locking query.
src/main/java/com/example/wallet/repository/TransferRepository.java Transfer repository including idempotency key lookup.
src/main/java/com/example/wallet/repository/LedgerEntryRepository.java Ledger entry repository with transfer-based query helpers.
src/main/java/com/example/wallet/repository/IdempotencyRecordRepository.java Repository for persisted idempotency records.
src/main/java/com/example/wallet/domain/Wallet.java Wallet entity storing balance and timestamps.
src/main/java/com/example/wallet/domain/TransferStatus.java Enum for transfer lifecycle state.
src/main/java/com/example/wallet/domain/Transfer.java Transfer entity with unique idempotency key and status tracking.
src/main/java/com/example/wallet/domain/LedgerEntryType.java Enum for debit/credit ledger entry types.
src/main/java/com/example/wallet/domain/LedgerEntry.java Ledger entry entity for double-entry accounting records.
src/main/java/com/example/wallet/domain/IdempotencyRecord.java Entity storing idempotency key → status code + serialized response.
src/main/java/com/example/wallet/config/GlobalExceptionHandler.java Centralized validation error handling.
src/main/java/com/example/wallet/config/DevDataInitializer.java Non-test profile seed data initializer for local runs.
README.md Updated documentation describing architecture, idempotency, concurrency, and usage.
pom.xml Maven build definition and dependency set for Spring Boot + testing.
mvnw.cmd Maven wrapper script (Windows).
mvnw Maven wrapper script (Unix).
intellij_postman_db_setup.md Local dev setup instructions for IntelliJ/Postman/H2 console.
approach.md Architectural and design approach document.
.mvn/wrapper/maven-wrapper.properties Maven wrapper configuration and distribution URL.
.gitignore Ignores build output and IDE metadata.
.gitattributes Normalizes line endings for wrapper scripts.

Comment thread pom.xml
Comment on lines +5 to +10
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Comment thread pom.xml
Comment on lines +33 to +40
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-h2console</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Comment thread pom.xml
Comment on lines +45 to +48
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
Comment thread pom.xml
Comment on lines +49 to +52
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jackson</artifactId>
</dependency>
Comment thread pom.xml
Comment on lines +59 to +73
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc-test</artifactId>
<scope>test</scope>
</dependency>
@@ -0,0 +1 @@
# Test profile is activated automatically by @SpringBootTest.
Comment on lines +40 to +58
try {
return transferExecutor.execute(request);
} catch (DataIntegrityViolationException exception) {
return resolveDuplicateRequest(request.idempotencyKey());
}
}

private TransferExecutionResult resolveDuplicateRequest(String idempotencyKey) {
Optional<TransferExecutionResult> cached = idempotencyService.findCachedResult(idempotencyKey);
if (cached.isPresent()) {
return cached.get();
}

return transferRepository
.findByIdempotencyKey(idempotencyKey)
.map(this::buildResultFromTransfer)
.orElseThrow(() -> new IllegalStateException(
"Duplicate idempotency key detected but no transfer record found: " + idempotencyKey));
}
Comment on lines +9 to +14
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

@Entity
@Table(name = "wallets")
public class Wallet {
Comment on lines +26 to +30
@Column(name = "wallet_id", nullable = false)
private String walletId;

@Column(name = "transfer_id", nullable = false)
private String transferId;
Comment thread src/main/resources/application.yml Outdated
Comment on lines +9 to +11
h2:
console:
enabled: true
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