feat: update bootstrap go
parent
608c493339
commit
c4bf46c7c5
|
@ -1,26 +1,21 @@
|
||||||
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap" rel=stylesheet><link rel=stylesheet href=styles.css><a href=index>Index</a><h1>Bootstrap Go</h1><p>It is hard to write bootstrap tool to quickly create Go service.<br>So I write this guide instead.<br>This is a quick checklist for me every damn time I need to write a Go service from scratch.<br>Also, this is my personal opinion, so feel free to comment.<h2>Structure</h2><pre><code class=language-txt>main.go
|
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=preconnect href=https://fonts.googleapis.com><link rel=preconnect href=https://fonts.gstatic.com crossorigin><link href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap" rel=stylesheet><link rel=stylesheet href=styles.css><a href=index>Index</a><h1>Bootstrap Go</h1><p>It is hard to write bootstrap tool to quickly create Go service.<br>So I write this guide instead.<br>This is a quick checklist for me every damn time I need to write a Go service from scratch.<br>Also, this is my personal opinion, so feel free to comment.<h2>Structure</h2><pre><code class=language-txt>main.go
|
||||||
internal
|
internal
|
||||||
| business_1
|
| business
|
||||||
| | http
|
| | http
|
||||||
| | | handler.go
|
| | | handler.go
|
||||||
| | | service.go
|
| | | service.go
|
||||||
| | | repository.go
|
|
||||||
| | | models.go
|
| | | models.go
|
||||||
| | grpc
|
| | grpc
|
||||||
| | | handler.go
|
| | | handler.go
|
||||||
|
| | | models.go
|
||||||
|
| | consumer
|
||||||
|
| | | handler.go
|
||||||
| | | service.go
|
| | | service.go
|
||||||
| | | repository.go
|
|
||||||
| | | models.go
|
| | | models.go
|
||||||
| | service.go
|
| | service.go
|
||||||
| | repository.go
|
| | repository.go
|
||||||
| | models.go
|
| | models.go
|
||||||
| business_2
|
</code></pre><p>All business codes are inside <code>internal</code>.<br>Each business has a different directory <code>business</code>.<p>Inside each business, there are 2 handlers: <code>http</code>, <code>grpc</code>:<ul><li><code>http</code> is for public APIs (Android, iOS, ... are clients).<li><code>grpc</code> is for internal APIs (other services are clients).<li><code>consumer</code> is for consuming messages from queue (Kafka, RabbitMQ, ...).</ul><p>For each handler, there are usually 3 layers: <code>handler</code>, <code>service</code>, <code>repository</code>:<ul><li><code>handler</code> interacts directly with gRPC, REST or consumer using specific codes (cookies, ...) In case gRPC, there are frameworks outside handle for us so we can write business/logic codes here too. But remember, gRPC only.<li><code>service</code> is where we write business/logic codes, and only business/logic codes is written here.<li><code>repository</code> is where we write codes which interacts with database/cache like MySQL, Redis, ... And we should place it directly inside of <code>business</code>.<li><code>models</code> is where we put all request, response, data models.</ul><p><code>handler</code> must exist inside <code>grpc</code>, <code>http</code>.<br>But <code>service</code>, <code>models</code> can exist directly inside of <code>business</code> if both <code>grpc</code>, <code>http</code> has same business/logic.<h2>Do not repeat!</h2><p>If we have too many services, some of the logic will be overlapped.<p>For example, service A and service B both need to make POST call API to service C.<br>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.<br>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.<p>Another bad practice is adapter service.<br>No need to write a new service if what we need is just common pkg libs.<h2>Taste on style guide</h2><h3>Use functional options, but don't overuse it!</h3><p>For simple struct with 1 or 2 fields, no need to use functional options.<p><a href=https://go.dev/play/p/0XnOLiHuoz3>Example</a>:<pre><code class=language-go>func main() {
|
||||||
| | grpc
|
|
||||||
| | | handler.go
|
|
||||||
| | | service.go
|
|
||||||
| | | repository.go
|
|
||||||
| | | models.go
|
|
||||||
</code></pre><p>All business codes are inside <code>internal</code>.<br>Each business has a different directory (<code>business_1</code>, <code>business_2</code>).<p>Inside each business, there are 2 handlers: <code>http</code>, <code>grpc</code>:<ul><li><code>http</code> is for public APIs (Android, iOS,... are clients).<li><code>grpc</code> is for internal APIs (other services are clients).</ul><p>Inside each handler, there are usually 3 layers: <code>handler</code>, <code>service</code>, <code>repository</code>:<ul><li><code>handler</code> interacts directly with gRPC or REST using specific codes (cookies,...)<li><code>service</code> is where we write business/logic codes, and only business/logic codes is written here.<li><code>repository</code> is where we write codes which interacts with database/cache like MySQL, Redis, ...</ul><p><code>handler</code> must exist inside <code>grpc</code>, <code>http</code>.<br>But <code>service</code>, <code>repository</code>, <code>models</code> can exist directly inside <code>business</code> if both <code>grpc</code>, <code>http</code> has same business/logic.<h2>Do not repeat!</h2><p>If we have too many services, some of the logic will be overlapped.<p>For example, service A and service B both need to make POST call API to service C.<br>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.<br>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.<p>Another bad practice is adapter service.<br>No need to write a new service if what we need is just common pkg libs.<h2>Taste on style guide</h2><h3>Use functional options, but don't overuse it!</h3><p>For simple struct with 1 or 2 fields, no need to use functional options.<p><a href=https://go.dev/play/p/0XnOLiHuoz3>Example</a>:<pre><code class=language-go>func main() {
|
|
||||||
s := NewS(WithA(1), WithB("b"))
|
s := NewS(WithA(1), WithB("b"))
|
||||||
fmt.Printf("%+v\n", s)
|
fmt.Printf("%+v\n", s)
|
||||||
}
|
}
|
||||||
|
@ -51,4 +46,9 @@ func NewS(opts ...OptionS) *S {
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
</code></pre><p>In above example, I construct <code>s</code> with <code>WithA</code> and <code>WithB</code> option.<br>No need to pass direct field inside <code>s</code>.<h2>External libs</h2><h3>No need <code>vendor</code></h3><p>Only need if you need something from <code>vendor</code>, to generate mock or something else.<h3>Don't use cli libs (<a href=https://github.com/spf13/cobra>spf13/cobra</a>, <a href=https://github.com/urfave/cli>urfave/cli</a>) just for Go service</h3><p>What is the point to pass many params (<code>--abc</code>, <code>--xyz</code>) when what we only need is start service?<p>In my case, service starts with only config, and config should be read from file or environment like <a href=https://12factor.net/>The Twelve Factors</a> guide.<h3>Don't use <a href=https://github.com/grpc-ecosystem/grpc-gateway>grpc-ecosystem/grpc-gateway</a></h3><p>Just don't.<p>Use <a href=https://github.com/protocolbuffers/protobuf-go>protocolbuffers/protobuf-go</a>, <a href=https://github.com/grpc/grpc-go>grpc/grpc-go</a> for gRPC.<p>Write 1 for both gRPC, REST sounds good, but in the end, it is not worth it.<h3>Don't use <a href=https://github.com/uber/prototool>uber/prototool</a>, use <a href=https://github.com/bufbuild/buf>bufbuild/buf</a></h3><p>prototool is deprecated, and buf can generate, lint, format as good as prototool.<h3>Use <a href=https://github.com/gin-gonic/gin>gin-gonic/gin</a> for REST.</h3><p>Don't use <code>gin.Context</code> when pass context from handler layer to service layer, use <code>gin.Context.Request.Context()</code> instead.<h3>If you want log, just use <a href=https://github.com/uber-go/zap>uber-go/zap</a></h3><p>It is fast!<ul><li><p>Don't overuse <code>func (*Logger) With</code>. Because if log line is too long, there is a possibility that we can lost it.<li><p>Use <code>MarshalLogObject</code> when we need to hide some field of object when log (field has long or sensitive value)<li><p>Don't use <code>Panic</code>. Use <code>Fatal</code> for errors when start service to check dependencies. If you really need panic level, use <code>DPanic</code>.<li><p>If doubt, use <code>zap.Any</code>.<li><p>Use <code>contextID</code> or <code>traceID</code> in every log lines for easily debug.</ul><h3>Don't overuse ORM libs, no need to handle another layer above SQL.</h3><p>Each ORM libs has each different syntax.<br>To learn and use those libs correctly is time consuming.<br>So just stick to plain SQL.<br>It is easier to debug when something is wrong.<p>But <code>database/sql</code> has its own limit.<br>For example, it is hard to get primary key after insert/update.<br>So may be you want to use ORM for those cases.<h3>If you want test, just use <a href=https://github.com/stretchr/testify>stretchr/testify</a>.</h3><p>It is easy to write a suite test, thanks to testify.<br>Also, for mocking, there are many options out there.<br>Pick 1 then sleep peacefully.<h3>Replace <code>go fmt</code>, <code>goimports</code> with <a href=https://github.com/mvdan/gofumpt>mvdan/gofumpt</a>.</h3><p><code>gofumpt</code> provides more rules when format Go codes.<h3>Use <a href=https://github.com/golangci/golangci-lint>golangci/golangci-lint</a>.</h3><p>No need to say more.<br>Lint or get the f out!<h2>Thanks</h2><ul><li><a href=https://github.com/uber-go/guide/blob/master/style.md>Uber Go Style Guide</a><li><a href=https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis>Functional options for friendly APIs</a></ul><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
|
</code></pre><p>In above example, I construct <code>s</code> with <code>WithA</code> and <code>WithB</code> option.<br>No need to pass direct field inside <code>s</code>.<h2>External libs</h2><h3>No need <code>vendor</code></h3><p>Only need if you need something from <code>vendor</code>, to generate mock or something else.<h3>Don't use cli libs (<a href=https://github.com/spf13/cobra>spf13/cobra</a>, <a href=https://github.com/urfave/cli>urfave/cli</a>) just for Go service</h3><p>What is the point to pass many params (<code>do-it</code>, <code>--abc</code>, <code>--xyz</code>) when what we only need is start service?<p>In my case, service starts with only config, and config should be read from file or environment like <a href=https://12factor.net/>The Twelve Factors</a> guide.<h3>Don't use <a href=https://github.com/grpc-ecosystem/grpc-gateway>grpc-ecosystem/grpc-gateway</a></h3><p>Just don't.<p>Use <a href=https://github.com/protocolbuffers/protobuf-go>protocolbuffers/protobuf-go</a>, <a href=https://github.com/grpc/grpc-go>grpc/grpc-go</a> for gRPC.<p>Write 1 for both gRPC, REST sounds good, but in the end, it is not worth it.<h3>Don't use <a href=https://github.com/uber/prototool>uber/prototool</a>, use <a href=https://github.com/bufbuild/buf>bufbuild/buf</a></h3><p>prototool is deprecated, and buf can generate, lint, format as good as prototool.<h3>Use <a href=https://github.com/gin-gonic/gin>gin-gonic/gin</a> for REST.</h3><p>Don't use <code>gin.Context</code> when pass context from handler layer to service layer, use <code>gin.Context.Request.Context()</code> instead.<h3>If you want log, just use <a href=https://github.com/uber-go/zap>uber-go/zap</a></h3><p>It is fast!<ul><li><p>Don't overuse <code>func (*Logger) With</code>. Because if log line is too long, there is a possibility that we can lost it.<li><p>Use <code>MarshalLogObject</code> when we need to hide some field of object when log (field is long or has sensitive value)<li><p>Don't use <code>Panic</code>. Use <code>Fatal</code> for errors when start service to check dependencies. If you really need panic level, use <code>DPanic</code>.<li><p>If doubt, use <code>zap.Any</code>.<li><p>Use <code>contextID</code> or <code>traceID</code> in every log lines for easily debug.</ul><h3>Don't overuse ORM libs, no need to handle another layer above SQL.</h3><p>Each ORM libs has each different syntax.<br>To learn and use those libs correctly is time consuming.<br>So just stick to plain SQL.<br>It is easier to debug when something is wrong.<p>But <code>database/sql</code> has its own limit.<br>For example, it is hard to get primary key after insert/update.<br>So may be you want to use ORM for those cases.<h3>If you want test, just use <a href=https://github.com/stretchr/testify>stretchr/testify</a>.</h3><p>It is easy to write a suite test, thanks to testify.<br>Also, for mocking, there are many options out there.<br>Pick 1 then sleep peacefully.<h3>Replace <code>go fmt</code>, <code>goimports</code> with <a href=https://github.com/mvdan/gofumpt>mvdan/gofumpt</a>.</h3><p><code>gofumpt</code> provides more rules when format Go codes.<h3>Use <a href=https://github.com/golangci/golangci-lint>golangci/golangci-lint</a>.</h3><p>No need to say more.<br>Lint or get the f out!<p>If you get <code>fieldalignment</code> error, use <a href=https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment>fieldalignment</a> to fix them.<pre><code class=language-sh># Install
|
||||||
|
go install golang.org/x/tools/go/analysis/passes/fieldalignment@latest
|
||||||
|
|
||||||
|
# Fix
|
||||||
|
fieldalignment -fix ./internal/business/*.go
|
||||||
|
</code></pre><h2>Thanks</h2><ul><li><a href=https://github.com/uber-go/guide/blob/master/style.md>Uber Go Style Guide</a><li><a href=https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis>Functional options for friendly APIs</a></ul><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
|
|
@ -10,44 +10,41 @@ Also, this is my personal opinion, so feel free to comment.
|
||||||
```txt
|
```txt
|
||||||
main.go
|
main.go
|
||||||
internal
|
internal
|
||||||
| business_1
|
| business
|
||||||
| | http
|
| | http
|
||||||
| | | handler.go
|
| | | handler.go
|
||||||
| | | service.go
|
| | | service.go
|
||||||
| | | repository.go
|
|
||||||
| | | models.go
|
| | | models.go
|
||||||
| | grpc
|
| | grpc
|
||||||
| | | handler.go
|
| | | handler.go
|
||||||
|
| | | models.go
|
||||||
|
| | consumer
|
||||||
|
| | | handler.go
|
||||||
| | | service.go
|
| | | service.go
|
||||||
| | | repository.go
|
|
||||||
| | | models.go
|
| | | models.go
|
||||||
| | service.go
|
| | service.go
|
||||||
| | repository.go
|
| | repository.go
|
||||||
| | models.go
|
| | models.go
|
||||||
| business_2
|
|
||||||
| | grpc
|
|
||||||
| | | handler.go
|
|
||||||
| | | service.go
|
|
||||||
| | | repository.go
|
|
||||||
| | | models.go
|
|
||||||
```
|
```
|
||||||
|
|
||||||
All business codes are inside `internal`.
|
All business codes are inside `internal`.
|
||||||
Each business has a different directory (`business_1`, `business_2`).
|
Each business has a different directory `business`.
|
||||||
|
|
||||||
Inside each business, there are 2 handlers: `http`, `grpc`:
|
Inside each business, there are 2 handlers: `http`, `grpc`:
|
||||||
|
|
||||||
- `http` is for public APIs (Android, iOS, ... are clients).
|
- `http` is for public APIs (Android, iOS, ... are clients).
|
||||||
- `grpc` is for internal APIs (other services are clients).
|
- `grpc` is for internal APIs (other services are clients).
|
||||||
|
- `consumer` is for consuming messages from queue (Kafka, RabbitMQ, ...).
|
||||||
|
|
||||||
Inside each handler, there are usually 3 layers: `handler`, `service`, `repository`:
|
For each handler, there are usually 3 layers: `handler`, `service`, `repository`:
|
||||||
|
|
||||||
- `handler` interacts directly with gRPC or REST using specific codes (cookies,...)
|
- `handler` interacts directly with gRPC, REST or consumer using specific codes (cookies, ...) In case gRPC, there are frameworks outside handle for us so we can write business/logic codes here too. But remember, gRPC only.
|
||||||
- `service` is where we write business/logic codes, and only business/logic codes is written here.
|
- `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, ...
|
- `repository` is where we write codes which interacts with database/cache like MySQL, Redis, ... And we should place it directly inside of `business`.
|
||||||
|
- `models` is where we put all request, response, data models.
|
||||||
|
|
||||||
`handler` must exist inside `grpc`, `http`.
|
`handler` must exist inside `grpc`, `http`.
|
||||||
But `service`, `repository`, `models` can exist directly inside `business` if both `grpc`, `http` has same business/logic.
|
But `service`, `models` can exist directly inside of `business` if both `grpc`, `http` has same business/logic.
|
||||||
|
|
||||||
## Do not repeat!
|
## Do not repeat!
|
||||||
|
|
||||||
|
@ -113,7 +110,7 @@ Only need if you need something from `vendor`, to generate mock or something els
|
||||||
|
|
||||||
### Don't use cli libs ([spf13/cobra](https://github.com/spf13/cobra), [urfave/cli](https://github.com/urfave/cli)) just for Go service
|
### 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?
|
What is the point to pass many params (`do-it`, `--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.
|
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.
|
||||||
|
|
||||||
|
@ -139,7 +136,7 @@ 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.
|
- 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 `MarshalLogObject` when we need to hide some field of object when log (field is long or has sensitive value)
|
||||||
|
|
||||||
- Don't use `Panic`. Use `Fatal` for errors when start service to check dependencies. If you really need panic level, use `DPanic`.
|
- Don't use `Panic`. Use `Fatal` for errors when start service to check dependencies. If you really need panic level, use `DPanic`.
|
||||||
|
|
||||||
|
@ -173,6 +170,16 @@ Pick 1 then sleep peacefully.
|
||||||
No need to say more.
|
No need to say more.
|
||||||
Lint or get the f out!
|
Lint or get the f out!
|
||||||
|
|
||||||
|
If you get `fieldalignment` error, use [fieldalignment](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment) to fix them.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Install
|
||||||
|
go install golang.org/x/tools/go/analysis/passes/fieldalignment@latest
|
||||||
|
|
||||||
|
# Fix
|
||||||
|
fieldalignment -fix ./internal/business/*.go
|
||||||
|
```
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
- [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md)
|
- [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md)
|
||||||
|
|
Loading…
Reference in New Issue