Skip to content

Commit

Permalink
InMemory prod repo + readme updated
Browse files Browse the repository at this point in the history
kamil.jedrzejuk committed Oct 7, 2024
1 parent 868f821 commit aa83eb0
Showing 3 changed files with 121 additions and 17 deletions.
96 changes: 79 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,93 @@

# Currency Exchange API (PLN <-> USD)

## Overview

This is a Kotlin-based Spring Boot application that provides a REST API for creating currency accounts and performing currency exchanges between PLN (Polish Zloty) and USD (US Dollar). The exchange rates are retrieved from the National Bank of Poland (NBP) public API.

## Functional Requirements
The project follows **Domain-Driven Design (DDD)** principles, where core domain logic is encapsulated in domain aggregates, ensuring that the business logic is well-organized and scalable. The endpoints provided by the application are idempotent, ensuring consistency of operations, even when they are retried.

## Features

1. The application exposes a REST API for creating currency accounts.
2. When creating an account, the user must provide the initial balance in PLN.
3. The user must also provide their full name (first and last name).
4. The application generates a unique account identifier when a new account is created. This identifier is used in further API interactions.
5. The application exposes a REST API for exchanging money between PLN and USD, fetching the current exchange rate from the public NBP API.
6. The application exposes a REST API for retrieving account details, including the current balance in PLN and USD.
1. **Account Management**:
- REST API for creating currency accounts.
- When creating an account, the user must provide an initial balance in PLN and their full name.
- A unique account identifier is generated for future interactions.

## Non-Functional Requirements
2. **Currency Exchange**:
- REST API for exchanging currencies between PLN and USD, using live exchange rates from the NBP API.

1. The application is written in Kotlin and uses the Spring Boot framework.
2. The application does not persist data after a restart (in-memory storage).
3. The source code is hosted on a version control platform like GitHub, GitLab, or Bitbucket.
4. The application is built using a build tool such as Maven or Gradle.
5. Any unspecified details are left to the developer's discretion.
6. If any questions arise, clarification should be requested by email.
3. **Balance Retrieval**:
- REST API for retrieving account details, including current balances in PLN and USD.

## Technologies

- **Language**: Kotlin
- **Framework**: Spring Boot
- **Build tool**: Gradle
- **Testing**: JUnit 5
- **External API**: NBP public API for exchange rates [http://api.nbp.pl/](http://api.nbp.pl/)
- **Build Tool**: Gradle
- **External API**: NBP API for exchange rates
- **Testing**: JUnit 5, tests based on the [Readable Tests by Example](https://blog.allegro.tech/2022/02/readable-tests-by-example.html) approach to improve readability and maintainability.

## Architecture

The application is structured according to **Domain-Driven Design (DDD)**. It contains well-defined aggregates that represent the key business entities, ensuring that domain logic and data consistency are managed centrally. Here's an example from the `Account` aggregate:

```kotlin
class Account(
val id: AccountId,
val name: String,
var balancePLN: BigDecimal,
var balanceUSD: BigDecimal
) {
fun exchangeCurrency(amount: BigDecimal, exchangeRate: BigDecimal) {
// Business logic to handle currency exchange
}
}
```

## Running the Application

1. Clone the repository:
```bash
git clone https://github.com/CamilYed/currency-exchange-api.git
cd currency-exchange-api
```

2. Build the project:
```bash
./gradlew build
```

3. Run the application:
```bash
./gradlew bootRun
```

3. Run tests:
```bash
./gradlew check
```

4. The application will be accessible at `http://localhost:8080`.

## TODO

The following features and tests are still missing or require improvements:

1. **Missing REST API for Account Details**:
- Endpoint for retrieving account details (current balances in PLN and USD).
- Tests for the account details API.

2. **Missing Exchange Endpoint (USD to PLN)**:
- An additional API endpoint for currency exchange from USD to PLN.
- Tests to cover this exchange scenario.

3. **Optimistic Locking Tests**:
- Currently, there are no tests to validate how optimistic locking is handled during concurrent transactions.

4. **Error Handling Tests**:
- Tests to handle scenarios where the NBP API is unavailable (e.g., simulate HTTP 500 responses and ensure that FeignClient retries or fallback mechanisms work as expected).

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
package camilyed.github.io.currencyexchangeapi.domain

import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap

interface AccountOperationRepository {

fun findAccountIdBy(operationId: UUID): UUID?

fun save(events: List<AccountEvent>)
}

@Component
@Profile("!test")
class InMemoryAccountOperationRepository : AccountOperationRepository {

private val operations = ConcurrentHashMap<UUID, UUID>()
private val events = mutableListOf<AccountEvent>()

override fun findAccountIdBy(operationId: UUID): UUID? {
return operations[operationId]
}

override fun save(events: List<AccountEvent>) {
events.forEach { event ->
this.events.add(event)
operations[event.operationId] = event.accountId
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package camilyed.github.io.currencyexchangeapi.domain

import org.springframework.context.annotation.Profile
import org.springframework.stereotype.Component
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap

interface AccountRepository {
fun nextAccountId(): UUID = UUID.randomUUID()
@@ -9,3 +12,20 @@ interface AccountRepository {

fun find(id: UUID): Account?
}

@Component
@Profile("!test")
class InMemoryAccountRepository : AccountRepository {
private val accounts = ConcurrentHashMap<UUID, AccountSnapshot>()

override fun nextAccountId(): UUID = UUID.randomUUID()

override fun save(account: Account) {
val snapshot = account.toSnapshot()
accounts[snapshot.id] = snapshot
}

override fun find(id: UUID): Account? {
return accounts[id]?.let { Account.fromSnapshot(it) }
}
}

0 comments on commit aa83eb0

Please sign in to comment.