rocketride

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 22, 2022 License: MIT Imports: 7 Imported by: 0

README

go-rocket-ride

Lint and Tests Go Report Card

Read more about this project's motivations and reasonings:

Description

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.

Architecture & code organization

.
├── api               # application ports
│   └── http          # HTTP transport layer
├── cmd               # application commands
│   └── api           # 'main.go' for running the API server
├── datastore         # app data stores (e.g., PostgreSQL, MySQL, etc.)
│   ├── bun           # Postgres data access based on Bun ORM 
│   └── sqlc          # Postgres data access based on Sqlc
├── db                # database related files
│   ├── fixtures      # fixtures used in integration tests and local development
│   ├── migrations    # db migrations
│   └── queries       # Sqlc db queries
├── entity            # application entities (including their specific enum types)
├── mocks             # interface mocks for unit testing
├── pkg               # 3rd party lib wrappers
│   ├── 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
│   └── tools         # keep track of dev deps
├── usecase           # application use cases
├── config.go         # handle config via env vars and .env files
└── rocketride.go     # interface definitions

Setup

Requirements:

  1. Make a copy of the app.env.sample file and name it app.env, then use it to set the env vars as needed
  2. A working instance of Postgres (for convenience, there's a docker-compose.yaml included to help with this step)
  3. Stripe's stripe-mock (also provided with the docker-compose.yaml)
  4. Docker is also needed for running the integration tests, since they rely on testcontainers
  5. This project makes use of testfixtures CLI to facilitate loading db fixtures, please take a look at how to install it here
  6. Finally, run the following commands:
# download and install both the project and dev dependencies
make 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 make migrate

# load db fixtures, remember to export the $DSN env var before running it
DSN=postgresql://postgres:postgres@localhost:5432/rides?sslmode=disable make fixtures

# start the API server
make server

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 }'

Development & testing

Use the provided Makefile to help you with dev & testing tasks:

help         # Show help
deps         # Install dependencies
migrate      # Run db migrations (expects $DSN env var, i.e., 'DSN=<postgres dsn> make migrate')
fixtures     # Load db fixtures (expects $DSN env var, i.e., 'DSN=<postgres dsn> make fixtures')
lint         # Run linter
format       # Format source code
sqlc         # Generate sqlc files
mock         # Generate interfaces mocks
integration  # Run integration tests
unit         # Run unit tests
server       # Run API server locally

IDE

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
  }
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Config

type Config struct {
	IdemKeyTimeout  int    `mapstructure:"IDEM_KEY_TIMEOUT" validate:"required"`
	DBSource        string `mapstructure:"DB_SOURCE"  validate:"required"`
	ServerAddress   string `mapstructure:"SERVER_ADDRESS"  validate:"required"`
	StripeKey       string `mapstructure:"STRIPE_KEY"  validate:"required"`
	DatastoreAccess string `mapstructure:"DATASTORE_ACCESS" validate:"required,oneof=bun sqlc"`
}

Config stores all configuration of the application. The values are read by viper from a config file or environment variable.

func LoadConfig

func LoadConfig(cfgPath string) (config Config, err error)

LoadConfig reads configuration from file or environment variables.

type Datastore

type Datastore interface {
	Atomic(ctx context.Context, fn func(ds Datastore) error) error
	CreateAuditRecord(ctx context.Context, ar *entity.AuditRecord) (*entity.AuditRecord, error)
	CreateIdempotencyKey(ctx context.Context, ik *entity.IdempotencyKey) (*entity.IdempotencyKey, error)
	CreateRide(ctx context.Context, rd *entity.Ride) (*entity.Ride, error)
	CreateStagedJob(ctx context.Context, sj *entity.StagedJob) (*entity.StagedJob, error)
	GetIdempotencyKey(ctx context.Context, key string, userID int64) (*entity.IdempotencyKey, error)
	GetRideByIdempotencyKeyID(ctx context.Context, keyID int64) (*entity.Ride, error)
	GetUserByEmail(ctx context.Context, email string) (*entity.User, error)
	UpdateIdempotencyKey(ctx context.Context, ik *entity.IdempotencyKey) (*entity.IdempotencyKey, error)
	UpdateRide(ctx context.Context, rd *entity.Ride) (*entity.Ride, error)
}

type RideUseCase

type RideUseCase interface {
	Create(ctx context.Context, ik *entity.IdempotencyKey, rd *entity.Ride) (*entity.IdempotencyKey, error)
}

Directories

Path Synopsis
api
cmd
api
datastore
bun
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL