crag-vt

module
v0.0.0-...-a40a463 Latest Latest
Warning

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

Go to latest
Published: Apr 8, 2022 License: MIT

README

Go RESTful API Starter Kit (Boilerplate)

GoDoc Build Status Code Coverage Go Report

Getting Started

Install Go (requires Go 1.13 or above)

Install Docker (requires Docker 17.05 or higher)

# download
git clone https://github.com/jnr122/crag-vt.git

cd crag-vt

# start a PostgreSQL database server in a Docker container
make db-start

# seed the database with data
python3 service_bot.py

# run the RESTful API server
make run

# or run the API server with live reloading, which is useful during development
# requires fswatch (https://github.com/emcrisostomo/fswatch)
make run-live

At this time, you have a RESTful API server running at http://127.0.0.1:8080.

Try the URL http://localhost:8080/healthcheck in a browser, and you should see something like "OK v1.0.0" displayed.

If you have cURL or some API client tools (e.g. Postman), you may try the following more complex scenarios:

# authenticate the user via: POST /v1/login
curl -X POST -H "Content-Type: application/json" -d '{"username": "demo", "password": "pass"}' http://localhost:8080/v1/login
# should return a JWT token like: {"token":"...JWT token here..."}

# with the above JWT token, access the album resources, such as: GET /v1/albums
curl -X GET -H "Authorization: Bearer ...JWT token here..." http://localhost:8080/v1/albums
# should return a list of album records in the JSON format

Project Layout

.
├── cmd                  main applications of the project
│   └── server           the API server application
├── config               configuration files for different environments
├── internal             private application and library code
│   ├── album            album-related features
│   ├── auth             authentication feature
│   ├── config           configuration library
│   ├── entity           entity definitions and domain logic
│   ├── errors           error types and handling
│   ├── healthcheck      healthcheck feature
│   └── test             helpers for testing purpose
├── migrations           database migrations
├── pkg                  public library code
│   ├── accesslog        access log middleware
│   ├── graceful         graceful shutdown of HTTP server
│   ├── log              structured and context-aware logger
│   └── pagination       paginated list
└── testdata             test data scripts

Contributing

# use main as source of truth
git checkout main

# get the latest
git pull

# make feature branch from updated main
git checkout -b "<branch-name>"

# after developing
git push

From the UI make a PR for review

Implementing a New Feature

Implementing a new feature typically involves the following steps:

  1. Develop the service that implements the business logic supporting the feature. Please refer to internal/album/service.go as an example.
  2. Develop the RESTful API exposing the service about the feature. Please refer to internal/album/api.go as an example.
  3. Develop the repository that persists the data entities needed by the service. Please refer to internal/album/repository.go as an example.
  4. Wire up the above components together by injecting their dependencies in the main function. Please refer to the album.RegisterHandlers() call in cmd/server/main.go.
Working with DB Transactions

It is the responsibility of the service layer to determine whether DB operations should be enclosed in a transaction. The DB operations implemented by the repository layer should work both with and without a transaction.

You can use dbcontext.DB.Transactional() in a service method to enclose multiple repository method calls in a transaction. For example,

func serviceMethod(ctx context.Context, repo Repository, transactional dbcontext.TransactionFunc) error {
    return transactional(ctx, func(ctx context.Context) error {
        repo.method1(...)
        repo.method2(...)
        return nil
    })
}

If needed, you can also enclose method calls of different repositories in a single transaction. The return value of the function in transactional above determines if the transaction should be committed or rolled back.

You can also use dbcontext.DB.TransactionHandler() as a middleware to enclose a whole API handler in a transaction. This is especially useful if an API handler needs to put method calls of multiple services in a transaction.

Updating Database Schema

database migration

# Execute new migrations made by you or other team members.
# Usually you should run this command each time after you pull new code from the code repo. 
make migrate

# Create a new database migration.
# In the generated `migrations/*.up.sql` file, write the SQL statements that implement the schema changes.
# In the `*.down.sql` file, write the SQL statements that revert the schema changes.
make migrate-new

# Revert the last database migration.
# This is often used when a migration has some issues and needs to be reverted.
make migrate-down

# Clean up the database and rerun the migrations from the very beginning.
# Note that this command will first erase all data and tables in the database, and then
# run all migrations. 
make migrate-reset
Managing Configurations

The application configuration is represented in internal/config/config.go. When the application starts, it loads the configuration from a configuration file as well as environment variables. The path to the configuration file is specified via the -config command line argument which defaults to ./config/local.yml. Configurations specified in environment variables should be named with the APP_ prefix and in upper case. When a configuration is specified in both a configuration file and an environment variable, the latter takes precedence.

The config directory contains the configuration files named after different environments. For example, config/local.yml corresponds to the local development environment and is used when running the application via make run.

Do not keep secrets in the configuration files. Provide them via environment variables instead. For example, you should provide Config.DSN using the APP_DSN environment variable. Secrets can be populated from a secret storage (e.g. HashiCorp Vault) into environment variables in a bootstrap script (e.g. cmd/server/entryscript.sh).

Deployment

The application can be run as a docker container. You can use make build-docker to build the application into a docker image. The docker container starts with the cmd/server/entryscript.sh script which reads the APP_ENV environment variable to determine which configuration file to use. For example, if APP_ENV is qa, the application will be started with the config/qa.yml configuration file.

You can also run make build to build an executable binary named server. Then start the API server using the following command,

./server -config=./config/prod.yml

References: SOLID principles, clean architecture, Standard Go Project Layout, clean architecture.

Packages Used

Directories

Path Synopsis
cmd
internal
pkg
accesslog
Package accesslog provides a middleware that records every RESTful API call in a log message.
Package accesslog provides a middleware that records every RESTful API call in a log message.
dbcontext
Package dbcontext provides DB transaction support for transactions tha span method calls of multiple repositories and services.
Package dbcontext provides DB transaction support for transactions tha span method calls of multiple repositories and services.
log
Package log provides context-aware and structured logging capabilities.
Package log provides context-aware and structured logging capabilities.
pagination
Package pagination provides support for pagination requests and responses.
Package pagination provides support for pagination requests and responses.

Jump to

Keyboard shortcuts

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