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:
internal
: private codes for the project
pkg
: shareable codes e.g. helper/utitily 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
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