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
)
- Cache but revalidate (Header
- 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
)
- Offset Pagination (Query param
- Check resource (
HEAD
verb) - Delete resource (
DELETE
verb, idempotent)
- Create Resource (
- Testing
- Table Driven Test
- Mocking (using
@mock
annotation)
- Others
- Database migration and seed tool
- Generate code,
.env
file andUSAGE.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:
internal
: private codes for the projectinternal/app
internal/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:
tools
Supporting tool for the project e.g. Build Toolapi
Any related scripts for API e.g. api-model script (swagger, raml, etc) or client scriptdatabase
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