Solution/alkajaiswal#94
Open
alka7321 wants to merge 3 commits into
Open
Conversation
There was a problem hiding this comment.
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 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 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 on lines
+45
to
+48
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-webmvc</artifactId> | ||
| </dependency> |
Comment on lines
+49
to
+52
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-jackson</artifactId> | ||
| </dependency> |
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 on lines
+9
to
+11
| h2: | ||
| console: | ||
| enabled: true |
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
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
Schema Design
We introduced 4 primary tables in H2 (PostgreSQL Mode):
wallets:id(VARCHAR, PK)balance(DECIMAL(19, 4), NOT NULL) - using high precision decimal to avoid floating-point issuescreated_at/updated_at(TIMESTAMP)balance >= 0to prevent negative balances at the database level.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, FAILEDerror_message(VARCHAR)idempotency_key(VARCHAR, UNIQUE)ledger_entries:id(BIGINT, PK, Auto-Increment)wallet_id(VARCHAR, FK to wallets)transfer_id(VARCHAR, FK to transfers)type(VARCHAR) - DEBIT, CREDITamount(DECIMAL(19, 4), NOT NULL)idempotency_records:idempotency_key(VARCHAR, PK)status_code(INT)response_body(TEXT) - serialized JSON responseIdempotency Strategy
API idempotency is managed by the
IdempotencyServiceandTransferService:idempotencyKeyin theidempotency_recordstable. If found, the cached status and serialized response body are returned immediately.transferstable holds aUNIQUEindex constraint onidempotency_key.idempotency_keyis violated by the second transaction. This throws aDataIntegrityViolationException, which we catch and resolve by fetching the completed transaction result of the first request.Concurrency Strategy
@Lock(LockModeType.PESSIMISTIC_WRITE)(SELECT ... FOR UPDATEat the database level). This blocks any other concurrent transfer from reading or modifying the balances until the current transaction commits.@Transactionalon the executor class to guarantee atomicity. If any step fails (e.g. database disconnect, insufficient funds), the transaction rolls back safely.How to Run
.\mvnw.cmd spring-boot:run