Develop a microservice for working with users' balance (balance, crediting / debiting / transferring funds). The service must provide an HTTP API and accept/return requests/responses in JSON format. Additionally, implement methods for converting the balance and obtaining a list of transactions. Full description in TASK.
- Following the REST API design.
- Clean architecture and dependency injection
- Working with framework labstack/echo.
- Working with Postgres using sqlx and writing SQL queries.
- App configuration with viper library.
- Launching with Docker.
- Unit/Integration testing using mocks testify, sqlmock, gomock.
Project structure:
.
├── internal // business logic
│ ├── handler
│ ├── service
│ └── repository
├── cmd
├── pkg // Importable code (logging and utils)
│ ├── utils
│ └── logging
├── schema // SQL migrations files
├── configs // App configs
├── models // Custom types
├── scripts // Shell scripts
├── docs // Swagger documentation
- GET /balance/{user_id} - get user`s balance
- Path variables:
- user_id - unique user`s id.
- Query params:
- currency - convert user`s balance to currency (EUR by default).
- Path variables:
- GET /transactions/{user_id} - get user`s transactions
- Path variables:
- user_id - unique user`s id.
- Query params:
- page
- limit - number of transactions per page
- sort
- Path variables:
- POST /top-up/{user_id} - replenishment of the user's balance
- Path variables:
- user_id - unique user`s id,
- Request body:
- amount - replenishment amount in EUR.
- Path variables:
- POST /debit/{user_id} - write-off from the user's balance
- Path variables:
- user_id - unique user`s id,
- Request body:
- amount - replenishment amount in EUR.
- Path variables:
- POST /transfer/ - transferring funds to the balance of another user
- Path variables:
- user_id - unique user`s id,
- Request body:
- to_id - id of the user whose balance the funds are credited to,
- amount - transfer amount in EUR.
- Path variables:
make compose-build
make compose-up
To run tests, use:
make test
Request:
$ curl --location --request GET 'localhost:8080/balance/1' \
--header 'Content-Type: application/json'
Response body:
{
"user_id": 1,
"balance": 4.13
}
Request:
$ curl --location --request GET 'localhost:8080/balance/1?currency=UAH' \
--header 'Content-Type: application/json'
Response body:
{
"user_id": 1,
"balance": 165.43
}
Request:
$ curl --location --request GET 'localhost:8080/transactions/1?page=1&limit=1&sort=date' \
--header 'Content-Type: application/json'
Response body:
[
{
"id": 1,
"user_id": 1,
"amount": 30,
"operation": "",
"date": "2023-06-14 02:19:40"
}
]
$ curl --location --request GET 'localhost:8080/transactions/2?page=1&limit=2&sort=date' \
--header 'Content-Type: application/json'
Response body:
[
{
"id": 2,
"user_id": 2,
"amount": 101,
"operation": "",
"date": "2023-06-14 02:19:40"
},
{
"id": 3,
"user_id": 2,
"amount": 32,
"operation": "",
"date": "2023-06-14 02:19:40"
}
]
Request:
$ curl --location --request POST 'localhost:8080/top-up' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id":1,
"amount":1000
}'
Response body:
{
"user_id": 1,
"balance": 1004.13
}
Request:
$ curl --location --request POST 'localhost:8080/debit' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id":1,
"amount":1000
}'
Response body:
{
"user_id": 1,
"balance": 4.13
}
But if you try to do it again, there will no enough money to perform debit:
{
"message": "not enough money to perform purchase"
}
Request:
$ curl --location --request POST 'localhost:8080/transfer' \
--header 'Content-Type: application/json' \
--data-raw '{
"user_id":1,
"to_id":2,
"amount":1
}'
Response body:
{
"user_id": 2,
"balance": 33
}