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
@ctorannotation) - 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)
- Cache but revalidate (Header
- Request ID in logger (Header
X-Request-Id: xxx)
- RESTful
- Create Resource (
POSTverb) - Update Resource (
PUTverb) - Partially Update Resource (
PATCHverb) - Find Resource (
GETverb)- Offset Pagination (Query param
?limit=100&offset=0) - Sorting (Query param
?sort=-title,created_at) - Total count (Header
X-Total-Count: 99)
- Offset Pagination (Query param
- Check resource (
HEADverb) - Delete resource (
DELETEverb, idempotent)
- Create Resource (
- Testing
- Table Driven Test
- Mocking (using
@mockannotation)
- Others
- Database migration and seed tool
- Generate code,
.envfile andUSAGE.mdaccording the configuration (using@envconfigannotation) - Generate code for repository layer
- Releaser
Copy .env.sample for working configuration
cp .env.sample .env # copy the working .envSetup 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 postgresGenerate code by annotation (if any change required)
./typicalw generateBuild + Run application:
./typicalw run # run the applicationTest application:
./typicalw test # run testProject descriptor at tools/typical-build/typical-build.go
var descriptor = typgo.Descriptor{
ProjectName: "typical-rest-server",
ProjectVersion: "0.9.7",
Tasks: []typgo.Tasker{
// tasks ...
}
}Typical-Rest encourage standard go project layout
Source codes:
internal: private codes for the projectinternal/appinternal/app/infra: infrastructure for the project e.g. config and connection objectinternal/app/controller: presentation layerinternal/app/service: logic layerinternal/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 Librarycmd: the main package
Others directory:
toolsSupporting tool for the project e.g. Build ToolapiAny related scripts for API e.g. api-model script (swagger, raml, etc) or client scriptdatabaseAny related scripts for Databases e.g. migration scripts and seed data
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"
)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"
)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
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() }()
// ...
}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)Golang:
RESTful API:
- Best Practices for Designing a Pragmatic RESTful API
- Everything You Need to know About API Pagination
This project is licensed under the MIT License - see the LICENSE.md file for details