Read more about this project's motivations and reasonings:
- Go (Golang): Clean Architecture & Repositories vs Transactions
- Go (Golang): Unit of Work and Generics
- Go (Golang): Testing tools & tips to step up your game
This is a toy project based on rocket-rides-atomic repo and, of course, on the original Stripe's Rocket Rides demo as well. It aims to replicate the implementation of idempotency keys using Golang and Clean Architecture. Please refer to Brandur's amazing article about this topic for full details.
Quoting Brandur's own words about the project:
The work done API service is separated into atomic phases, and as the name suggests, all the work done during the phase is guaranteed to be atomic. Midway through each API request a call is made out to Stripe's API which can't be rolled back if it fails, so if it does we rely on clients re-issuing the API request with the same Idempotency-Key header until its results are definitive. After any request is considered to be complete, the results are stored on the idempotency key relation and returned for any future requests that use the same key.
.
├── api # HTTP transport layer
├── cmd # application commands
│ └── api # 'main.go' for running the API server
├── datastore # Postgres data access based on Bun ORM
│ └── uow # unit-of-work handling
├── db # database related files
│ ├── fixtures # fixtures used in integration tests and local development
│ └── migrations # db migrations
├── entity # application entities (including their specific enum types)
├── mocks # interface mocks for unit testing
├── pkg # 3rd party lib wrappers
│ ├── config # handle config via env vars and .env files
│ ├── data # CRUD repository implementation
│ ├── db # handle Postgres connections
│ ├── httpserver # http server with default config and behavior
│ ├── migrate # help with db migrations during integration tests
│ ├── stripemock # set Stripe's API SDK Backend to use stripe-mock
│ ├── testcontainer # create db containers used in integration tests
│ └── testfixtures # load db fixtures needed for integration tests
└── usecase # application use cases
Requirements:
- Make a copy of the
app.env.sample
file and name itapp.env
, then use it to set the env vars as needed - A working instance of Postgres (for convenience, there's a
docker-compose.yaml
included to help with this step) - Stripe's stripe-mock (also provided with the
docker-compose.yaml
) - Docker is also needed for running the integration tests, since they rely on testcontainers
- This project makes use of
testfixtures
CLI to facilitate loading db fixtures, please take a look at how to install it here - Instead of a
Makefile
, this project usesTaskfile
, please check its installation procedure here - Finally, run the following commands:
# download and install both the project and dev dependencies
task deps
# start the dependencies (postgres and stripe-mock)
docker-compose up -d
# run db migrations, remember to export the $DSN env var before running it
DSN=postgresql://postgres:postgres@localhost:5432/rides?sslmode=disable task db:migrate-up
# load db fixtures, remember to export the $DSN env var before running it
DSN=postgresql://postgres:postgres@localhost:5432/rides?sslmode=disable task db:fixtures
# start the API server
task api
Once the server is up running, send requests to it:
curl -i -w '\n' -X POST http://localhost:8080/ \
-H 'content-type: application/json' \
-H 'idempotency-key: key123' \
-H 'authorization: local.user@email.com' \
-d '{ "origin_lat": 0.0, "origin_lon": 0.0, "target_lat": 0.0, "target_lon": 0.0 }'
Use the provided Taskfile
to help you with dev & testing tasks:
task: Available tasks for this project:
* api: Run API server locally
* db:fixtures: Load DB fixtures (expects $DSN env var to be set)
* db:migrate-drop: Drop local DB (expects $DSN env var to be set)
* db:migrate-up: Up DB migrations (expects $DSN env var to be set)
* deps: Install dependencies
* format: Format source code
* lint: Run linter
* test:integration: Run integration tests
* test:mock: Generate interfaces mocks
* test:unit: Run unit tests
If you're using VS Code, make sure to check out this article with some tips on how to setup your IDE: Setting up VS Code for Golang.
And here's a sample settings.json
file with some suggestions:
{
"go.testFlags": [
"-failfast",
"-v"
],
"go.toolsManagement.autoUpdate": true,
"go.useLanguageServer": true,
"go.lintFlags": [
"--build-tags=integration,unit"
],
"go.lintOnSave": "package",
"go.lintTool": "golangci-lint",
"gopls": {
"build.buildFlags": [
"-tags=integration,unit"
],
"ui.semanticTokens": true
}
}