typical-rest-server
typical-rest-server copied to clipboard
Pragmatic Boilerplate for Golang RESTful Server Implementation
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
- [x] Go-Standards Project Layout
- [x] Environment Variable Configuration
- [x] Health-Check and Debug API
- [x] Graceful Shutdown
- Layered architecture
- [x] SOLID Principle
- [x] Dependency Injection (using
@ctor
annotation) - [x] ORMHate
- [x] Database Transaction
- HTTP Server
- [x] Echo framework
- [x] Server Side Caching
- [x] Cache but revalidate (Header
Cache-Control: no-cache
) - [x] Set Expiration Time (Header
Cache-Control: max-age=120
) - [x] Return 304 if not modified (Header
If-Modified-Since: Sat, 31 Oct 2020 10:28:02 GMT
)
- [x] Cache but revalidate (Header
- [x] Request ID in logger (Header
X-Request-Id: xxx
)
- RESTful
- [x] Create Resource (
POST
verb) - [x] Update Resource (
PUT
verb) - [x] Partially Update Resource (
PATCH
verb) - [x] Find Resource (
GET
verb)- [x] Offset Pagination (Query param
?limit=100&offset=0
) - [x] Sorting (Query param
?sort=-title,created_at
) - [x] Total count (Header
X-Total-Count: 99
)
- [x] Offset Pagination (Query param
- [x] Check resource (
HEAD
verb) - [x] Delete resource (
DELETE
verb, idempotent)
- [x] Create Resource (
- Testing
- [x] Table Driven Test
- [x] Mocking (using
@mock
annotation)
- Others
- [x] Database migration and seed tool
- [x] Generate code,
.env
file andUSAGE.md
according the configuration (using@envconfig
annotation) - [ ] Generate code for repository layer
- [x] 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:
-
internal
: private codes for the project-
internal/app
-
internal/app/infra
: infrastructure for the project e.g. config and connection object -
internal/app/controller
: presentation layer -
internal/app/service
: logic layer -
internal/app/repo
: data-access layer for database repo or domain model
-
-
internal/generated
: code generated e.g. typical, grpc, xsd, etc.
-
-
pkg
: shareable codes e.g. helper/utility Library -
cmd
: the main package
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:
- Best Practices for Designing a Pragmatic RESTful API
- Everything You Need to know About API Pagination
License
This project is licensed under the MIT License - see the LICENSE.md file for details