Skip to content
This repository has been archived by the owner on May 21, 2024. It is now read-only.

Pragmatic Boilerplate for Golang RESTful Server Implementation

License

Notifications You must be signed in to change notification settings

typical-go/typical-rest-server

Repository files navigation

Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public. Go-Workflow Go Report Card codebeat badge codecov

typical-rest-server

The project status is WIP (Work in progress) which means the author continously evaluate and improve the project.

Pragmatic Golang RESTful Server Implementation. The project using typical-go as its build-tool.

  • Application
    • Go-Standards Project Layout
    • Environment Variable Configuration
    • Health-Check and Debug API
    • Graceful Shutdown
  • Layered architecture
    • SOLID Principle
    • Dependency Injection (using @ctor annotation)
    • ORMHate
    • Database Transaction
  • HTTP Server
    • Echo framework
    • Server Side Caching
      • Cache but revalidate (Header Cache-Control: no-cache)
      • Set Expiration Time (Header Cache-Control: max-age=120)
      • Return 304 if not modified (Header If-Modified-Since: Sat, 31 Oct 2020 10:28:02 GMT)
    • Request ID in logger (Header X-Request-Id: xxx)
  • RESTful
    • Create Resource (POST verb)
    • Update Resource (PUT verb)
    • Partially Update Resource (PATCH verb)
    • Find Resource (GET verb)
      • Offset Pagination (Query param ?limit=100&offset=0)
      • Sorting (Query param ?sort=-title,created_at)
      • Total count (Header X-Total-Count: 99)
    • Check resource (HEAD verb)
    • Delete resource (DELETE verb, idempotent)
  • Testing
    • Table Driven Test
    • Mocking (using @mock annotation)
  • Others
    • Database migration and seed tool
    • Generate code, .env file and USAGE.md according the configuration (using @envconfig annotation)
    • Generate code for repository layer
    • Releaser

Run/Test Project

Copy .env.sample for working configuration

cp .env.sample .env    # copy the working .env

Setup the local environment

./typicalw docker up -d  # equivalent with `docker-compose up -d`

# wait few seconds to make sure docker ready
./typicalw setup         # setup dependency e.g. mysql and postgres

Generate code by annotation (if any change required)

./typicalw generate

Build + Run application:

./typicalw run         # run the application

Test application:

./typicalw test        # run test

Project descriptor at tools/typical-build/typical-build.go

var descriptor = typgo.Descriptor{
  ProjectName:    "typical-rest-server",
  ProjectVersion: "0.9.7",

  Tasks: []typgo.Tasker{
    // tasks ...
  }
}

Project Layout

Typical-Rest encourage standard go project layout

Source codes:

Others directory:

  • tools Supporting tool for the project e.g. Build Tool
  • api Any related scripts for API e.g. api-model script (swagger, raml, etc) or client script
  • database Any related scripts for Databases e.g. migration scripts and seed data

Dependency Injection

Typical-Rest encourage dependency injection using uber-dig and annotations (@ctor).

// NewConn ...
// @ctor
func NewConn() *sql.DB{
}

Add import side-effect to make it work

import (
  _ "github.com/typical-go/typical-rest-server/internal/generated/ctor"
)

Application Config

Typical-Rest encourage application config with environment variables using envconfig and annotation (@envconfig).

type (
  // AppCfg application configuration
  // @envconfig (prefix:"APP")
  AppCfg struct {
    Address string `envconfig:"ADDRESS" default:":8089" required:"true"`
    Debug   bool   `envconfig:"DEBUG" default:"true"`
  }
)

Generate usage documentation (USAGE.md) and .env file

// in typical-build

&typcfg.EnvconfigAnnot{
  DotEnv:   ".env",     // generate .env file
  UsageDoc: "USAGE.md", // generate USAGE.md
}

Add import side-effect to make it work

import(
  _ "github.com/typical-go/typical-rest-server/internal/generated/envcfg"
)

Mocking

Typical-Rest encourage mocking using gomock and annotation(@mock).

type(
  // Reader responsible to read
  // @mock
  Reader interface{
    Read() error
  }
)

Mock class will be generated in *_mock package

Database Transaction

In Repository layer

func (r *RepoImpl) Delete(ctx context.Context) (int64, error) {
  txn, err := dbtxn.Use(ctx, r.DB) // use transaction if begin detected
  if err != nil {                  // create transaction error
      return -1, err
  }
  db := txn                     // transaction object or database connection
  // result, err := ...
  if err != nil {
      txn.AppendError(err)            // append error to plan for rollback
      return -1, err
  }
  // ...
}

In Service layer

func (s *SvcImpl) SomeOperation(ctx context.Context) (err error){
  // begin the transaction
  txn := dbtxn.Begin(&ctx)

  // commit/rollback in end function
  defer func(){ err = txn.Commit() }()
  // ...
}

Server-Side Cache

Use echo middleware to handling cache

cacheStore := &cachekit.Store{
  Client:        redis.NewClient(&redis.Options{Addr: "localhost:6379"}),
  DefaultMaxAge: 30 * time.Second,
  PrefixKey:     "cache_",
}

e := echo.New()
e.GET("/", handle, cacheStore.Middleware)

References

Golang:

RESTful API:

License

This project is licensed under the MIT License - see the LICENSE.md file for details