README

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 (using @entity annotation)
    • 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   # equivalent with `docker-compose up -d`
./typicalw reset       # reset infra: drop, create and migrate postgres database 

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
  • databases Any related scripts for Databases e.g. migration scripts and seed data

Layered Architecture

Typical-Rest encourage layered architecture as most adoptable architectural pattern

  • Presentation Layer at internal/app/controller
    • Parsing the request
    • Sending response
  • Logic Layer at internal/app/service
    • Intermediary between controller (end-point) and repository (data)
    • Logic of controller
    • Data Validation
  • Data Access Layer at internal/app/entity (database) or internal/app/model (business)
    • No logic except operation to database
    • Repository pattern for Database entity or Business Model

Dependency Injection

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

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

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.EnvconfigAnnotation{
  DotEnv:   ".env",     // generate .env file
  UsageDoc: "USAGE.md", // generate USAGE.md
}

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.SetError(err)            // set the error and 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

Expand ▾ Collapse ▴

Directories

Path Synopsis
cmd
pkg
tools
internal
app
app/service_mock
Package service_mock is a generated GoMock package.
Package service_mock is a generated GoMock package.
generated/entity/app/repo_mock
Package repo_mock is a generated GoMock package.
Package repo_mock is a generated GoMock package.