<p>All business codes are inside <code>internal</code>.
Each business has a different directory <code>business</code>.</p>
<p>Inside each business, there are 2 handlers: <code>http</code>, <code>grpc</code>:</p>
<ul>
<li>
<code>http</code> is for public APIs (Android, iOS, ... are clients).</li>
<li>
<code>grpc</code> is for internal APIs (other services are clients).</li>
<li>
<code>consumer</code> is for consuming messages from queue (Kafka, RabbitMQ, ...).</li>
</ul>
<p>For each handler, there are usually 3 layers: <code>handler</code>, <code>service</code>, <code>repository</code>:</p>
<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>
<li>
<code>service</code> is where we write business/logic codes, and only business/logic codes is written here.</li>
<li>
<code>repository</code> is where we write codes which interacts with database/cache like MySQL, Redis, ...</li>
<li>
<code>models</code> is where we put all request, response, data models.</li>
</ul>
<p>Location:</p>
<ul>
<li>
<code>handler</code> must exist inside <code>grpc</code>, <code>http</code>, <code>consumer</code>.</li>
<li>
<code>service</code>, <code>models</code> can exist directly inside of <code>business</code> if both <code>grpc</code>, <code>http</code>, <code>consumer</code> has same business/logic.</li>
<li>
<code>repository</code> should be placed directly inside of <code>business</code>.</li>
</ul>
<h2><aid="user-content-do-not-repeat"class="anchor"aria-hidden="true"href="#do-not-repeat"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Do not repeat!</h2>
<p>If we have too many services, some of the logic will be overlapped.</p>
<p>For example, service A and service B both need to make POST call API to service C.
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>
<p>Another bad practice is adapter service.
No need to write a new service if what we need is just common pkg libs.</p>
<h2><aid="user-content-taste-on-style-guide"class="anchor"aria-hidden="true"href="#taste-on-style-guide"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Taste on style guide</h2>
<h3><aid="user-content-stop-using-global-var"class="anchor"aria-hidden="true"href="#stop-using-global-var"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Stop using global var</h3>
<p>If I see someone using global var, I swear I will shoot them twice in the face.</p>
<p>Why?</p>
<ul>
<li>Can not write unit test.</li>
<li>Is not thread safe.</li>
</ul>
<h3><aid="user-content-use-functional-options-but-dont-overuse-it"class="anchor"aria-hidden="true"href="#use-functional-options-but-dont-overuse-it"><spanaria-hidden="true"class="octicon octicon-link"></span></a>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>
<p>In above example, I construct <code>s</code> with <code>WithA</code> and <code>WithB</code> option.
No need to pass direct field inside <code>s</code>.</p>
<h3><aid="user-content-use-errgroup-as-much-as-possible"class="anchor"aria-hidden="true"href="#use-errgroup-as-much-as-possible"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Use <ahref="https://pkg.go.dev/golang.org/x/sync/errgroup"rel="nofollow">errgroup</a> as much as possible</h3>
<p>If business logic involves calling too many APIs, but they are not depend on each other.
We can fire them parallel :)</p>
<p>Personally, I prefer <code>errgroup</code> to <code>WaitGroup</code> (<ahref="https://pkg.go.dev/sync#WaitGroup"rel="nofollow">https://pkg.go.dev/sync#WaitGroup</a>).
<h3><aid="user-content-use-semaphore-when-need-to-implement-workerpool"class="anchor"aria-hidden="true"href="#use-semaphore-when-need-to-implement-workerpool"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Use <ahref="https://pkg.go.dev/golang.org/x/sync/semaphore"rel="nofollow">semaphore</a> when need to implement WorkerPool</h3>
<p>Please don't use external libs for WorkerPool, I don't want to deal with dependency hell.</p>
<h3><aid="user-content-no-need-vendor"class="anchor"aria-hidden="true"href="#no-need-vendor"><spanaria-hidden="true"class="octicon octicon-link"></span></a>No need <code>vendor</code>
</h3>
<p>Only need if you need something from <code>vendor</code>, to generate mock or something else.</p>
<h3><aid="user-content-use-buildgo-to-include-build-tools-in-gomod"class="anchor"aria-hidden="true"href="#use-buildgo-to-include-build-tools-in-gomod"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Use <code>build.go</code> to include build tools in go.mod</h3>
go install github.com/golang/protobuf/protoc-gen-go</pre></div>
<p>We always get the version of build tools in <code>go.mod</code> each time we install it.
Future contributors will not cry anymore.</p>
<h3><aid="user-content-dont-use-cli-libs-spf13cobra-urfavecli-just-for-go-service"class="anchor"aria-hidden="true"href="#dont-use-cli-libs-spf13cobra-urfavecli-just-for-go-service"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Don't use cli libs (<ahref="https://github.com/spf13/cobra">spf13/cobra</a>, <ahref="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>
<p>In my case, service starts with only config, and config should be read from file or environment like <ahref="https://12factor.net/"rel="nofollow">The Twelve Factors</a> guide.</p>
<h3><aid="user-content-dont-use-grpc-ecosystemgrpc-gateway"class="anchor"aria-hidden="true"href="#dont-use-grpc-ecosystemgrpc-gateway"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Don't use <ahref="https://github.com/grpc-ecosystem/grpc-gateway">grpc-ecosystem/grpc-gateway</a>
</h3>
<p>Just don't.</p>
<p>Use <ahref="https://github.com/protocolbuffers/protobuf-go">protocolbuffers/protobuf-go</a>, <ahref="https://github.com/grpc/grpc-go">grpc/grpc-go</a> for gRPC.</p>
<p>Write 1 for both gRPC, REST sounds good, but in the end, it is not worth it.</p>
<h3><aid="user-content-dont-use-uberprototool-use-bufbuildbuf"class="anchor"aria-hidden="true"href="#dont-use-uberprototool-use-bufbuildbuf"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Don't use <ahref="https://github.com/uber/prototool">uber/prototool</a>, use <ahref="https://github.com/bufbuild/buf">bufbuild/buf</a>
</h3>
<p>prototool is deprecated, and buf can generate, lint, format as good as prototool.</p>
<h3><aid="user-content-use-gin-gonicgin-for-rest"class="anchor"aria-hidden="true"href="#use-gin-gonicgin-for-rest"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Use <ahref="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.</p>
<h3><aid="user-content-if-you-want-log-just-use-uber-gozap"class="anchor"aria-hidden="true"href="#if-you-want-log-just-use-uber-gozap"><spanaria-hidden="true"class="octicon octicon-link"></span></a>If you want log, just use <ahref="https://github.com/uber-go/zap">uber-go/zap</a>
</h3>
<p>It is fast!</p>
<ul>
<li>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>
<li>Use <code>MarshalLogObject</code> when we need to hide some field of object when log (field is long or has sensitive value)</li>
<li>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>
<li>If doubt, use <code>zap.Any</code>.</li>
<li>Use <code>contextID</code> or <code>traceID</code> in every log lines for easily debug.</li>
</ul>
<h3><aid="user-content-to-read-config-use-spf13viper"class="anchor"aria-hidden="true"href="#to-read-config-use-spf13viper"><spanaria-hidden="true"class="octicon octicon-link"></span></a>To read config, use <ahref="https://github.com/spf13/viper">spf13/viper</a>
</h3>
<p>Only init config in main or cmd layer.
Do not use <code>viper.Get...</code> in business layer or inside business layer.</p>
<p>Why?</p>
<ul>
<li>Hard to mock and test</li>
<li>Put all config in single place for easily tracking</li>
</ul>
<p>Also, be careful if config value is empty.
You should decide to continue or stop the service if there is no config.</p>
<h3><aid="user-content-dont-overuse-orm-libs-no-need-to-handle-another-layer-above-sql"class="anchor"aria-hidden="true"href="#dont-overuse-orm-libs-no-need-to-handle-another-layer-above-sql"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Don't overuse ORM libs, no need to handle another layer above SQL.</h3>
I hear that <ahref="https://github.com/go-gorm/gorm">go-gorm/gorm</a>, <ahref="https://github.com/ent/ent">ent/ent</a> is good.</p>
<h3><aid="user-content-if-you-want-test-just-use-stretchrtestify"class="anchor"aria-hidden="true"href="#if-you-want-test-just-use-stretchrtestify"><spanaria-hidden="true"class="octicon octicon-link"></span></a>If you want test, just use <ahref="https://github.com/stretchr/testify">stretchr/testify</a>.</h3>
<p>It is easy to write a suite test, thanks to testify.
<h3><aid="user-content-if-need-to-mock-choose-matryermoq-or-golangmock"class="anchor"aria-hidden="true"href="#if-need-to-mock-choose-matryermoq-or-golangmock"><spanaria-hidden="true"class="octicon octicon-link"></span></a>If need to mock, choose <ahref="https://github.com/matryer/moq">matryer/moq</a> or <ahref="https://github.com/golang/mock">golang/mock</a>
</h3>
<p>The first is easy to use but not powerful as the later.
If you want to make sure mock func is called with correct times, use the later.</p>
<p>Example with <code>matryer/moq</code>:</p>
<divclass="highlight highlight-source-go"><pre><spanclass="pl-c">// Only gen mock if source code file is newer than mock file</span>
<h3><aid="user-content-be-careful-with-spf13cast"class="anchor"aria-hidden="true"href="#be-careful-with-spf13cast"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Be careful with <ahref="https://github.com/spf13/cast">spf13/cast</a>
<h3><aid="user-content-use-stringer-if-you-want-your-type-enum-can-be-print-as-string"class="anchor"aria-hidden="true"href="#use-stringer-if-you-want-your-type-enum-can-be-print-as-string"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Use <ahref="https://pkg.go.dev/golang.org/x/tools/cmd/stringer"rel="nofollow">stringer</a> if you want your type enum can be print as string</h3>
<spanclass="pl-c"><spanclass="pl-c">#</span> Run inside directory which contains Drink</span>
stringer -type=Drink</pre></div>
<h3><aid="user-content-dont-waste-your-time-rewrite-rate-limiter-if-your-use-case-is-simple-use-rate-or-go-redisredis_rate"class="anchor"aria-hidden="true"href="#dont-waste-your-time-rewrite-rate-limiter-if-your-use-case-is-simple-use-rate-or-go-redisredis_rate"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Don't waste your time rewrite rate limiter if your use case is simple, use <ahref="https://pkg.go.dev/golang.org/x/time/rate"rel="nofollow">rate</a> or <ahref="https://github.com/go-redis/redis_rate">go-redis/redis_rate</a>
</h3>
<p>rate if you want rate limiter locally in your single instance of service.
redis_rate if you want rate limiter distributed across all your instances of service.</p>
<h3><aid="user-content-replace-go-fmt-goimports-with-mvdangofumpt"class="anchor"aria-hidden="true"href="#replace-go-fmt-goimports-with-mvdangofumpt"><spanaria-hidden="true"class="octicon octicon-link"></span></a>Replace <code>go fmt</code>, <code>goimports</code> with <ahref="https://github.com/mvdan/gofumpt">mvdan/gofumpt</a>.</h3>
<p><code>gofumpt</code> provides more rules when format Go codes.</p>
<p>If you get <code>fieldalignment</code> error, use <ahref="https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment"rel="nofollow">fieldalignment</a> to fix them.</p>