diff --git a/posts/2020-07-10-bootstrap-go.md b/posts/2020-07-10-bootstrap-go.md new file mode 100644 index 0000000..34c6dfc --- /dev/null +++ b/posts/2020-07-10-bootstrap-go.md @@ -0,0 +1,113 @@ +# Bootstrap Go + +It is hard to write bootstrap tool to quickly create Go service. +So I write this guide instead. +This is a quick checklist for me every damn time I need to write a Go service from scratch. +Also, this is my personal opinion, so feel free to comment. + +## Structure + +```txt +main.go +internal +| business_1 +| | http +| | | handler.go +| | | service.go +| | | repository.go +| | | models.go +| | grpc +| | | handler.go +| | | service.go +| | | repository.go +| | | models.go +| | service.go +| | repository.go +| | models.go +| business_2 +| | grpc +| | | handler.go +| | | service.go +| | | repository.go +| | | models.go +``` + +All business codes are inside `internal`. +Each business has a different directory (`business_1`, `business_2`). + +Inside each business, there are 2 handlers: `http`, `grpc`: + +- `http` is for public APIs (Android, iOS,... are clients). +- `grpc` is for internal APIs (other services are clients). + +Inside each handler, there are usually 3 layers: `handler`, `service`, `repository`: + +- `handler` interacts directly with gRPC or REST using specific codes (cookies,...) +- `service` is where we write business/logic codes, and only business/logic codes is written here. +- `repository` is where we write codes which interacts with database/cache like MySQL, Redis, ... + +`handler` must exist inside `grpc`, `http`. +But `service`, `repository`, `models` can exist directly inside `business` if both `grpc`, `http` has same business/logic. + +## Do not repeat! + +If we have too many services, some of the logic will be overlapped. + +For example, service A and service B both need to make POST call API to service C. +If service A and service B both have libs to call service C to do that API, we need to move the libs to some common pkg libs. +So in the future, service D which needs to call C will not need to copy libs to handle service C api but only need to import from common pkg libs. + +Another bad practice is adapter service. +No need to write a new service if what we need is just common pkg libs. + +## External libs + +### Don't use cli libs ([spf13/cobra](https://github.com/spf13/cobra), [urfave/cli](https://github.com/urfave/cli)) just for Go service + +What is the point to pass many params (`--abc`, `--xyz`) when what we only need is start service? + +In my case, service starts with only config, and config should be read from file or environment like [The Twelve Factors](https://12factor.net/) guide. + +### Don't use [grpc-ecosystem/grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) + +Just don't. + +Use [protocolbuffers/protobuf-go](https://github.com/protocolbuffers/protobuf-go), [grpc/grpc-go](https://github.com/grpc/grpc-go) for gRPC. + +Write 1 for both gRPC, REST sounds good, but in the end, it is not worth it. + +### Use [gin-gonic/gin](https://github.com/gin-gonic/gin) for REST. + +Don't use `gin.Context` when pass context from handler layer to service layer, use `gin.Context.Request.Context()` instead. + +### Don't use [uber/prototool](https://github.com/uber/prototool), use [bufbuild/buf](https://github.com/bufbuild/buf) + +prototool is deprecated, and buf can generate, lint as good as prototool. + +### If you want log, just use [uber-go/zap](https://github.com/uber-go/zap) + +It is fast! + +- Don't overuse `func (*Logger) With`. + Because if log line is too long, there is a possibility that we can lost it. + +- Use `MarshalLogObject` when we need to hide some field of object when log (field has long or sensitive value) + +- Use `contextID` or `traceID` in every log lines for easily debug. + +### Don't overuse ORM libs, no need to handle another layer above SQL. + +Each ORM libs has each different syntax. +To learn and use those libs correctly is time consuming. +So just stick to plain SQL. +It is easier to debug when something is wrong. + +But `database/sql` has its own limit. +For example, it is hard to get id after insert/update. +So may be you want to use ORM for those cases. + +### If you want test, just use [stretchr/testify](https://github.com/stretchr/testify). + +It is easy to write a suite test, thanks to testify. +Also, for mocking, there are many options out there. +Pick 1 then sleep peacefully.