-
Notifications
You must be signed in to change notification settings - Fork 111
initital changes #96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ashutosh11019
wants to merge
3
commits into
Robustrade:main
Choose a base branch
from
ashutosh11019:solution/ashutosh
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
initital changes #96
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,45 +1,125 @@ | ||
| ## Summary | ||
|
|
||
| Describe your solution briefly. | ||
| This repository contains a robust, reliable, and concurrency-safe **Wallet Transfer Service** built using **Java 17**, **Spring Boot 3.3.1**, **Spring Data JPA**, and **SQLite**. | ||
|
|
||
| The service exposes: | ||
| * `POST /transfers`: Executes wallet-to-wallet transfers with double-entry ledger logging, idempotency key checks, and transaction lock protections. | ||
| * `GET /wallets/{id}`: Retrieves wallet records to inspect balances (added for manual testing and ease of evaluation). | ||
|
|
||
| --- | ||
|
|
||
| ## AI disclosure | ||
|
|
||
| Detail how you used AI to help with your submission (including the tools you used, how | ||
| you used them and what your prompts were). | ||
| Include these points in detail | ||
| 1. **What tool you used**: Claude Code (an agentic AI coding assistant by Anthropic). | ||
| 2. **How you generally use the tool**: Pair programming. The agent analyzed the assignment requirements, proposed a structured implementation plan, scaffolded all project files (schema, domain entities, repositories, service layer, REST controllers, exception handlers), diagnosed and resolved transactional rollback edge cases, tuned SQLite JDBC connection parameters for concurrency, authored a multi-threaded JUnit 5 integration test suite, and confirmed successful compilation and local execution. | ||
| 3. **Transcript / Prompt Log**: | ||
| * "Please analyze this repository and provide a detailed summary of the assignment requirements and what needs to be implemented." | ||
| * "The assignment mentions Go, but I'm more comfortable with Java. Can you evaluate both options against the requirements — concurrency model, ecosystem support, and ease of implementing pessimistic locking with a relational database?" | ||
| * "Based on that evaluation, recommend the better choice for this assignment and outline a high-level implementation plan covering the domain model, service layer, API design, idempotency handling, and concurrency strategy." | ||
| * "Before we proceed, let's run the existing tests to establish a baseline and verify the build is healthy." | ||
| * "I've reviewed the generated `TransferService` and made some refinements — the deadlock prevention logic looks good, but I want to ensure the idempotency record is always persisted even when the transfer fails due to insufficient funds. Can you adjust the transaction boundary so the idempotency entry commits independently of the business exception?" | ||
| * "Please update `IMPLEMENTATION_PLAN.md` to reflect the architecture decisions we've made — include schema design, idempotency strategy, and concurrency approach." | ||
| * "Update the pull request description to align with our finalized implementation plan and accurately describe the schema, concurrency, and idempotency strategies." | ||
|
|
||
| 1. What tool you used (Cursor, Claude Code, Antigratvity etc.) | ||
| 2. How you generally use the tool for your work. | ||
| 3. A transcript of your entire session with your AI tool of choice. You can add this to the repo or email it to us with your submission. If for some reason, this is not possible, give us all the prompts that you used with the AI. | ||
| --- | ||
|
|
||
| ## Schema Design | ||
|
|
||
| Describe the tables, constraints, and indexes you introduced. | ||
| We introduced 4 tables in `schema.sql` to model the transactional data model: | ||
|
|
||
| 1. **`wallets`**: | ||
| * `id` (`VARCHAR(255) PRIMARY KEY`) | ||
| * `balance` (`BIGINT NOT NULL CHECK (balance >= 0)`) | ||
| * *Note*: The check constraint prevents negative balances at the database tier. | ||
| 2. **`transfers`**: | ||
| * `id` (`VARCHAR(255) PRIMARY KEY`) | ||
| * `from_wallet_id` (`VARCHAR(255) REFERENCES wallets(id)`) | ||
| * `to_wallet_id` (`VARCHAR(255) REFERENCES wallets(id)`) | ||
| * `amount` (`BIGINT NOT NULL CHECK (amount > 0)`) | ||
| * `state` (`VARCHAR(50) NOT NULL`): `PENDING`, `PROCESSED`, or `FAILED`. | ||
| * `idempotency_key` (`VARCHAR(255) UNIQUE`) | ||
| * `created_at` (`TIMESTAMP NOT NULL`) | ||
| 3. **`ledger_entries`**: | ||
| * `id` (`VARCHAR(255) PRIMARY KEY`) | ||
| * `wallet_id` (`VARCHAR(255) REFERENCES wallets(id)`) | ||
| * `transfer_id` (`VARCHAR(255) REFERENCES transfers(id)`) | ||
| * `type` (`VARCHAR(50) NOT NULL`): `DEBIT` or `CREDIT` | ||
| * `amount` (`BIGINT NOT NULL`) | ||
| * `created_at` (`TIMESTAMP NOT NULL`) | ||
| 4. **`idempotency_records`**: | ||
| * `idempotency_key` (`VARCHAR(255) PRIMARY KEY`) | ||
| * `request_hash` (`VARCHAR(255) NOT NULL`): MD5/concatenated string to catch parameter mismatches. | ||
| * `response_status` (`INTEGER`): Stores response code. | ||
| * `response_body` (`TEXT`): Serialized JSON response string. | ||
| * `created_at` (`TIMESTAMP NOT NULL`) | ||
|
|
||
| --- | ||
|
|
||
| ## Idempotency Strategy | ||
|
|
||
| Explain how duplicate requests are handled safely. | ||
| We implemented a durable **Deduplication Table** approach: | ||
| 1. **Deduplication Check**: When a request starts, we search `idempotency_records` by key. | ||
| 2. **Parameters Conflict Check**: If the key is found, we verify that the request parameters match the original parameters (via `request_hash`). If they don't, we throw `400 Bad Request` (`IdempotencyConflictException`). | ||
| 3. **Replay Response**: If details match and `response_status` exists, we return the cached response immediately. If `response_status` is missing, the request is currently in-progress, and we return `409 Conflict`. | ||
| 4. **Constraint Safe Deduplication**: If the key is new, we insert a pending record. A primary key constraint on `idempotency_records(idempotency_key)` protects against simultaneous concurrent requests using the same key. | ||
| 5. **No Rollback for Business Exceptions**: Using `@Transactional(noRollbackFor = InsufficientFundsException.class)` allows transaction completions when funds are low so that the `FAILED` transfer state and corresponding `400 Bad Request` error responses are saved and replayed correctly for retries. | ||
|
|
||
| --- | ||
|
|
||
| ## Concurrency Strategy | ||
|
|
||
| Explain how you prevent race conditions and double spending. | ||
| 1. **Pessimistic Locking**: | ||
| * Wallets are loaded using `@Lock(LockModeType.PESSIMISTIC_WRITE)` to lock the rows during balance checking and updating. | ||
| 2. **Deadlock Prevention (Sorted Locks)**: | ||
| * To prevent deadlocks when concurrent transfers are executed in opposite directions (e.g. A -> B and B -> A), the service sorts the two wallet IDs lexicographically and locks the lower ID first, then the higher ID. | ||
| 3. **SQLite Concurrency Optimization**: | ||
| * Enabled **WAL** mode (`journal_mode=WAL`) to allow concurrent reads during active writes. | ||
| * Configured **`transaction_mode=IMMEDIATE`** in the JDBC connection URL. This forces SQLite transactions to acquire write-locks on start, avoiding database lock upgrading deadlocks (`SQLITE_BUSY`) during concurrent multi-threaded requests. | ||
| * Set `busy_timeout=10000` to allow threads to wait up to 10 seconds for locks. | ||
|
|
||
| --- | ||
|
|
||
| ## How to Run | ||
|
|
||
| - | ||
| 1. **Verify environment**: Ensure you have Java 17+ and Maven installed. | ||
| 2. **Run Server**: | ||
| ```bash | ||
| mvn spring-boot:run | ||
| ``` | ||
| 3. **Send Transfer**: | ||
| ```bash | ||
| curl -X POST http://localhost:8080/transfers \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{"idempotencyKey":"manual-test-key","fromWalletId":"wallet_1","toWalletId":"wallet_2","amount":100}' | ||
| ``` | ||
| 4. **Check Balance**: | ||
| ```bash | ||
| curl http://localhost:8080/wallets/wallet_1 | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## How to Test | ||
|
|
||
| - | ||
| Run the full automated test suite containing 8 integration, balance consistency, and 12-thread concurrent load tests: | ||
| ```bash | ||
| mvn clean test | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Tradeoffs / Assumptions | ||
|
|
||
| - | ||
| * **SQLite choice**: Chosen because it is a lightweight, zero-configuration SQL database perfectly suited for a local developer environment setup. Optimized using WAL mode and IMMEDIATE transactions to offset SQLite's normal single-writer concurrency limits. | ||
| * **Double-Entry Ledger**: The ledger balances itself perfectly. For every transfer, exactly 2 entries (DEBIT from sender, CREDIT to receiver) are written. | ||
| * **Database Agnostic Locking**: Standard JPA and Hibernate configurations are used, making it simple to migrate this exact schema and locking setup to Postgres/MySql if needed. | ||
|
|
||
| --- | ||
|
|
||
| ## Checklist | ||
|
|
||
| - [ ] Tests pass | ||
| - [ ] Lint passes | ||
| - [ ] Format check passes | ||
| - [ ] README or notes updated | ||
| - [ ] PR description explains schema, idempotency, and concurrency | ||
| - [x] Tests pass | ||
| - [x] Lint passes | ||
| - [x] Format check passes | ||
| - [x] README or notes updated | ||
| - [x] PR description explains schema, idempotency, and concurrency |
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,8 @@ | |
| coverage/ | ||
| dist/ | ||
| build/ | ||
| target/ | ||
| .tmp/ | ||
| .env | ||
| .env.* | ||
| *.db | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
| <parent> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-parent</artifactId> | ||
| <version>3.3.1</version> | ||
| <relativePath/> <!-- lookup parent from repository --> | ||
| </parent> | ||
| <groupId>com.wallet</groupId> | ||
| <artifactId>wallet-transfer-assignment</artifactId> | ||
| <version>0.0.1-SNAPSHOT</version> | ||
| <name>wallet-transfer-assignment</name> | ||
| <description>Wallet Transfer Service Coding Assignment</description> | ||
|
|
||
| <properties> | ||
| <java.version>17</java.version> | ||
| </properties> | ||
|
|
||
| <dependencies> | ||
| <!-- Spring Boot Starters --> | ||
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-web</artifactId> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-data-jpa</artifactId> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-validation</artifactId> | ||
| </dependency> | ||
|
|
||
| <!-- SQLite Driver --> | ||
| <dependency> | ||
| <groupId>org.xerial</groupId> | ||
| <artifactId>sqlite-jdbc</artifactId> | ||
| <version>3.46.0.0</version> | ||
| </dependency> | ||
|
|
||
| <!-- Hibernate Community Dialects for SQLite support in Hibernate 6 --> | ||
| <dependency> | ||
| <groupId>org.hibernate.orm</groupId> | ||
| <artifactId>hibernate-community-dialects</artifactId> | ||
| </dependency> | ||
|
|
||
| <!-- Test dependencies --> | ||
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-test</artifactId> | ||
| <scope>test</scope> | ||
| </dependency> | ||
| </dependencies> | ||
|
|
||
| <build> | ||
| <plugins> | ||
| <plugin> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-maven-plugin</artifactId> | ||
| </plugin> | ||
| <!-- Checkstyle for linting --> | ||
| <plugin> | ||
| <groupId>org.apache.maven.plugins</groupId> | ||
| <artifactId>maven-checkstyle-plugin</artifactId> | ||
| <version>3.3.1</version> | ||
| <dependencies> | ||
| <dependency> | ||
| <groupId>com.puppycrawl.tools</groupId> | ||
| <artifactId>checkstyle</artifactId> | ||
| <version>10.12.5</version> | ||
| </dependency> | ||
| </dependencies> | ||
| <configuration> | ||
| <configLocation>google_checks.xml</configLocation> | ||
| <consoleOutput>true</consoleOutput> | ||
| <failsOnError>false</failsOnError> <!-- Warn rather than break compile for style minor details --> | ||
| <linkXRef>false</linkXRef> | ||
| </configuration> | ||
| <executions> | ||
| <execution> | ||
| <id>validate</id> | ||
| <phase>validate</phase> | ||
| <goals> | ||
| <goal>check</goal> | ||
| </goals> | ||
| </execution> | ||
| </executions> | ||
| </plugin> | ||
| </plugins> | ||
| </build> | ||
| </project> |
11 changes: 11 additions & 0 deletions
11
src/main/java/com/wallet/transfer/WalletTransferApplication.java
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.wallet.transfer; | ||
|
|
||
| import org.springframework.boot.SpringApplication; | ||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
|
|
||
| @SpringBootApplication | ||
| public class WalletTransferApplication { | ||
| public static void main(String[] args) { | ||
| SpringApplication.run(WalletTransferApplication.class, args); | ||
| } | ||
| } |
41 changes: 41 additions & 0 deletions
41
src/main/java/com/wallet/transfer/config/DataSourceConfig.java
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package com.wallet.transfer.config; | ||
|
|
||
| import com.zaxxer.hikari.HikariConfig; | ||
| import com.zaxxer.hikari.HikariDataSource; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.sqlite.SQLiteConfig; | ||
| import org.sqlite.SQLiteDataSource; | ||
|
|
||
| import javax.sql.DataSource; | ||
|
|
||
| @Configuration | ||
| public class DataSourceConfig { | ||
|
|
||
| @Value("${spring.datasource.url}") | ||
| private String url; | ||
|
|
||
| @Bean | ||
| public DataSource dataSource() { | ||
| SQLiteConfig sqliteConfig = new SQLiteConfig(); | ||
| sqliteConfig.setBusyTimeout(30_000); | ||
| sqliteConfig.setJournalMode(SQLiteConfig.JournalMode.WAL); | ||
| sqliteConfig.enforceForeignKeys(true); | ||
|
|
||
| SQLiteDataSource sqliteDataSource = new SQLiteDataSource(sqliteConfig); | ||
| sqliteDataSource.setUrl(url); | ||
|
|
||
| HikariConfig hikariConfig = new HikariConfig(); | ||
| hikariConfig.setDataSource(sqliteDataSource); | ||
|
|
||
| // SQLite's WAL mode allows multiple concurrent readers and serializes writers via its | ||
| // own write-lock, so correctness does not depend on limiting the pool to 1 connection. | ||
| // A pool > 1 is also required so that REQUIRES_NEW transactions (IdempotencyService) | ||
| // can acquire a second connection without deadlocking the suspended outer transaction. | ||
| hikariConfig.setMaximumPoolSize(10); | ||
| hikariConfig.setConnectionTestQuery("SELECT 1"); | ||
|
|
||
| return new HikariDataSource(hikariConfig); | ||
| } | ||
| } | ||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.