feat: generate using github markdown

Use github css
main
sudo pacman -Syu 2022-12-26 02:13:58 +07:00
parent e60e6852ad
commit 4e70059c4f
18 changed files with 1460 additions and 505 deletions

View File

@ -2,16 +2,15 @@
Write markdown, convert to html, then publish using Github Pages.
First add GitHub access token in `.github_access_token`.
Steps:
```sh
# Write new post in posts/
- Write new post in `posts`
# Update index in posts/index.md
- Update index in `posts/index.md`
# Generate HTML
make
```
- Generate HTML by running `make`
## Thanks
@ -26,3 +25,5 @@ make
- https://github.com/be5invis/Iosevka
- https://github.com/ntk148v/iosevkawebfont
- https://gist.github.com/JoeyBurzynski/617fb6201335779f8424ad9528b72c41
- https://github.com/sindresorhus/github-markdown-css
- https://github.com/google/go-github

View File

@ -1,36 +1,114 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-backup-my-way class=anchor aria-hidden=true href=#backup-my-way><span aria-hidden=true class="octicon octicon-link"></span></a>Backup my way</h1><p>First thing first, I want to list my own devices, which I have through the years:<ul><li><del>Laptop Samsung NP300E4Z-S06VN (Old laptop which I give to my mom)</del><li><del><a href=https://www.dell.com/support/home/en-vn/product-support/product/inspiron-15-3567-laptop/drivers rel=nofollow>Laptop Dell Inspiron 15 3567</a> (My mom bought it for me when I go to college, I give it to my mom afterward)</del><li><del><a href=https://www.acer.com/ac/en/US/content/support-product/8841 rel=nofollow>Laptop Acer Nitro AN515-45</a> (Gaming laptop which I buy for gaming, I give it to my sister)</del><li>MacBook Pro M1 2020 (My company laptop)<li><del>Phone <a href=https://forum.xda-developers.com/c/lg-g3.3147/ rel=nofollow>LG G3</a> (Bought long time ago, now is a brick)</del><li><del>Phone <a href=https://forum.xda-developers.com/c/xiaomi-redmi-6a.7881/ rel=nofollow>Xiaomi Redmi 6A</a> (I give it to my sister too)</del><li>Phone <a href=https://forum.xda-developers.com/c/xiaomi-poco-x3-nfc.11523/ rel=nofollow>Xiaomi Poco X3 NFC</a> (Primary phone which I use daily)</ul><p>App/Service I use daily:<ul><li><a href=https://bitwarden.com/ rel=nofollow>Bitwarden</a><li><a href=https://getaegis.app/ rel=nofollow>Aegis Authenticator</a><li><a href=https://rclone.org/ rel=nofollow>Rclone</a><li><a href=https://restic.net/ rel=nofollow>restic</a><li><a href=https://tailscale.com/ rel=nofollow>Tailscale</a><li>GitHub / GitLab<li>Google Keep / Notion<li>Google Drive (I use 200GB plan)</ul><p>The purpose is that I want my data to be safe, secure, and can be easily recovered if I lost some devices;
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-backup-my-way" class="anchor" aria-hidden="true" href="#backup-my-way"><span aria-hidden="true" class="octicon octicon-link"></span></a>Backup my way</h1>
<p>First thing first, I want to list my own devices, which I have through the years:</p>
<ul>
<li><del>Laptop Samsung NP300E4Z-S06VN (Old laptop which I give to my mom)</del></li>
<li><del><a href="https://www.dell.com/support/home/en-vn/product-support/product/inspiron-15-3567-laptop/drivers" rel="nofollow">Laptop Dell Inspiron 15 3567</a> (My mom bought it for me when I go to college, I give it to my mom afterward)</del></li>
<li><del><a href="https://www.acer.com/ac/en/US/content/support-product/8841" rel="nofollow">Laptop Acer Nitro AN515-45</a> (Gaming laptop which I buy for gaming, I give it to my sister)</del></li>
<li>MacBook Pro M1 2020 (My company laptop)</li>
<li><del>Phone <a href="https://forum.xda-developers.com/c/lg-g3.3147/" rel="nofollow">LG G3</a> (Bought long time ago, now is a brick)</del></li>
<li><del>Phone <a href="https://forum.xda-developers.com/c/xiaomi-redmi-6a.7881/" rel="nofollow">Xiaomi Redmi 6A</a> (I give it to my sister too)</del></li>
<li>Phone <a href="https://forum.xda-developers.com/c/xiaomi-poco-x3-nfc.11523/" rel="nofollow">Xiaomi Poco X3 NFC</a> (Primary phone which I use daily)</li>
</ul>
<p>App/Service I use daily:</p>
<ul>
<li><a href="https://bitwarden.com/" rel="nofollow">Bitwarden</a></li>
<li><a href="https://getaegis.app/" rel="nofollow">Aegis Authenticator</a></li>
<li><a href="https://rclone.org/" rel="nofollow">Rclone</a></li>
<li><a href="https://restic.net/" rel="nofollow">restic</a></li>
<li><a href="https://tailscale.com/" rel="nofollow">Tailscale</a></li>
<li>GitHub / GitLab</li>
<li>Google Keep / Notion</li>
<li>Google Drive (I use 200GB plan)</li>
</ul>
<p>The purpose is that I want my data to be safe, secure, and can be easily recovered if I lost some devices;
or in the worst situation, I lost all.
Because you know, it is hard to guess what is waiting for us in the future.<p>There are 2 sections which I want to share, the first is <strong>How to backup</strong>, the second is <strong>Recover strategy</strong>.<h2><a id=user-content-how-to-backup class=anchor aria-hidden=true href=#how-to-backup><span aria-hidden=true class="octicon octicon-link"></span></a>How to backup</h2><p>Before I talk about backup, I want to talk about data.
In specifically, which data should I backup?<p>I use Arch Linux and macOS, primarily work in the terminal so I have too many dotfiles, for example, <code>~/.config/nvim/init.lua</code>.
Each time I reinstall Arch Linux (I like it a lot), I need to reconfigure all the settings, and it is time-consuming.<p>So for the DE and UI settings, I keep it as default as possible, unless it's getting in my way, I leave the default setting there and forget about it.
The others are dotfiles, which I write my own <a href=https://github.com/haunt98/dotfiles>dotfiles tool</a> to backup and reconfigure easily and quickly.
Because you know, it is hard to guess what is waiting for us in the future.</p>
<p>There are 2 sections which I want to share, the first is <strong>How to backup</strong>, the second is <strong>Recover strategy</strong>.</p>
<h2><a id="user-content-how-to-backup" class="anchor" aria-hidden="true" href="#how-to-backup"><span aria-hidden="true" class="octicon octicon-link"></span></a>How to backup</h2>
<p>Before I talk about backup, I want to talk about data.
In specifically, which data should I backup?</p>
<p>I use Arch Linux and macOS, primarily work in the terminal so I have too many dotfiles, for example, <code>~/.config/nvim/init.lua</code>.
Each time I reinstall Arch Linux (I like it a lot), I need to reconfigure all the settings, and it is time-consuming.</p>
<p>So for the DE and UI settings, I keep it as default as possible, unless it's getting in my way, I leave the default setting there and forget about it.
The others are dotfiles, which I write my own <a href="https://github.com/haunt98/dotfiles">dotfiles tool</a> to backup and reconfigure easily and quickly.
Also, I know that installing Arch Linux is not easy, despite I install it too many times (Like thousand times since I was in high school).
Not because it is hard, but as life goes on, the <a href=https://wiki.archlinux.org/title/installation_guide rel=nofollow>official install guide</a> keeps getting new update and covering too many cases for my own personal use, so I write my own <a href=https://github.com/haunt98/til/blob/main/install-archlinux.md>guide</a> to quickly capture what I need to do.
Not because it is hard, but as life goes on, the <a href="https://wiki.archlinux.org/title/installation_guide" rel="nofollow">official install guide</a> keeps getting new update and covering too many cases for my own personal use, so I write my own <a href="https://github.com/haunt98/til/blob/main/install-archlinux.md">guide</a> to quickly capture what I need to do.
I back up all my dotfiles in GitHub and GitLab as I trust them both.
Also as I travel the Internet, I discover <a href=https://codeberg.org/ rel=nofollow>Codeberg</a> and <a href=https://gitea.treehouse.systems/ rel=nofollow>Treehouse</a> and use them as another backup for git repo.<p>So that is my dotfiles, for my regular data, like Wallpaper or Books, Images, I use Google Drive (Actually I pay for it).
Also as I travel the Internet, I discover <a href="https://codeberg.org/" rel="nofollow">Codeberg</a> and <a href="https://gitea.treehouse.systems/" rel="nofollow">Treehouse</a> and use them as another backup for git repo.</p>
<p>So that is my dotfiles, for my regular data, like Wallpaper or Books, Images, I use Google Drive (Actually I pay for it).
But the step: open the webpage, click the upload button and choose files seems boring and time-consuming.
So I use Rclone, it supports Google Drive, One Drive and many providers but I only use Google Drive for now.
The commands are simple:<div class="highlight highlight-source-shell"><pre><span class=pl-c><span class=pl-c>#</span> Sync from local to remote</span>
The commands are simple:</p>
<div class="highlight highlight-source-shell"><pre><span class="pl-c"><span class="pl-c">#</span> Sync from local to remote</span>
rclone sync MyBooks remote:MyBooks -P --exclude .DS_Store
<span class=pl-c><span class=pl-c>#</span> Sync from remote to local</span>
rclone sync remote:MyBooks MyBooks -P --exclude .DS_Store</pre></div><p>Before you use Rclone to sync to Google Drive, you should read <a href=https://rclone.org/drive/ rel=nofollow>Google Drive rclone configuration</a> first.<p>For private data, I use restic which can be used with Rclone:<div class="highlight highlight-source-shell"><pre><span class=pl-c><span class=pl-c>#</span> Init</span>
<span class="pl-c"><span class="pl-c">#</span> Sync from remote to local</span>
rclone sync remote:MyBooks MyBooks -P --exclude .DS_Store</pre></div>
<p>Before you use Rclone to sync to Google Drive, you should read <a href="https://rclone.org/drive/" rel="nofollow">Google Drive rclone configuration</a> first.</p>
<p>For private data, I use restic which can be used with Rclone:</p>
<div class="highlight highlight-source-shell"><pre><span class="pl-c"><span class="pl-c">#</span> Init</span>
restic -r rclone:remote:PrivateData init
<span class=pl-c><span class=pl-c>#</span> Backup</span>
<span class="pl-c"><span class="pl-c">#</span> Backup</span>
restic -r rclone:remote:PrivateData backup PrivateData
<span class=pl-c><span class=pl-c>#</span> Cleanup old backups</span>
<span class="pl-c"><span class="pl-c">#</span> Cleanup old backups</span>
restic -r rclone:remote:PrivateData forget --keep-last 1 --prune
<span class=pl-c><span class=pl-c>#</span> Restore</span>
restic -r rclone:remote:PrivateData restore latest --target <span class=pl-k>~</span></pre></div><p>The next data is my passwords and my OTPs.
<span class="pl-c"><span class="pl-c">#</span> Restore</span>
restic -r rclone:remote:PrivateData restore latest --target <span class="pl-k">~</span></pre></div>
<p>The next data is my passwords and my OTPs.
These are the things which I'm scare to lose the most.
First thing first, I enable 2-Step Verification for all of my important accounts, should use both OTP and phone method.<p>I use Bitwarden for passwords (That is a long story, coming from Google Password manager to Firefox Lockwise and then settle down with Bitwarden) and Aegis for OTPs.
The reason I choose Aegis, not Authy (I use Authy for so long but Aegis is definitely better) is because Aegis allows me to extract all the OTPs to a single file (Can be encrypted), which I use to transfer or backup easily.<p>As long as Bitwarden provides free passwords stored, I use all of its apps, extensions so that I can easily sync passwords between laptops and phones.
The thing I need to remember is the master password of Bitwarden in my head.<p>With Aegis, I export the data, then sync it to Google Drive, also store it locally in my phone.<p>The main problem here is the OTP, I can not store all of my OTPs in the cloud completely.
Because if I want to access my OTPs in the cloud, I should log in, and then input my OTP, this is a circle, my friends.<h2><a id=user-content-recovery-strategy class=anchor aria-hidden=true href=#recovery-strategy><span aria-hidden=true class="octicon octicon-link"></span></a>Recovery strategy</h2><p>There are many strategies that I process to react as if something strange is happening to my devices.<p>If I lost my laptops, single laptop or all, do not panic as long as I have my phones.
The OTPs are in there, the passwords are in Bitwarden cloud, other data is in Google Drive so nothing is lost here.<p>If I lost my phone, but not my laptops, I use the OTPs which are stored locally in my laptops.<p>In the worst situation, I lost everything, my laptops, my phone.
First thing first, I enable 2-Step Verification for all of my important accounts, should use both OTP and phone method.</p>
<p>I use Bitwarden for passwords (That is a long story, coming from Google Password manager to Firefox Lockwise and then settle down with Bitwarden) and Aegis for OTPs.
The reason I choose Aegis, not Authy (I use Authy for so long but Aegis is definitely better) is because Aegis allows me to extract all the OTPs to a single file (Can be encrypted), which I use to transfer or backup easily.</p>
<p>As long as Bitwarden provides free passwords stored, I use all of its apps, extensions so that I can easily sync passwords between laptops and phones.
The thing I need to remember is the master password of Bitwarden in my head.</p>
<p>With Aegis, I export the data, then sync it to Google Drive, also store it locally in my phone.</p>
<p>The main problem here is the OTP, I can not store all of my OTPs in the cloud completely.
Because if I want to access my OTPs in the cloud, I should log in, and then input my OTP, this is a circle, my friends.</p>
<h2><a id="user-content-recovery-strategy" class="anchor" aria-hidden="true" href="#recovery-strategy"><span aria-hidden="true" class="octicon octicon-link"></span></a>Recovery strategy</h2>
<p>There are many strategies that I process to react as if something strange is happening to my devices.</p>
<p>If I lost my laptops, single laptop or all, do not panic as long as I have my phones.
The OTPs are in there, the passwords are in Bitwarden cloud, other data is in Google Drive so nothing is lost here.</p>
<p>If I lost my phone, but not my laptops, I use the OTPs which are stored locally in my laptops.</p>
<p>In the worst situation, I lost everything, my laptops, my phone.
The first step is to recover my SIM, then log in to Google account using the password and SMS OTP.
After that, log in to Bitwarden account using the master password and OTP from Gmail, which I open previously.<h2><a id=user-content-the-end class=anchor aria-hidden=true href=#the-end><span aria-hidden=true class="octicon octicon-link"></span></a>The end</h2><p>This guide will be updated regularly I promise.</p><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
After that, log in to Bitwarden account using the master password and OTP from Gmail, which I open previously.</p>
<h2><a id="user-content-the-end" class="anchor" aria-hidden="true" href="#the-end"><span aria-hidden="true" class="octicon octicon-link"></span></a>The end</h2>
<p>This guide will be updated regularly I promise.</p>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1,44 +1,97 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-dockerfile-for-go class=anchor aria-hidden=true href=#dockerfile-for-go><span aria-hidden=true class="octicon octicon-link"></span></a>Dockerfile for Go</h1><p>Each time I start a new Go project, I repeat many steps.
Like set up <code>.gitignore</code>, CI configs, Dockerfile, ...<p>So I decide to have a baseline Dockerfile like this:<div class="highlight highlight-source-dockerfile"><pre><span class=pl-k>FROM</span> golang:1.19-bullseye as builder
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
<span class=pl-k>RUN</span> go install golang.org/dl/go1.19@latest \
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-dockerfile-for-go" class="anchor" aria-hidden="true" href="#dockerfile-for-go"><span aria-hidden="true" class="octicon octicon-link"></span></a>Dockerfile for Go</h1>
<p>Each time I start a new Go project, I repeat many steps.
Like set up <code>.gitignore</code>, CI configs, Dockerfile, ...</p>
<p>So I decide to have a baseline Dockerfile like this:</p>
<div class="highlight highlight-source-dockerfile"><pre><span class="pl-k">FROM</span> golang:1.19-bullseye as builder
<span class="pl-k">RUN</span> go install golang.org/dl/go1.19@latest \
&amp;&amp; go1.19 download
<span class=pl-k>WORKDIR</span> /build
<span class="pl-k">WORKDIR</span> /build
<span class=pl-k>COPY</span> go.mod .
<span class=pl-k>COPY</span> go.sum .
<span class=pl-k>COPY</span> vendor .
<span class=pl-k>COPY</span> . .
<span class="pl-k">COPY</span> go.mod .
<span class="pl-k">COPY</span> go.sum .
<span class="pl-k">COPY</span> vendor .
<span class="pl-k">COPY</span> . .
<span class=pl-k>RUN</span> CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 go build -o ./app -tags timetzdata -trimpath .
<span class="pl-k">RUN</span> CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 go build -o ./app -tags timetzdata -trimpath .
<span class=pl-k>FROM</span> gcr.io/distroless/base-debian11
<span class="pl-k">FROM</span> gcr.io/distroless/base-debian11
<span class=pl-k>COPY</span> --from=builder /build/app /app
<span class="pl-k">COPY</span> --from=builder /build/app /app
<span class=pl-k>ENTRYPOINT</span> [<span class=pl-s>"/app"</span>]</pre></div><p>I use <a href=https://docs.docker.com/develop/develop-images/multistage-build/ rel=nofollow>multi-stage build</a> to keep my image size small.
First stage is <a href=https://hub.docker.com/_/golang rel=nofollow>Go official image</a>,
second stage is <a href=https://github.com/GoogleContainerTools/distroless>Distroless</a>.<p>Before Distroless, I use <a href=https://hub.docker.com/_/alpine rel=nofollow>Alpine official image</a>,
<span class="pl-k">ENTRYPOINT</span> [<span class="pl-s">"/app"</span>]</pre></div>
<p>I use <a href="https://docs.docker.com/develop/develop-images/multistage-build/" rel="nofollow">multi-stage build</a> to keep my image size small.
First stage is <a href="https://hub.docker.com/_/golang" rel="nofollow">Go official image</a>,
second stage is <a href="https://github.com/GoogleContainerTools/distroless">Distroless</a>.</p>
<p>Before Distroless, I use <a href="https://hub.docker.com/_/alpine" rel="nofollow">Alpine official image</a>,
There is a whole discussion on the Internet to choose which is the best base image for Go.
After reading some blogs, I discover Distroless as a small and secure base image.
So I stick with it for a while.<p>Also, remember to match Distroless Debian version with Go official image Debian version.<div class="highlight highlight-source-dockerfile"><pre><span class=pl-k>FROM</span> golang:1.19-bullseye as builder</pre></div><p>This is Go image I use as a build stage.
This can be official Go image or custom image is required in some companies.<div class="highlight highlight-source-dockerfile"><pre><span class=pl-k>RUN</span> go install golang.org/dl/go1.19@latest \
&amp;&amp; go1.19 download</pre></div><p>This is optional.
In my case, my company is slow to update Go image so I use this trick to install latest Go version.<div class="highlight highlight-source-dockerfile"><pre><span class=pl-k>WORKDIR</span> /build
So I stick with it for a while.</p>
<p>Also, remember to match Distroless Debian version with Go official image Debian version.</p>
<div class="highlight highlight-source-dockerfile"><pre><span class="pl-k">FROM</span> golang:1.19-bullseye as builder</pre></div>
<p>This is Go image I use as a build stage.
This can be official Go image or custom image is required in some companies.</p>
<div class="highlight highlight-source-dockerfile"><pre><span class="pl-k">RUN</span> go install golang.org/dl/go1.19@latest \
&amp;&amp; go1.19 download</pre></div>
<p>This is optional.
In my case, my company is slow to update Go image so I use this trick to install latest Go version.</p>
<div class="highlight highlight-source-dockerfile"><pre><span class="pl-k">WORKDIR</span> /build
<span class=pl-k>COPY</span> go.mod .
<span class=pl-k>COPY</span> go.sum .
<span class=pl-k>COPY</span> vendor .
<span class=pl-k>COPY</span> . .</pre></div><p>I use <code>/build</code> to emphasize that I am building something in that directory.<p>The 4 <code>COPY</code> lines are familiar if you use Go enough.
<span class="pl-k">COPY</span> go.mod .
<span class="pl-k">COPY</span> go.sum .
<span class="pl-k">COPY</span> vendor .
<span class="pl-k">COPY</span> . .</pre></div>
<p>I use <code>/build</code> to emphasize that I am building something in that directory.</p>
<p>The 4 <code>COPY</code> lines are familiar if you use Go enough.
First is <code>go.mod</code> and <code>go.sum</code> because it defines Go modules.
The second is <code>vendor</code>, this is optional but I use it because I don't want each time I build Dockerfile, I need to redownload Go modules.<div class="highlight highlight-source-dockerfile"><pre><span class=pl-k>RUN</span> CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 go build -o ./app -tags timetzdata -trimpath .</pre></div><p>This is where I build Go program.<p><code>CGO_ENABLED=0</code> because I don't want to mess with C libraries.
The second is <code>vendor</code>, this is optional but I use it because I don't want each time I build Dockerfile, I need to redownload Go modules.</p>
<div class="highlight highlight-source-dockerfile"><pre><span class="pl-k">RUN</span> CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 go build -o ./app -tags timetzdata -trimpath .</pre></div>
<p>This is where I build Go program.</p>
<p><code>CGO_ENABLED=0</code> because I don't want to mess with C libraries.
<code>GOOS=linux GOARCH=amd64</code> is easy to explain, Linux with x86-64.
<code>GOAMD64=v3</code> is new since <a href=https://go.dev/doc/go1.18#amd64 rel=nofollow>Go 1.18</a>,
I use v3 because I read about AMD64 version in <a href=https://gitlab.archlinux.org/archlinux/rfcs/-/blob/master/rfcs/0002-march.rst rel=nofollow>Arch Linux rfcs</a>. TLDR's newer computers are already x86-64-v3.<p><code>-tags timetzdata</code> to embed timezone database incase base image does not have.
<code>-trimpath</code> to support reproduce build.<div class="highlight highlight-source-dockerfile"><pre><span class=pl-k>FROM</span> gcr.io/distroless/base-debian11
<code>GOAMD64=v3</code> is new since <a href="https://go.dev/doc/go1.18#amd64" rel="nofollow">Go 1.18</a>,
I use v3 because I read about AMD64 version in <a href="https://gitlab.archlinux.org/archlinux/rfcs/-/blob/master/rfcs/0002-march.rst" rel="nofollow">Arch Linux rfcs</a>. TLDR's newer computers are already x86-64-v3.</p>
<p><code>-tags timetzdata</code> to embed timezone database incase base image does not have.
<code>-trimpath</code> to support reproduce build.</p>
<div class="highlight highlight-source-dockerfile"><pre><span class="pl-k">FROM</span> gcr.io/distroless/base-debian11
<span class=pl-k>COPY</span> --from=builder /build/app /app
<span class="pl-k">COPY</span> --from=builder /build/app /app
<span class=pl-k>ENTRYPOINT</span> [<span class=pl-s>"/app"</span>]</pre></div><p>Finally, I copy <code>app</code> to Distroless base image.</p><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
<span class="pl-k">ENTRYPOINT</span> [<span class="pl-s">"/app"</span>]</pre></div>
<p>Finally, I copy <code>app</code> to Distroless base image.</p>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1,113 +1,279 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-bootstrap-go class=anchor aria-hidden=true href=#bootstrap-go><span aria-hidden=true class="octicon octicon-link"></span></a>Bootstrap Go</h1><p>It is hard to write bootstrap tool to quickly create Go service.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-bootstrap-go" class="anchor" aria-hidden="true" href="#bootstrap-go"><span aria-hidden="true" class="octicon octicon-link"></span></a>Bootstrap Go</h1>
<p>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.<h2><a id=user-content-structure class=anchor aria-hidden=true href=#structure><span aria-hidden=true class="octicon octicon-link"></span></a>Structure</h2><div class="highlight highlight-text-adblock"><pre>main.go
Also, this is my personal opinion, so feel free to comment.</p>
<h2><a id="user-content-structure" class="anchor" aria-hidden="true" href="#structure"><span aria-hidden="true" class="octicon octicon-link"></span></a>Structure</h2>
<div class="highlight highlight-text-adblock"><pre>main.go
internal
<span class=pl-k>|</span> business
<span class=pl-k>|</span> | http
<span class=pl-k>|</span> | | handler.go
<span class=pl-k>|</span> | | service.go
<span class=pl-k>|</span> | | models.go
<span class=pl-k>|</span> | grpc
<span class=pl-k>|</span> | | handler.go
<span class=pl-k>|</span> | | models.go
<span class=pl-k>|</span> | consumer
<span class=pl-k>|</span> | | handler.go
<span class=pl-k>|</span> | | service.go
<span class=pl-k>|</span> | | models.go
<span class=pl-k>|</span> | service.go
<span class=pl-k>|</span> | repository.go
<span class=pl-k>|</span> | models.go</pre></div><p>All business codes are inside <code>internal</code>.
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, ...<li><code>models</code> is where we put all request, response, data models.</ul><p>Location:<ul><li><code>handler</code> must exist inside <code>grpc</code>, <code>http</code>, <code>consumer</code>.<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><code>repository</code> should be placed directly inside of <code>business</code>.</ul><h2><a id=user-content-do-not-repeat class=anchor aria-hidden=true href=#do-not-repeat><span aria-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>For example, service A and service B both need to make POST call API to service C.
<span class="pl-k">|</span> business
<span class="pl-k">|</span> | http
<span class="pl-k">|</span> | | handler.go
<span class="pl-k">|</span> | | service.go
<span class="pl-k">|</span> | | models.go
<span class="pl-k">|</span> | grpc
<span class="pl-k">|</span> | | handler.go
<span class="pl-k">|</span> | | models.go
<span class="pl-k">|</span> | consumer
<span class="pl-k">|</span> | | handler.go
<span class="pl-k">|</span> | | service.go
<span class="pl-k">|</span> | | models.go
<span class="pl-k">|</span> | service.go
<span class="pl-k">|</span> | repository.go
<span class="pl-k">|</span> | models.go</pre></div>
<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><a id="user-content-do-not-repeat" class="anchor" aria-hidden="true" href="#do-not-repeat"><span aria-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.
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.<p>Another bad practice is adapter service.
No need to write a new service if what we need is just common pkg libs.<h2><a id=user-content-taste-on-style-guide class=anchor aria-hidden=true href=#taste-on-style-guide><span aria-hidden=true class="octicon octicon-link"></span></a>Taste on style guide</h2><h3><a id=user-content-stop-using-global-var class=anchor aria-hidden=true href=#stop-using-global-var><span aria-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>Why?<ul><li>Can not write unit test.<li>Is not thread safe.</ul><h3><a id=user-content-use-functional-options-but-dont-overuse-it class=anchor aria-hidden=true href=#use-functional-options-but-dont-overuse-it><span aria-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><a href=https://go.dev/play/p/0XnOLiHuoz3 rel=nofollow>Example</a>:<div class="highlight highlight-source-go"><pre><span class=pl-k>func</span> <span class=pl-en>main</span>() {
<span class=pl-s1>s</span> <span class=pl-c1>:=</span> <span class=pl-en>NewS</span>(<span class=pl-en>WithA</span>(<span class=pl-c1>1</span>), <span class=pl-en>WithB</span>(<span class=pl-s>"b"</span>))
<span class=pl-s1>fmt</span>.<span class=pl-en>Printf</span>(<span class=pl-s>"%+v<span class=pl-cce>\n</span>"</span>, <span class=pl-s1>s</span>)
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><a id="user-content-taste-on-style-guide" class="anchor" aria-hidden="true" href="#taste-on-style-guide"><span aria-hidden="true" class="octicon octicon-link"></span></a>Taste on style guide</h2>
<h3><a id="user-content-stop-using-global-var" class="anchor" aria-hidden="true" href="#stop-using-global-var"><span aria-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><a id="user-content-use-functional-options-but-dont-overuse-it" class="anchor" aria-hidden="true" href="#use-functional-options-but-dont-overuse-it"><span aria-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><a href="https://go.dev/play/p/0XnOLiHuoz3" rel="nofollow">Example</a>:</p>
<div class="highlight highlight-source-go"><pre><span class="pl-k">func</span> <span class="pl-en">main</span>() {
<span class="pl-s1">s</span> <span class="pl-c1">:=</span> <span class="pl-en">NewS</span>(<span class="pl-en">WithA</span>(<span class="pl-c1">1</span>), <span class="pl-en">WithB</span>(<span class="pl-s">"b"</span>))
<span class="pl-s1">fmt</span>.<span class="pl-en">Printf</span>(<span class="pl-s">"%+v<span class="pl-cce">\n</span>"</span>, <span class="pl-s1">s</span>)
}
<span class=pl-k>type</span> <span class=pl-smi>S</span> <span class=pl-k>struct</span> {
<span class=pl-c1>fieldA</span> <span class=pl-smi>int</span>
<span class=pl-c1>fieldB</span> <span class=pl-smi>string</span>
<span class="pl-k">type</span> <span class="pl-smi">S</span> <span class="pl-k">struct</span> {
<span class="pl-c1">fieldA</span> <span class="pl-smi">int</span>
<span class="pl-c1">fieldB</span> <span class="pl-smi">string</span>
}
<span class=pl-k>type</span> <span class=pl-smi>OptionS</span> <span class=pl-k>func</span>(<span class=pl-s1>s</span> <span class=pl-c1>*</span><span class=pl-smi>S</span>)
<span class="pl-k">type</span> <span class="pl-smi">OptionS</span> <span class="pl-k">func</span>(<span class="pl-s1">s</span> <span class="pl-c1">*</span><span class="pl-smi">S</span>)
<span class=pl-k>func</span> <span class=pl-en>WithA</span>(<span class=pl-s1>a</span> <span class=pl-smi>int</span>) <span class=pl-smi>OptionS</span> {
<span class=pl-k>return</span> <span class=pl-k>func</span>(<span class=pl-s1>s</span> <span class=pl-c1>*</span><span class=pl-smi>S</span>) {
<span class=pl-s1>s</span>.<span class=pl-c1>fieldA</span> <span class=pl-c1>=</span> <span class=pl-s1>a</span>
<span class="pl-k">func</span> <span class="pl-en">WithA</span>(<span class="pl-s1">a</span> <span class="pl-smi">int</span>) <span class="pl-smi">OptionS</span> {
<span class="pl-k">return</span> <span class="pl-k">func</span>(<span class="pl-s1">s</span> <span class="pl-c1">*</span><span class="pl-smi">S</span>) {
<span class="pl-s1">s</span>.<span class="pl-c1">fieldA</span> <span class="pl-c1">=</span> <span class="pl-s1">a</span>
}
}
<span class=pl-k>func</span> <span class=pl-en>WithB</span>(<span class=pl-s1>b</span> <span class=pl-smi>string</span>) <span class=pl-smi>OptionS</span> {
<span class=pl-k>return</span> <span class=pl-k>func</span>(<span class=pl-s1>s</span> <span class=pl-c1>*</span><span class=pl-smi>S</span>) {
<span class=pl-s1>s</span>.<span class=pl-c1>fieldB</span> <span class=pl-c1>=</span> <span class=pl-s1>b</span>
<span class="pl-k">func</span> <span class="pl-en">WithB</span>(<span class="pl-s1">b</span> <span class="pl-smi">string</span>) <span class="pl-smi">OptionS</span> {
<span class="pl-k">return</span> <span class="pl-k">func</span>(<span class="pl-s1">s</span> <span class="pl-c1">*</span><span class="pl-smi">S</span>) {
<span class="pl-s1">s</span>.<span class="pl-c1">fieldB</span> <span class="pl-c1">=</span> <span class="pl-s1">b</span>
}
}
<span class=pl-k>func</span> <span class=pl-en>NewS</span>(<span class=pl-s1>opts</span> <span class=pl-c1>...</span><span class=pl-smi>OptionS</span>) <span class=pl-c1>*</span><span class=pl-smi>S</span> {
<span class=pl-s1>s</span> <span class=pl-c1>:=</span> <span class=pl-c1>&amp;</span><span class=pl-smi>S</span>{}
<span class=pl-k>for</span> <span class=pl-s1>_</span>, <span class=pl-s1>opt</span> <span class=pl-c1>:=</span> <span class=pl-k>range</span> <span class=pl-s1>opts</span> {
<span class=pl-en>opt</span>(<span class=pl-s1>s</span>)
<span class="pl-k">func</span> <span class="pl-en">NewS</span>(<span class="pl-s1">opts</span> <span class="pl-c1">...</span><span class="pl-smi">OptionS</span>) <span class="pl-c1">*</span><span class="pl-smi">S</span> {
<span class="pl-s1">s</span> <span class="pl-c1">:=</span> <span class="pl-c1">&amp;</span><span class="pl-smi">S</span>{}
<span class="pl-k">for</span> <span class="pl-s1">_</span>, <span class="pl-s1">opt</span> <span class="pl-c1">:=</span> <span class="pl-k">range</span> <span class="pl-s1">opts</span> {
<span class="pl-en">opt</span>(<span class="pl-s1">s</span>)
}
<span class=pl-k>return</span> <span class=pl-s1>s</span>
}</pre></div><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>.<h3><a id=user-content-use-errgroup-as-much-as-possible class=anchor aria-hidden=true href=#use-errgroup-as-much-as-possible><span aria-hidden=true class="octicon octicon-link"></span></a>Use <a href=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>Personally, I prefer <code>errgroup</code> to <code>WaitGroup</code> (<a href=https://pkg.go.dev/sync#WaitGroup rel=nofollow>https://pkg.go.dev/sync#WaitGroup</a>).
Because I always need deal with error.<p>Example:<div class="highlight highlight-source-go"><pre><span class=pl-s1>eg</span>, <span class=pl-s1>egCtx</span> <span class=pl-c1>:=</span> <span class=pl-s1>errgroup</span>.<span class=pl-en>WithContext</span>(<span class=pl-s1>ctx</span>)
<span class="pl-k">return</span> <span class="pl-s1">s</span>
}</pre></div>
<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><a id="user-content-use-errgroup-as-much-as-possible" class="anchor" aria-hidden="true" href="#use-errgroup-as-much-as-possible"><span aria-hidden="true" class="octicon octicon-link"></span></a>Use <a href="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> (<a href="https://pkg.go.dev/sync#WaitGroup" rel="nofollow">https://pkg.go.dev/sync#WaitGroup</a>).
Because I always need deal with error.</p>
<p>Example:</p>
<div class="highlight highlight-source-go"><pre><span class="pl-s1">eg</span>, <span class="pl-s1">egCtx</span> <span class="pl-c1">:=</span> <span class="pl-s1">errgroup</span>.<span class="pl-en">WithContext</span>(<span class="pl-s1">ctx</span>)
<span class=pl-s1>eg</span>.<span class=pl-en>Go</span>(<span class=pl-k>func</span>() <span class=pl-smi>error</span> {
<span class=pl-c>// Do some thing</span>
<span class=pl-k>return</span> <span class=pl-c1>nil</span>
<span class="pl-s1">eg</span>.<span class="pl-en">Go</span>(<span class="pl-k">func</span>() <span class="pl-smi">error</span> {
<span class="pl-c">// Do some thing</span>
<span class="pl-k">return</span> <span class="pl-c1">nil</span>
})
<span class=pl-s1>eg</span>.<span class=pl-en>Go</span>(<span class=pl-k>func</span>() <span class=pl-smi>error</span> {
<span class=pl-c>// Do other thing</span>
<span class=pl-k>return</span> <span class=pl-c1>nil</span>
<span class="pl-s1">eg</span>.<span class="pl-en">Go</span>(<span class="pl-k">func</span>() <span class="pl-smi">error</span> {
<span class="pl-c">// Do other thing</span>
<span class="pl-k">return</span> <span class="pl-c1">nil</span>
})
<span class=pl-k>if</span> <span class=pl-s1>err</span> <span class=pl-c1>:=</span> <span class=pl-s1>eg</span>.<span class=pl-en>Wait</span>(); <span class=pl-s1>err</span> <span class=pl-c1>!=</span> <span class=pl-c1>nil</span> {
<span class=pl-c>// Handle error</span>
}</pre></div><h3><a id=user-content-use-semaphore-when-need-to-implement-workerpool class=anchor aria-hidden=true href=#use-semaphore-when-need-to-implement-workerpool><span aria-hidden=true class="octicon octicon-link"></span></a>Use <a href=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.<h2><a id=user-content-external-libs class=anchor aria-hidden=true href=#external-libs><span aria-hidden=true class="octicon octicon-link"></span></a>External libs</h2><h3><a id=user-content-no-need-vendor class=anchor aria-hidden=true href=#no-need-vendor><span aria-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.<h3><a id=user-content-use-buildgo-to-include-build-tools-in-gomod class=anchor aria-hidden=true href=#use-buildgo-to-include-build-tools-in-gomod><span aria-hidden=true class="octicon octicon-link"></span></a>Use <code>build.go</code> to include build tools in go.mod</h3><p>To easily control version of build tools.<p>For example <code>build.go</code>:<div class="highlight highlight-source-go"><pre><span class=pl-c>//go:build tools</span>
<span class=pl-c>// +build tools</span>
<span class="pl-k">if</span> <span class="pl-s1">err</span> <span class="pl-c1">:=</span> <span class="pl-s1">eg</span>.<span class="pl-en">Wait</span>(); <span class="pl-s1">err</span> <span class="pl-c1">!=</span> <span class="pl-c1">nil</span> {
<span class="pl-c">// Handle error</span>
}</pre></div>
<h3><a id="user-content-use-semaphore-when-need-to-implement-workerpool" class="anchor" aria-hidden="true" href="#use-semaphore-when-need-to-implement-workerpool"><span aria-hidden="true" class="octicon octicon-link"></span></a>Use <a href="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>
<h2><a id="user-content-external-libs" class="anchor" aria-hidden="true" href="#external-libs"><span aria-hidden="true" class="octicon octicon-link"></span></a>External libs</h2>
<h3><a id="user-content-no-need-vendor" class="anchor" aria-hidden="true" href="#no-need-vendor"><span aria-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><a id="user-content-use-buildgo-to-include-build-tools-in-gomod" class="anchor" aria-hidden="true" href="#use-buildgo-to-include-build-tools-in-gomod"><span aria-hidden="true" class="octicon octicon-link"></span></a>Use <code>build.go</code> to include build tools in go.mod</h3>
<p>To easily control version of build tools.</p>
<p>For example <code>build.go</code>:</p>
<div class="highlight highlight-source-go"><pre><span class="pl-c">//go:build tools</span>
<span class="pl-c">// +build tools</span>
<span class=pl-k>package</span> main
<span class="pl-k">package</span> main
<span class=pl-k>import</span> (
_ <span class=pl-s>"github.com/golang/protobuf/protoc-gen-go"</span>
)</pre></div><p>And then in <code>Makefile</code>:<div class="highlight highlight-source-makefile"><pre><span class=pl-en>build</span>:
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.<h3><a id=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><span aria-hidden=true class="octicon octicon-link"></span></a>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/ rel=nofollow>The Twelve Factors</a> guide.<h3><a id=user-content-dont-use-grpc-ecosystemgrpc-gateway class=anchor aria-hidden=true href=#dont-use-grpc-ecosystemgrpc-gateway><span aria-hidden=true class="octicon octicon-link"></span></a>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><a id=user-content-dont-use-uberprototool-use-bufbuildbuf class=anchor aria-hidden=true href=#dont-use-uberprototool-use-bufbuildbuf><span aria-hidden=true class="octicon octicon-link"></span></a>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><a id=user-content-use-gin-gonicgin-for-rest class=anchor aria-hidden=true href=#use-gin-gonicgin-for-rest><span aria-hidden=true class="octicon octicon-link"></span></a>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><a id=user-content-if-you-want-log-just-use-uber-gozap class=anchor aria-hidden=true href=#if-you-want-log-just-use-uber-gozap><span aria-hidden=true class="octicon octicon-link"></span></a>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>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>Use <code>MarshalLogObject</code> when we need to hide some field of object when log (field is long or has sensitive value)<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>If doubt, use <code>zap.Any</code>.<li>Use <code>contextID</code> or <code>traceID</code> in every log lines for easily debug.</ul><h3><a id=user-content-to-read-config-use-spf13viper class=anchor aria-hidden=true href=#to-read-config-use-spf13viper><span aria-hidden=true class="octicon octicon-link"></span></a>To read config, use <a href=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>Why?<ul><li>Hard to mock and test<li>Put all config in single place for easily tracking</ul><p>Also, be careful if config value is empty.
You should decide to continue or stop the service if there is no config.<h3><a id=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><span aria-hidden=true class="octicon octicon-link"></span></a>Don't overuse ORM libs, no need to handle another layer above SQL.</h3><p>Each ORM libs has each different syntax.
<span class="pl-k">import</span> (
_ <span class="pl-s">"github.com/golang/protobuf/protoc-gen-go"</span>
)</pre></div>
<p>And then in <code>Makefile</code>:</p>
<div class="highlight highlight-source-makefile"><pre><span class="pl-en">build</span>:
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><a id="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"><span aria-hidden="true" class="octicon octicon-link"></span></a>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>
<p>In my case, service starts with only config, and config should be read from file or environment like <a href="https://12factor.net/" rel="nofollow">The Twelve Factors</a> guide.</p>
<h3><a id="user-content-dont-use-grpc-ecosystemgrpc-gateway" class="anchor" aria-hidden="true" href="#dont-use-grpc-ecosystemgrpc-gateway"><span aria-hidden="true" class="octicon octicon-link"></span></a>Don't use <a href="https://github.com/grpc-ecosystem/grpc-gateway">grpc-ecosystem/grpc-gateway</a>
</h3>
<p>Just don't.</p>
<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>
<p>Write 1 for both gRPC, REST sounds good, but in the end, it is not worth it.</p>
<h3><a id="user-content-dont-use-uberprototool-use-bufbuildbuf" class="anchor" aria-hidden="true" href="#dont-use-uberprototool-use-bufbuildbuf"><span aria-hidden="true" class="octicon octicon-link"></span></a>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.</p>
<h3><a id="user-content-use-gin-gonicgin-for-rest" class="anchor" aria-hidden="true" href="#use-gin-gonicgin-for-rest"><span aria-hidden="true" class="octicon octicon-link"></span></a>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.</p>
<h3><a id="user-content-if-you-want-log-just-use-uber-gozap" class="anchor" aria-hidden="true" href="#if-you-want-log-just-use-uber-gozap"><span aria-hidden="true" class="octicon octicon-link"></span></a>If you want log, just use <a href="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><a id="user-content-to-read-config-use-spf13viper" class="anchor" aria-hidden="true" href="#to-read-config-use-spf13viper"><span aria-hidden="true" class="octicon octicon-link"></span></a>To read config, use <a href="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><a id="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"><span aria-hidden="true" class="octicon octicon-link"></span></a>Don't overuse ORM libs, no need to handle another layer above SQL.</h3>
<p>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.<p>But <code>database/sql</code> has its own limit.
It is easier to debug when something is wrong.</p>
<p>But <code>database/sql</code> has its own limit.
For example, it is hard to get primary key after insert/update.
So may be you want to use ORM for those cases.
I hear that <a href=https://github.com/go-gorm/gorm>go-gorm/gorm</a>, <a href=https://github.com/ent/ent>ent/ent</a> is good.<h3><a id=user-content-if-you-want-test-just-use-stretchrtestify class=anchor aria-hidden=true href=#if-you-want-test-just-use-stretchrtestify><span aria-hidden=true class="octicon octicon-link"></span></a>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.
I hear that <a href="https://github.com/go-gorm/gorm">go-gorm/gorm</a>, <a href="https://github.com/ent/ent">ent/ent</a> is good.</p>
<h3><a id="user-content-if-you-want-test-just-use-stretchrtestify" class="anchor" aria-hidden="true" href="#if-you-want-test-just-use-stretchrtestify"><span aria-hidden="true" class="octicon octicon-link"></span></a>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.
Also, for mocking, there are many options out there.
Pick 1 then sleep peacefully.<h3><a id=user-content-if-need-to-mock-choose-matryermoq-or-golangmock class=anchor aria-hidden=true href=#if-need-to-mock-choose-matryermoq-or-golangmock><span aria-hidden=true class="octicon octicon-link"></span></a>If need to mock, choose <a href=https://github.com/matryer/moq>matryer/moq</a> or <a href=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>Example with <code>matryer/moq</code>:<div class="highlight highlight-source-go"><pre><span class=pl-c>// Only gen mock if source code file is newer than mock file</span>
<span class=pl-c>// https://jonwillia.ms/2019/12/22/conditional-gomock-mockgen</span>
<span class=pl-c>//go:generate sh -c "test service_mock_generated.go -nt $GOFILE &amp;&amp; exit 0; moq -rm -out service_mock_generated.go . Service"</span></pre></div><h3><a id=user-content-be-careful-with-spf13cast class=anchor aria-hidden=true href=#be-careful-with-spf13cast><span aria-hidden=true class="octicon octicon-link"></span></a>Be careful with <a href=https://github.com/spf13/cast>spf13/cast</a></h3><p>Don't cast proto enum:<div class="highlight highlight-source-go"><pre><span class=pl-c>// Bad</span>
<span class=pl-s1>a</span> <span class=pl-c1>:=</span> <span class=pl-s1>cast</span>.<span class=pl-en>ToInt32</span>(<span class=pl-s1>servicev1</span>.<span class=pl-c1>ReasonCode_ABC</span>)
Pick 1 then sleep peacefully.</p>
<h3><a id="user-content-if-need-to-mock-choose-matryermoq-or-golangmock" class="anchor" aria-hidden="true" href="#if-need-to-mock-choose-matryermoq-or-golangmock"><span aria-hidden="true" class="octicon octicon-link"></span></a>If need to mock, choose <a href="https://github.com/matryer/moq">matryer/moq</a> or <a href="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>
<div class="highlight highlight-source-go"><pre><span class="pl-c">// Only gen mock if source code file is newer than mock file</span>
<span class="pl-c">// https://jonwillia.ms/2019/12/22/conditional-gomock-mockgen</span>
<span class="pl-c">//go:generate sh -c "test service_mock_generated.go -nt $GOFILE &amp;&amp; exit 0; moq -rm -out service_mock_generated.go . Service"</span></pre></div>
<h3><a id="user-content-be-careful-with-spf13cast" class="anchor" aria-hidden="true" href="#be-careful-with-spf13cast"><span aria-hidden="true" class="octicon octicon-link"></span></a>Be careful with <a href="https://github.com/spf13/cast">spf13/cast</a>
</h3>
<p>Don't cast proto enum:</p>
<div class="highlight highlight-source-go"><pre><span class="pl-c">// Bad</span>
<span class="pl-s1">a</span> <span class="pl-c1">:=</span> <span class="pl-s1">cast</span>.<span class="pl-en">ToInt32</span>(<span class="pl-s1">servicev1</span>.<span class="pl-c1">ReasonCode_ABC</span>)
<span class=pl-c>// Good</span>
<span class=pl-s1>a</span> <span class=pl-c1>:=</span> <span class=pl-en>int32</span>(<span class=pl-s1>servicev1</span>.<span class=pl-c1>ReasonCode_ABC</span>)</pre></div><h3><a id=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><span aria-hidden=true class="octicon octicon-link"></span></a>Use <a href=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><div class="highlight highlight-source-go"><pre><span class=pl-k>type</span> <span class=pl-smi>Drink</span> <span class=pl-smi>int</span>
<span class="pl-c">// Good</span>
<span class="pl-s1">a</span> <span class="pl-c1">:=</span> <span class="pl-en">int32</span>(<span class="pl-s1">servicev1</span>.<span class="pl-c1">ReasonCode_ABC</span>)</pre></div>
<h3><a id="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"><span aria-hidden="true" class="octicon octicon-link"></span></a>Use <a href="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>
<div class="highlight highlight-source-go"><pre><span class="pl-k">type</span> <span class="pl-smi">Drink</span> <span class="pl-smi">int</span>
<span class=pl-k>const</span> (
<span class=pl-s1>Beer</span> <span class=pl-smi>Drink</span> <span class=pl-c1>=</span> <span class=pl-s1>iota</span>
<span class=pl-s1>Water</span>
<span class=pl-s1>OrangeJuice</span>
)</pre></div><div class="highlight highlight-source-shell"><pre>go install golang.org/x/tools/cmd/stringer@latest
<span class="pl-k">const</span> (
<span class="pl-s1">Beer</span> <span class="pl-smi">Drink</span> <span class="pl-c1">=</span> <span class="pl-s1">iota</span>
<span class="pl-s1">Water</span>
<span class="pl-s1">OrangeJuice</span>
)</pre></div>
<div class="highlight highlight-source-shell"><pre>go install golang.org/x/tools/cmd/stringer@latest
<span class=pl-c><span class=pl-c>#</span> Run inside directory which contains Drink</span>
stringer -type=Drink</pre></div><h3><a id=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><span aria-hidden=true class="octicon octicon-link"></span></a>Don't waste your time rewrite rate limiter if your use case is simple, use <a href=https://pkg.go.dev/golang.org/x/time/rate rel=nofollow>rate</a> or <a href=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.<h3><a id=user-content-replace-go-fmt-goimports-with-mvdangofumpt class=anchor aria-hidden=true href=#replace-go-fmt-goimports-with-mvdangofumpt><span aria-hidden=true class="octicon octicon-link"></span></a>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><a id=user-content-use-golangcigolangci-lint class=anchor aria-hidden=true href=#use-golangcigolangci-lint><span aria-hidden=true class="octicon octicon-link"></span></a>Use <a href=https://github.com/golangci/golangci-lint>golangci/golangci-lint</a>.</h3><p>No need to say more.
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 rel=nofollow>fieldalignment</a> to fix them.<div class="highlight highlight-source-shell"><pre><span class=pl-c><span class=pl-c>#</span> Install</span>
<span class="pl-c"><span class="pl-c">#</span> Run inside directory which contains Drink</span>
stringer -type=Drink</pre></div>
<h3><a id="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"><span aria-hidden="true" class="octicon octicon-link"></span></a>Don't waste your time rewrite rate limiter if your use case is simple, use <a href="https://pkg.go.dev/golang.org/x/time/rate" rel="nofollow">rate</a> or <a href="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><a id="user-content-replace-go-fmt-goimports-with-mvdangofumpt" class="anchor" aria-hidden="true" href="#replace-go-fmt-goimports-with-mvdangofumpt"><span aria-hidden="true" class="octicon octicon-link"></span></a>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.</p>
<h3><a id="user-content-use-golangcigolangci-lint" class="anchor" aria-hidden="true" href="#use-golangcigolangci-lint"><span aria-hidden="true" class="octicon octicon-link"></span></a>Use <a href="https://github.com/golangci/golangci-lint">golangci/golangci-lint</a>.</h3>
<p>No need to say more.
Lint or get the f out!</p>
<p>If you get <code>fieldalignment</code> error, use <a href="https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment" rel="nofollow">fieldalignment</a> to fix them.</p>
<div class="highlight highlight-source-shell"><pre><span class="pl-c"><span class="pl-c">#</span> Install</span>
go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest
<span class=pl-c><span class=pl-c>#</span> Fix</span>
fieldalignment -fix ./internal/business/<span class=pl-k>*</span>.go</pre></div><h2><a id=user-content-thanks class=anchor aria-hidden=true href=#thanks><span aria-hidden=true class="octicon octicon-link"></span></a>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 rel=nofollow>Functional options for friendly APIs</a><li><a href=https://google.github.io/styleguide/go/index rel=nofollow>Google Go Style</a></ul><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
<span class="pl-c"><span class="pl-c">#</span> Fix</span>
fieldalignment -fix ./internal/business/<span class="pl-k">*</span>.go</pre></div>
<h2><a id="user-content-thanks" class="anchor" aria-hidden="true" href="#thanks"><span aria-hidden="true" class="octicon octicon-link"></span></a>Thanks</h2>
<ul>
<li><a href="https://github.com/uber-go/guide/blob/master/style.md">Uber Go Style Guide</a></li>
<li><a href="https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis" rel="nofollow">Functional options for friendly APIs</a></li>
<li><a href="https://google.github.io/styleguide/go/index" rel="nofollow">Google Go Style</a></li>
</ul>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1,12 +1,75 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-uuid-or-else class=anchor aria-hidden=true href=#uuid-or-else><span aria-hidden=true class="octicon octicon-link"></span></a>UUID or else</h1><p>There are many use cases where we need to use a unique ID.
In my experience, I only encouter 2 cases:<ul><li>ID to trace request from client to server, from service to service (microservice architecture or nanoservice I don't know).<li>Primary key for database.</ul><p>In my Go universe, there are some libs to help us with this:<ul><li><a href=https://github.com/google/uuid>google/uuid</a><li><a href=https://github.com/rs/xid>rs/xid</a><li><a href=https://github.com/segmentio/ksuid>segmentio/ksuid</a><li><a href=https://github.com/oklog/ulid>oklog/ulid</a></ul><h2><a id=user-content-first-use-case-is-trace-id-or-context-aware-id class=anchor aria-hidden=true href=#first-use-case-is-trace-id-or-context-aware-id><span aria-hidden=true class="octicon octicon-link"></span></a>First use case is trace ID, or context aware ID</h2><p>The ID is used only for trace and log.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-uuid-or-else" class="anchor" aria-hidden="true" href="#uuid-or-else"><span aria-hidden="true" class="octicon octicon-link"></span></a>UUID or else</h1>
<p>There are many use cases where we need to use a unique ID.
In my experience, I only encouter 2 cases:</p>
<ul>
<li>ID to trace request from client to server, from service to service (microservice architecture or nanoservice I don't know).</li>
<li>Primary key for database.</li>
</ul>
<p>In my Go universe, there are some libs to help us with this:</p>
<ul>
<li><a href="https://github.com/google/uuid">google/uuid</a></li>
<li><a href="https://github.com/rs/xid">rs/xid</a></li>
<li><a href="https://github.com/segmentio/ksuid">segmentio/ksuid</a></li>
<li><a href="https://github.com/oklog/ulid">oklog/ulid</a></li>
</ul>
<h2><a id="user-content-first-use-case-is-trace-id-or-context-aware-id" class="anchor" aria-hidden="true" href="#first-use-case-is-trace-id-or-context-aware-id"><span aria-hidden="true" class="octicon octicon-link"></span></a>First use case is trace ID, or context aware ID</h2>
<p>The ID is used only for trace and log.
If same ID is generated twice (because maybe the possibilty is too small but not 0), honestly I don't care.
When I use that ID to search log , if it pops more than things I care for, it is still no harm to me.<p>My choice for this use case is <strong>rs/xid</strong>.
Because it is small (not span too much on log line) and copy friendly.<h2><a id=user-content-second-use-case-is-primary-key-also-hard-choice class=anchor aria-hidden=true href=#second-use-case-is-primary-key-also-hard-choice><span aria-hidden=true class="octicon octicon-link"></span></a>Second use case is primary key, also hard choice</h2><p>Why I don't use auto increment key for primary key?
When I use that ID to search log , if it pops more than things I care for, it is still no harm to me.</p>
<p>My choice for this use case is <strong>rs/xid</strong>.
Because it is small (not span too much on log line) and copy friendly.</p>
<h2><a id="user-content-second-use-case-is-primary-key-also-hard-choice" class="anchor" aria-hidden="true" href="#second-use-case-is-primary-key-also-hard-choice"><span aria-hidden="true" class="octicon octicon-link"></span></a>Second use case is primary key, also hard choice</h2>
<p>Why I don't use auto increment key for primary key?
The answer is simple, I don't want to write database specific SQL.
SQLite has some different syntax from MySQL, and PostgreSQL and so on.
Every logic I can move to application layer from database layer, I will.<p>In the past and present, I use <strong>google/uuid</strong>, specificially I use UUID v4.
Every logic I can move to application layer from database layer, I will.</p>
<p>In the past and present, I use <strong>google/uuid</strong>, specificially I use UUID v4.
In the future I will look to use <strong>segmentio/ksuid</strong> and <strong>oklog/ulid</strong> (trial and error of course).
Both are sortable, but <strong>google/uuid</strong> is not.
The reason I'm afraid because the database is sensitive subject, and I need more testing and battle test proof to trust those libs.<h2><a id=user-content-what-else class=anchor aria-hidden=true href=#what-else><span aria-hidden=true class="octicon octicon-link"></span></a>What else?</h2><p>I think about adding prefix to ID to identify which resource that ID represents.<h2><a id=user-content-thanks class=anchor aria-hidden=true href=#thanks><span aria-hidden=true class="octicon octicon-link"></span></a>Thanks</h2><ul><li><a href=https://www.cybertec-postgresql.com/en/uuid-serial-or-identity-columns-for-postgresql-auto-generated-primary-keys/ rel=nofollow>UUID, SERIAL OR IDENTITY COLUMNS FOR POSTGRESQL AUTO-GENERATED PRIMARY KEYS?</a><li><a href=https://brandur.org/nanoglyphs/026-ids rel=nofollow>Identity Crisis: Sequence v. UUID as Primary Key</a><li><a href=https://blog.kowalczyk.info/article/JyRZ/generating-good-unique-ids-in-go.html rel=nofollow>Generating good unique ids in Go</a><li><a href=https://encore.dev/blog/go-1.18-generic-identifiers rel=nofollow>How we used Go 1.18 when designing our Identifiers</a><li><a href=https://blog.daveallie.com/ulid-primary-keys rel=nofollow>ULIDs and Primary Keys</a><li><a href=https://0pointer.net/blog/projects/ids.html rel=nofollow>On IDs</a></ul><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
The reason I'm afraid because the database is sensitive subject, and I need more testing and battle test proof to trust those libs.</p>
<h2><a id="user-content-what-else" class="anchor" aria-hidden="true" href="#what-else"><span aria-hidden="true" class="octicon octicon-link"></span></a>What else?</h2>
<p>I think about adding prefix to ID to identify which resource that ID represents.</p>
<h2><a id="user-content-thanks" class="anchor" aria-hidden="true" href="#thanks"><span aria-hidden="true" class="octicon octicon-link"></span></a>Thanks</h2>
<ul>
<li><a href="https://www.cybertec-postgresql.com/en/uuid-serial-or-identity-columns-for-postgresql-auto-generated-primary-keys/" rel="nofollow">UUID, SERIAL OR IDENTITY COLUMNS FOR POSTGRESQL AUTO-GENERATED PRIMARY KEYS?</a></li>
<li><a href="https://brandur.org/nanoglyphs/026-ids" rel="nofollow">Identity Crisis: Sequence v. UUID as Primary Key</a></li>
<li><a href="https://blog.kowalczyk.info/article/JyRZ/generating-good-unique-ids-in-go.html" rel="nofollow">Generating good unique ids in Go</a></li>
<li><a href="https://encore.dev/blog/go-1.18-generic-identifiers" rel="nofollow">How we used Go 1.18 when designing our Identifiers</a></li>
<li><a href="https://blog.daveallie.com/ulid-primary-keys" rel="nofollow">ULIDs and Primary Keys</a></li>
<li><a href="https://0pointer.net/blog/projects/ids.html" rel="nofollow">On IDs</a></li>
</ul>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1,43 +1,96 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-migrate-to-buf-from-prototool class=anchor aria-hidden=true href=#migrate-to-buf-from-prototool><span aria-hidden=true class="octicon octicon-link"></span></a>Migrate to <code>buf</code> from <code>prototool</code></h1><p>Why? Because <code>prototool</code> is outdated, and can not run on M1 mac.<p>We need 3 files:<ul><li><code>build.go</code>: need to install protoc-gen-* binaries with pin version in <code>go.mod</code><li><code>buf.yaml</code><li><code>buf.gen.yaml</code></ul><p>FYI, the libs version I use:<ul><li><a href=https://github.com/golang/protobuf/releases/tag/v1.5.2>golang/protobuf v1.5.2</a><li><a href=https://github.com/grpc-ecosystem/grpc-gateway/releases/tag/v1.16.0>grpc-ecosystem/grpc-gateway v1.16.0</a><li><a href=github.com/envoyproxy/protoc-gen-validate>envoyproxy/protoc-gen-validate</a><li><a href=github.com/kei2100/protoc-gen-marshal-zap>kei2100/protoc-gen-marshal-zap</a></ul><p><code>build.go</code>:<div class="highlight highlight-source-go"><pre><span class=pl-c>//go:build tools</span>
<span class=pl-c>// +build tools</span>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
<span class=pl-k>import</span> (
_ <span class=pl-s>"github.com/envoyproxy/protoc-gen-validate"</span>
_ <span class=pl-s>"github.com/golang/protobuf/protoc-gen-go"</span>
_ <span class=pl-s>"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway"</span>
_ <span class=pl-s>"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger"</span>
_ <span class=pl-s>"github.com/kei2100/protoc-gen-marshal-zap/plugin/protoc-gen-marshal-zap"</span>
)</pre></div><p><code>buf.yaml</code><div class="highlight highlight-source-yaml"><pre><span class=pl-ent>version</span>: <span class=pl-c1>v1</span>
<span class=pl-ent>deps</span>:
- <span class=pl-s>buf.build/haunt98/googleapis:b38d93f7ade94a698adff9576474ae7c</span>
- <span class=pl-s>buf.build/haunt98/grpc-gateway:ecf4f0f58aa8496f8a76ed303c6e06c7</span>
- <span class=pl-s>buf.build/haunt98/protoc-gen-validate:2686264610fc4ad4a9fcc932647e279d</span>
- <span class=pl-s>buf.build/haunt98/marshal-zap:2a593ca925134680a5820d3f13c1be5a</span>
<span class=pl-ent>breaking</span>:
<span class=pl-ent>use</span>:
- <span class=pl-s>FILE</span>
<span class=pl-ent>lint</span>:
<span class=pl-ent>use</span>:
- <span class=pl-s>DEFAULT</span></pre></div><p><code>buf.gen.yaml</code>:<div class="highlight highlight-source-yaml"><pre><span class=pl-ent>version</span>: <span class=pl-c1>v1</span>
<span class=pl-ent>plugins</span>:
- <span class=pl-ent>name</span>: <span class=pl-s>go</span>
<span class=pl-ent>out</span>: <span class=pl-s>pkg</span>
<span class=pl-ent>opt</span>:
- <span class=pl-s>plugins=grpc</span>
- <span class=pl-ent>name</span>: <span class=pl-s>grpc-gateway</span>
<span class=pl-ent>out</span>: <span class=pl-s>pkg</span>
<span class=pl-ent>opt</span>:
- <span class=pl-s>logtostderr=true</span>
- <span class=pl-ent>name</span>: <span class=pl-s>swagger</span>
<span class=pl-ent>out</span>: <span class=pl-s>.</span>
<span class=pl-ent>opt</span>:
- <span class=pl-s>logtostderr=true</span>
- <span class=pl-ent>name</span>: <span class=pl-s>validate</span>
<span class=pl-ent>out</span>: <span class=pl-s>pkg</span>
<span class=pl-ent>opt</span>:
- <span class=pl-s>lang=go</span>
- <span class=pl-ent>name</span>: <span class=pl-s>marshal-zap</span>
<span class=pl-ent>out</span>: <span class=pl-s>pkg</span></pre></div><p>Update <code>Makefile</code>:<div class="highlight highlight-source-makefile"><pre><span class=pl-en>gen</span>:
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-migrate-to-buf-from-prototool" class="anchor" aria-hidden="true" href="#migrate-to-buf-from-prototool"><span aria-hidden="true" class="octicon octicon-link"></span></a>Migrate to <code>buf</code> from <code>prototool</code>
</h1>
<p>Why? Because <code>prototool</code> is outdated, and can not run on M1 mac.</p>
<p>We need 3 files:</p>
<ul>
<li>
<code>build.go</code>: need to install protoc-gen-* binaries with pin version in <code>go.mod</code>
</li>
<li><code>buf.yaml</code></li>
<li><code>buf.gen.yaml</code></li>
</ul>
<p>FYI, the libs version I use:</p>
<ul>
<li><a href="https://github.com/golang/protobuf/releases/tag/v1.5.2">golang/protobuf v1.5.2</a></li>
<li><a href="https://github.com/grpc-ecosystem/grpc-gateway/releases/tag/v1.16.0">grpc-ecosystem/grpc-gateway v1.16.0</a></li>
<li><a href="github.com/envoyproxy/protoc-gen-validate">envoyproxy/protoc-gen-validate</a></li>
<li><a href="github.com/kei2100/protoc-gen-marshal-zap">kei2100/protoc-gen-marshal-zap</a></li>
</ul>
<p><code>build.go</code>:</p>
<div class="highlight highlight-source-go"><pre><span class="pl-c">//go:build tools</span>
<span class="pl-c">// +build tools</span>
<span class="pl-k">import</span> (
_ <span class="pl-s">"github.com/envoyproxy/protoc-gen-validate"</span>
_ <span class="pl-s">"github.com/golang/protobuf/protoc-gen-go"</span>
_ <span class="pl-s">"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway"</span>
_ <span class="pl-s">"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger"</span>
_ <span class="pl-s">"github.com/kei2100/protoc-gen-marshal-zap/plugin/protoc-gen-marshal-zap"</span>
)</pre></div>
<p><code>buf.yaml</code></p>
<div class="highlight highlight-source-yaml"><pre><span class="pl-ent">version</span>: <span class="pl-c1">v1</span>
<span class="pl-ent">deps</span>:
- <span class="pl-s">buf.build/haunt98/googleapis:b38d93f7ade94a698adff9576474ae7c</span>
- <span class="pl-s">buf.build/haunt98/grpc-gateway:ecf4f0f58aa8496f8a76ed303c6e06c7</span>
- <span class="pl-s">buf.build/haunt98/protoc-gen-validate:2686264610fc4ad4a9fcc932647e279d</span>
- <span class="pl-s">buf.build/haunt98/marshal-zap:2a593ca925134680a5820d3f13c1be5a</span>
<span class="pl-ent">breaking</span>:
<span class="pl-ent">use</span>:
- <span class="pl-s">FILE</span>
<span class="pl-ent">lint</span>:
<span class="pl-ent">use</span>:
- <span class="pl-s">DEFAULT</span></pre></div>
<p><code>buf.gen.yaml</code>:</p>
<div class="highlight highlight-source-yaml"><pre><span class="pl-ent">version</span>: <span class="pl-c1">v1</span>
<span class="pl-ent">plugins</span>:
- <span class="pl-ent">name</span>: <span class="pl-s">go</span>
<span class="pl-ent">out</span>: <span class="pl-s">pkg</span>
<span class="pl-ent">opt</span>:
- <span class="pl-s">plugins=grpc</span>
- <span class="pl-ent">name</span>: <span class="pl-s">grpc-gateway</span>
<span class="pl-ent">out</span>: <span class="pl-s">pkg</span>
<span class="pl-ent">opt</span>:
- <span class="pl-s">logtostderr=true</span>
- <span class="pl-ent">name</span>: <span class="pl-s">swagger</span>
<span class="pl-ent">out</span>: <span class="pl-s">.</span>
<span class="pl-ent">opt</span>:
- <span class="pl-s">logtostderr=true</span>
- <span class="pl-ent">name</span>: <span class="pl-s">validate</span>
<span class="pl-ent">out</span>: <span class="pl-s">pkg</span>
<span class="pl-ent">opt</span>:
- <span class="pl-s">lang=go</span>
- <span class="pl-ent">name</span>: <span class="pl-s">marshal-zap</span>
<span class="pl-ent">out</span>: <span class="pl-s">pkg</span></pre></div>
<p>Update <code>Makefile</code>:</p>
<div class="highlight highlight-source-makefile"><pre><span class="pl-en">gen</span>:
go install github.com/golang/protobuf/protoc-gen-go
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
@ -46,5 +99,36 @@
go install github.com/bufbuild/buf/cmd/buf@latest
buf mod update
buf format -w
buf generate</pre></div><p>Run <code>make gen</code> to have fun of course.<h2><a id=user-content-faq class=anchor aria-hidden=true href=#faq><span aria-hidden=true class="octicon octicon-link"></span></a>FAQ</h2><p>Remember <code>grpc-ecosystem/grpc-gateway</code>, <code>envoyproxy/protoc-gen-validate</code>, <code>kei2100/protoc-gen-marshal-zap</code> is optional, so feel free to delete if you don't use theme.<p>If use <code>vendor</code>:<ul><li>Replace <code>buf generate</code> with <code>buf generate --exclude-path vendor</code>.<li>Replace <code>buf format -w</code> with <code>buf format -w --exclude-path vendor</code>.</ul><p>If you use grpc-gateway:<ul><li>Replace <code>import "third_party/googleapis/google/api/annotations.proto";</code> with <code>import "google/api/annotations.proto";</code><li>Delete <code>security_definitions</code>, <code>security</code>, in <code>option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger)</code>.</ul><p>The last step is delete <code>prototool.yaml</code>.<p>If you are not migrate but start from scratch:<ul><li>Add <code>buf lint</code> to make sure your proto is good.<li>Add <code>buf breaking --against "https://your-grpc-repo-goes-here.git"</code> to make sure each time you update proto, you don't break backward compatibility.</ul><h2><a id=user-content-thanks class=anchor aria-hidden=true href=#thanks><span aria-hidden=true class="octicon octicon-link"></span></a>Thanks</h2><ul><li><a href=https://github.com/uber/prototool>uber/prototool</a><li><a href=https://github.com/bufbuild/buf>bufbuild/buf</a></ul><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
buf generate</pre></div>
<p>Run <code>make gen</code> to have fun of course.</p>
<h2><a id="user-content-faq" class="anchor" aria-hidden="true" href="#faq"><span aria-hidden="true" class="octicon octicon-link"></span></a>FAQ</h2>
<p>Remember <code>grpc-ecosystem/grpc-gateway</code>, <code>envoyproxy/protoc-gen-validate</code>, <code>kei2100/protoc-gen-marshal-zap</code> is optional, so feel free to delete if you don't use theme.</p>
<p>If use <code>vendor</code>:</p>
<ul>
<li>Replace <code>buf generate</code> with <code>buf generate --exclude-path vendor</code>.</li>
<li>Replace <code>buf format -w</code> with <code>buf format -w --exclude-path vendor</code>.</li>
</ul>
<p>If you use grpc-gateway:</p>
<ul>
<li>Replace <code>import "third_party/googleapis/google/api/annotations.proto";</code> with <code>import "google/api/annotations.proto";</code>
</li>
<li>Delete <code>security_definitions</code>, <code>security</code>, in <code>option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger)</code>.</li>
</ul>
<p>The last step is delete <code>prototool.yaml</code>.</p>
<p>If you are not migrate but start from scratch:</p>
<ul>
<li>Add <code>buf lint</code> to make sure your proto is good.</li>
<li>Add <code>buf breaking --against "https://your-grpc-repo-goes-here.git"</code> to make sure each time you update proto, you don't break backward compatibility.</li>
</ul>
<h2><a id="user-content-thanks" class="anchor" aria-hidden="true" href="#thanks"><span aria-hidden="true" class="octicon octicon-link"></span></a>Thanks</h2>
<ul>
<li><a href="https://github.com/uber/prototool">uber/prototool</a></li>
<li><a href="https://github.com/bufbuild/buf">bufbuild/buf</a></li>
</ul>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1,34 +1,92 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-experiment-go class=anchor aria-hidden=true href=#experiment-go><span aria-hidden=true class="octicon octicon-link"></span></a>Experiment Go</h1><p>There come a time when you need to experiment new things, new style, new approach.
So this post serves as it is named.<h1><a id=user-content-design-api-by-trimming-down-the-interfacestruct-or-whatever class=anchor aria-hidden=true href=#design-api-by-trimming-down-the-interfacestruct-or-whatever><span aria-hidden=true class="octicon octicon-link"></span></a>Design API by trimming down the interface/struct or whatever</h1><p>Instead of:<div class="highlight highlight-source-go"><pre><span class=pl-k>type</span> <span class=pl-smi>Client</span> <span class=pl-k>interface</span> {
<span class=pl-c1>GetUser</span>()
<span class=pl-c1>AddUser</span>()
<span class=pl-c1>GetAccount</span>()
<span class=pl-c1>RemoveAccount</span>()
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-experiment-go" class="anchor" aria-hidden="true" href="#experiment-go"><span aria-hidden="true" class="octicon octicon-link"></span></a>Experiment Go</h1>
<p>There come a time when you need to experiment new things, new style, new approach.
So this post serves as it is named.</p>
<h1><a id="user-content-design-api-by-trimming-down-the-interfacestruct-or-whatever" class="anchor" aria-hidden="true" href="#design-api-by-trimming-down-the-interfacestruct-or-whatever"><span aria-hidden="true" class="octicon octicon-link"></span></a>Design API by trimming down the interface/struct or whatever</h1>
<p>Instead of:</p>
<div class="highlight highlight-source-go"><pre><span class="pl-k">type</span> <span class="pl-smi">Client</span> <span class="pl-k">interface</span> {
<span class="pl-c1">GetUser</span>()
<span class="pl-c1">AddUser</span>()
<span class="pl-c1">GetAccount</span>()
<span class="pl-c1">RemoveAccount</span>()
}
<span class=pl-c>// c is Client</span>
<span class=pl-s1>c</span>.<span class=pl-en>GetUser</span>()
<span class=pl-s1>c</span>.<span class=pl-en>RemoveAccount</span>()</pre></div><p>Try:<div class="highlight highlight-source-go"><pre><span class=pl-k>type</span> <span class=pl-smi>Client</span> <span class=pl-k>struct</span> {
<span class=pl-c1>User</span> <span class=pl-smi>ClientUser</span>
<span class=pl-c1>Account</span> <span class=pl-smi>ClientAccount</span>
<span class="pl-c">// c is Client</span>
<span class="pl-s1">c</span>.<span class="pl-en">GetUser</span>()
<span class="pl-s1">c</span>.<span class="pl-en">RemoveAccount</span>()</pre></div>
<p>Try:</p>
<div class="highlight highlight-source-go"><pre><span class="pl-k">type</span> <span class="pl-smi">Client</span> <span class="pl-k">struct</span> {
<span class="pl-c1">User</span> <span class="pl-smi">ClientUser</span>
<span class="pl-c1">Account</span> <span class="pl-smi">ClientAccount</span>
}
<span class=pl-k>type</span> <span class=pl-smi>ClientUser</span> <span class=pl-k>interface</span> {
<span class=pl-c1>Get</span>()
<span class=pl-c1>Add</span>()
<span class="pl-k">type</span> <span class="pl-smi">ClientUser</span> <span class="pl-k">interface</span> {
<span class="pl-c1">Get</span>()
<span class="pl-c1">Add</span>()
}
<span class=pl-k>type</span> <span class=pl-smi>ClientAccount</span> <span class=pl-k>interface</span> {
<span class=pl-c1>Get</span>()
<span class=pl-c1>Remove</span>()
<span class="pl-k">type</span> <span class="pl-smi">ClientAccount</span> <span class="pl-k">interface</span> {
<span class="pl-c1">Get</span>()
<span class="pl-c1">Remove</span>()
}
<span class=pl-c>// c is Client</span>
<span class=pl-s1>c</span>.<span class=pl-c1>User</span>.<span class=pl-en>Get</span>()
<span class=pl-s1>c</span>.<span class=pl-c1>Account</span>.<span class=pl-en>Remove</span>()</pre></div><p>The difference is <code>c.GetUser()</code> -> <code>c.User.Get()</code>.<p>For example we have client which connect to bank.
<span class="pl-c">// c is Client</span>
<span class="pl-s1">c</span>.<span class="pl-c1">User</span>.<span class="pl-en">Get</span>()
<span class="pl-s1">c</span>.<span class="pl-c1">Account</span>.<span class="pl-en">Remove</span>()</pre></div>
<p>The difference is <code>c.GetUser()</code> -&gt; <code>c.User.Get()</code>.</p>
<p>For example we have client which connect to bank.
There are many functions like <code>GetUser</code>, <code>GetTransaction</code>, <code>VerifyAccount</code>, ...
So split big client to many children, each child handle single aspect, like user or transaction.<p>My concert is we replace an interface with a struct which contains multiple interfaces aka children.
I don't know if this is the right call.<p>This pattern is used by <a href=https://github.com/google/go-github>google/go-github</a>.<h2><a id=user-content-find-alternative-to-grpcgrpc-go class=anchor aria-hidden=true href=#find-alternative-to-grpcgrpc-go><span aria-hidden=true class="octicon octicon-link"></span></a>Find alternative to <a href=https://github.com/grpc/grpc-go>grpc/grpc-go</a></h2><p>Why?
So split big client to many children, each child handle single aspect, like user or transaction.</p>
<p>My concert is we replace an interface with a struct which contains multiple interfaces aka children.
I don't know if this is the right call.</p>
<p>This pattern is used by <a href="https://github.com/google/go-github">google/go-github</a>.</p>
<h2><a id="user-content-find-alternative-to-grpcgrpc-go" class="anchor" aria-hidden="true" href="#find-alternative-to-grpcgrpc-go"><span aria-hidden="true" class="octicon octicon-link"></span></a>Find alternative to <a href="https://github.com/grpc/grpc-go">grpc/grpc-go</a>
</h2>
<p>Why?
<a href="https://github.com/grpc/grpc-go/issues?q=is%3Aissue+compatibility+is%3Aclosed">See for yourself</a>.
Also read <a href=https://go.dev/blog/protobuf-apiv2 rel=nofollow>A new Go API for Protocol Buffers</a> to know why <code>v1.20.0</code> is <code>v2</code>.<p>Currently there are some:<ul><li><a href=https://github.com/bufbuild/connect-go>bufbuild/connect-go</a>. Comming from buf, trust worthy but need time to make it match feature parity with grpc-go.<li><a href=https://github.com/twitchtv/twirp>twitchtv/twirp</a><li><a href=https://github.com/storj/drpc>storj/drpc</a></ul><h1><a id=user-content-thanks class=anchor aria-hidden=true href=#thanks><span aria-hidden=true class="octicon octicon-link"></span></a>Thanks</h1><ul><li><a href=https://blog.gopheracademy.com/advent-2019/api-clients-humans/ rel=nofollow>API Clients for Humans</a></ul><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
Also read <a href="https://go.dev/blog/protobuf-apiv2" rel="nofollow">A new Go API for Protocol Buffers</a> to know why <code>v1.20.0</code> is <code>v2</code>.</p>
<p>Currently there are some:</p>
<ul>
<li>
<a href="https://github.com/bufbuild/connect-go">bufbuild/connect-go</a>. Comming from buf, trust worthy but need time to make it match feature parity with grpc-go.</li>
<li><a href="https://github.com/twitchtv/twirp">twitchtv/twirp</a></li>
<li><a href="https://github.com/storj/drpc">storj/drpc</a></li>
</ul>
<h1><a id="user-content-thanks" class="anchor" aria-hidden="true" href="#thanks"><span aria-hidden="true" class="octicon octicon-link"></span></a>Thanks</h1>
<ul>
<li><a href="https://blog.gopheracademy.com/advent-2019/api-clients-humans/" rel="nofollow">API Clients for Humans</a></li>
</ul>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1,27 +1,102 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-sql class=anchor aria-hidden=true href=#sql><span aria-hidden=true class="octicon octicon-link"></span></a>SQL</h1><p>Previously in my fresher software developer time, I rarely write SQL, I always use ORM to wrap SQL.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-sql" class="anchor" aria-hidden="true" href="#sql"><span aria-hidden="true" class="octicon octicon-link"></span></a>SQL</h1>
<p>Previously in my fresher software developer time, I rarely write SQL, I always use ORM to wrap SQL.
But time past and too much abstraction bites me.
So I decide to only write SQL from now as much as possible, no more ORM for me.
But if there is any cool ORM for Go, I guess I try.<p>This guide is not kind of guide which cover all cases.
Just my little tricks when I work with SQL.<h2><a id=user-content-stay-away-from-database-unique-id class=anchor aria-hidden=true href=#stay-away-from-database-unique-id><span aria-hidden=true class="octicon octicon-link"></span></a>Stay away from database unique id</h2><p>Use UUID instead.
If you can, and you should, choose UUID type which can be sortable.<h2><a id=user-content-stay-away-from-database-timestamp class=anchor aria-hidden=true href=#stay-away-from-database-timestamp><span aria-hidden=true class="octicon octicon-link"></span></a>Stay away from database timestamp</h2><p>Stay away from all kind of database timestamp (MySQL timestmap, SQLite timestamp, ...)
Just use int64 then pass the timestamp in service layer not database layer.<p>Why? Because time and date and location are too much complex to handle.
But if there is any cool ORM for Go, I guess I try.</p>
<p>This guide is not kind of guide which cover all cases.
Just my little tricks when I work with SQL.</p>
<h2><a id="user-content-stay-away-from-database-unique-id" class="anchor" aria-hidden="true" href="#stay-away-from-database-unique-id"><span aria-hidden="true" class="octicon octicon-link"></span></a>Stay away from database unique id</h2>
<p>Use UUID instead.
If you can, and you should, choose UUID type which can be sortable.</p>
<h2><a id="user-content-stay-away-from-database-timestamp" class="anchor" aria-hidden="true" href="#stay-away-from-database-timestamp"><span aria-hidden="true" class="octicon octicon-link"></span></a>Stay away from database timestamp</h2>
<p>Stay away from all kind of database timestamp (MySQL timestmap, SQLite timestamp, ...)
Just use int64 then pass the timestamp in service layer not database layer.</p>
<p>Why? Because time and date and location are too much complex to handle.
In my business, I use timestamp in milliseconds.
Then I save timestamp as int64 value to database.
Each time I get timestamp from database, I parse to time struct in Go with location or format I want.
No more hassle!<p>It looks like this:<div class="highlight highlight-text-adblock"><pre>[Business] time, data -&gt; convert to unix timestamp milliseconds -&gt; [Database] int64</pre></div><h2><a id=user-content-use-index class=anchor aria-hidden=true href=#use-index><span aria-hidden=true class="octicon octicon-link"></span></a>Use index!!!</h2><p>You should use index for faster query, but not too much.
No more hassle!</p>
<p>It looks like this:</p>
<div class="highlight highlight-text-adblock"><pre>[Business] time, data -&gt; convert to unix timestamp milliseconds -&gt; [Database] int64</pre></div>
<h2><a id="user-content-use-index" class="anchor" aria-hidden="true" href="#use-index"><span aria-hidden="true" class="octicon octicon-link"></span></a>Use index!!!</h2>
<p>You should use index for faster query, but not too much.
Don't create index for every fields in table.
Choose wisely!<p>For example, create index in MySQL:<div class="highlight highlight-source-sql"><pre><span class=pl-k>CREATE</span> <span class=pl-k>INDEX</span> `<span class=pl-en>idx_timestamp</span>`
<span class=pl-k>ON</span> <span class=pl-s><span class=pl-pds>`</span>user_upload<span class=pl-pds>`</span></span> (<span class=pl-s><span class=pl-pds>`</span>timestamp<span class=pl-pds>`</span></span>);</pre></div><h2><a id=user-content-be-careful-with-null class=anchor aria-hidden=true href=#be-careful-with-null><span aria-hidden=true class="octicon octicon-link"></span></a>Be careful with NULL</h2><p>If compare with field which can be NULL, remember to check NULL for safety.<div class="highlight highlight-source-sql"><pre><span class=pl-c><span class=pl-c>--</span> field_something can be NULL</span>
Choose wisely!</p>
<p>For example, create index in MySQL:</p>
<div class="highlight highlight-source-sql"><pre><span class="pl-k">CREATE</span> <span class="pl-k">INDEX</span> `<span class="pl-en">idx_timestamp</span>`
<span class="pl-k">ON</span> <span class="pl-s"><span class="pl-pds">`</span>user_upload<span class="pl-pds">`</span></span> (<span class="pl-s"><span class="pl-pds">`</span>timestamp<span class="pl-pds">`</span></span>);</pre></div>
<h2><a id="user-content-be-careful-with-null" class="anchor" aria-hidden="true" href="#be-careful-with-null"><span aria-hidden="true" class="octicon octicon-link"></span></a>Be careful with NULL</h2>
<p>If compare with field which can be NULL, remember to check NULL for safety.</p>
<div class="highlight highlight-source-sql"><pre><span class="pl-c"><span class="pl-c">--</span> field_something can be NULL</span>
<span class=pl-c><span class=pl-c>--</span> Bad</span>
<span class=pl-k>SELECT</span> <span class=pl-k>*</span>
<span class=pl-k>FROM</span> table
<span class=pl-k>WHERE</span> field_something <span class=pl-k>!=</span> <span class=pl-c1>1</span>
<span class="pl-c"><span class="pl-c">--</span> Bad</span>
<span class="pl-k">SELECT</span> <span class="pl-k">*</span>
<span class="pl-k">FROM</span> table
<span class="pl-k">WHERE</span> field_something <span class="pl-k">!=</span> <span class="pl-c1">1</span>
<span class=pl-c><span class=pl-c>--</span> Good</span>
<span class=pl-k>SELECT</span> <span class=pl-k>*</span>
<span class=pl-k>FROM</span> table
<span class=pl-k>WHERE</span> (field_something IS <span class=pl-k>NULL</span> <span class=pl-k>OR</span> field_something <span class=pl-k>!=</span> <span class=pl-c1>1</span>)</pre></div><p>Need clarify why this happpen? Idk :(<h2><a id=user-content-varchar-or-text class=anchor aria-hidden=true href=#varchar-or-text><span aria-hidden=true class="octicon octicon-link"></span></a>
<code>VARCHAR</code> or <code>TEXT</code></h2><p>Prefer <code>VARCHAR</code> if you need to query and of course use index, and make sure size of value will never hit the limit.
Prefer <code>TEXT</code> if you don't care, just want to store something.<h2><a id=user-content-be-super-careful-when-migrate-update-database-on-production-and-online class=anchor aria-hidden=true href=#be-super-careful-when-migrate-update-database-on-production-and-online><span aria-hidden=true class="octicon octicon-link"></span></a>Be super careful when migrate, update database on production and online!!!</h2><p>Plase read docs about online ddl operations before do anything online (keep database running the same time update it, for example create index, ...)<ul><li><a href=https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html rel=nofollow>For MySQL 5.7</a>, <a href=https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-limitations.html rel=nofollow>Limitations</a><li><a href=https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html rel=nofollow>For MySQL 8.0</a>, <a href=https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-limitations.html rel=nofollow>Limitations</a></ul><h2><a id=user-content-tools class=anchor aria-hidden=true href=#tools><span aria-hidden=true class="octicon octicon-link"></span></a>Tools</h2><ul><li>Use <a href=https://github.com/sqlfluff/sqlfluff>sqlfluff/sqlfluff</a> to check your SQL.<li>Use <a href=https://github.com/k1LoW/tbls>k1LoW/tbls</a> to grasp your database reality :)</ul><h2><a id=user-content-thanks class=anchor aria-hidden=true href=#thanks><span aria-hidden=true class="octicon octicon-link"></span></a>Thanks</h2><ul><li><a href=https://use-the-index-luke.com/ rel=nofollow>Use The Index, Luke</a><li><a href=https://www.foxhound.systems/blog/essential-elements-of-high-performance-sql-indexes/ rel=nofollow>Essential elements of high performance applications: SQL indexes</a><li><a href=https://architecturenotes.co/things-you-should-know-about-databases/ rel=nofollow>Things You Should Know About Databases</a></ul><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
<span class="pl-c"><span class="pl-c">--</span> Good</span>
<span class="pl-k">SELECT</span> <span class="pl-k">*</span>
<span class="pl-k">FROM</span> table
<span class="pl-k">WHERE</span> (field_something IS <span class="pl-k">NULL</span> <span class="pl-k">OR</span> field_something <span class="pl-k">!=</span> <span class="pl-c1">1</span>)</pre></div>
<p>Need clarify why this happpen? Idk :(</p>
<h2><a id="user-content-varchar-or-text" class="anchor" aria-hidden="true" href="#varchar-or-text"><span aria-hidden="true" class="octicon octicon-link"></span></a>
<code>VARCHAR</code> or <code>TEXT</code>
</h2>
<p>Prefer <code>VARCHAR</code> if you need to query and of course use index, and make sure size of value will never hit the limit.
Prefer <code>TEXT</code> if you don't care, just want to store something.</p>
<h2><a id="user-content-be-super-careful-when-migrate-update-database-on-production-and-online" class="anchor" aria-hidden="true" href="#be-super-careful-when-migrate-update-database-on-production-and-online"><span aria-hidden="true" class="octicon octicon-link"></span></a>Be super careful when migrate, update database on production and online!!!</h2>
<p>Plase read docs about online ddl operations before do anything online (keep database running the same time update it, for example create index, ...)</p>
<ul>
<li>
<a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html" rel="nofollow">For MySQL 5.7</a>, <a href="https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-limitations.html" rel="nofollow">Limitations</a>
</li>
<li>
<a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html" rel="nofollow">For MySQL 8.0</a>, <a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-limitations.html" rel="nofollow">Limitations</a>
</li>
</ul>
<h2><a id="user-content-tools" class="anchor" aria-hidden="true" href="#tools"><span aria-hidden="true" class="octicon octicon-link"></span></a>Tools</h2>
<ul>
<li>Use <a href="https://github.com/sqlfluff/sqlfluff">sqlfluff/sqlfluff</a> to check your SQL.</li>
<li>Use <a href="https://github.com/k1LoW/tbls">k1LoW/tbls</a> to grasp your database reality :)</li>
</ul>
<h2><a id="user-content-thanks" class="anchor" aria-hidden="true" href="#thanks"><span aria-hidden="true" class="octicon octicon-link"></span></a>Thanks</h2>
<ul>
<li><a href="https://use-the-index-luke.com/" rel="nofollow">Use The Index, Luke</a></li>
<li><a href="https://www.foxhound.systems/blog/essential-elements-of-high-performance-sql-indexes/" rel="nofollow">Essential elements of high performance applications: SQL indexes</a></li>
<li><a href="https://architecturenotes.co/things-you-should-know-about-databases/" rel="nofollow">Things You Should Know About Databases</a></li>
</ul>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1,17 +1,58 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-gitignore class=anchor aria-hidden=true href=#gitignore><span aria-hidden=true class="octicon octicon-link"></span></a>gitignore</h1><p>My quick check for <code>.gitignore</code>.<h2><a id=user-content-base class=anchor aria-hidden=true href=#base><span aria-hidden=true class="octicon octicon-link"></span></a>Base</h2><div class="highlight highlight-text-adblock"><pre><span class=pl-c># macOS</span>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-gitignore" class="anchor" aria-hidden="true" href="#gitignore"><span aria-hidden="true" class="octicon octicon-link"></span></a>gitignore</h1>
<p>My quick check for <code>.gitignore</code>.</p>
<h2><a id="user-content-base" class="anchor" aria-hidden="true" href="#base"><span aria-hidden="true" class="octicon octicon-link"></span></a>Base</h2>
<div class="highlight highlight-text-adblock"><pre><span class="pl-c"># macOS</span>
.DS_Store
<span class=pl-c># Windows</span>
<span class=pl-k>*</span>.exe
<span class="pl-c"># Windows</span>
<span class="pl-k">*</span>.exe
<span class=pl-c># IntelliJ</span>
<span class="pl-c"># IntelliJ</span>
.idea/
<span class=pl-c># VSCode</span>
.vscode/</pre></div><h2><a id=user-content-go class=anchor aria-hidden=true href=#go><span aria-hidden=true class="octicon octicon-link"></span></a>Go</h2><div class="highlight highlight-text-adblock"><pre><span class=pl-c># Go</span>
<span class=pl-c># Test coverage</span>
<span class="pl-c"># VSCode</span>
.vscode/</pre></div>
<h2><a id="user-content-go" class="anchor" aria-hidden="true" href="#go"><span aria-hidden="true" class="octicon octicon-link"></span></a>Go</h2>
<div class="highlight highlight-text-adblock"><pre><span class="pl-c"># Go</span>
<span class="pl-c"># Test coverage</span>
coverage.out
<span class=pl-c># Should ignore vendor</span>
vendor</pre></div><h2><a id=user-content-python class=anchor aria-hidden=true href=#python><span aria-hidden=true class="octicon octicon-link"></span></a>Python</h2><div class="highlight highlight-text-adblock"><pre>venv</pre></div><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
<span class="pl-c"># Should ignore vendor</span>
vendor</pre></div>
<h2><a id="user-content-python" class="anchor" aria-hidden="true" href="#python"><span aria-hidden="true" class="octicon octicon-link"></span></a>Python</h2>
<div class="highlight highlight-text-adblock"><pre>venv</pre></div>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1,47 +1,101 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-reload-config class=anchor aria-hidden=true href=#reload-config><span aria-hidden=true class="octicon octicon-link"></span></a>Reload config</h1><p>This serves as design draft of reload config system<div class="highlight highlight-source-wsd"><pre><span class=pl-k>@startuml</span> Reload config
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
<span class=pl-k>skinparam</span> <span class=pl-k>defaultFontName</span> <span class=pl-s>Iosevka Term SS08</span>
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-reload-config" class="anchor" aria-hidden="true" href="#reload-config"><span aria-hidden="true" class="octicon octicon-link"></span></a>Reload config</h1>
<p>This serves as design draft of reload config system</p>
<div class="highlight highlight-source-wsd"><pre><span class="pl-k">@startuml</span> Reload config
<span class=pl-k>participant</span> <span class=pl-c1>admin</span>
<span class=pl-k>participant</span> <span class=pl-c1>other_service</span>
<span class=pl-k>participant</span> <span class=pl-c1>config_service</span>
<span class=pl-k>participant</span> <span class=pl-c1>storage</span>
<span class="pl-k">skinparam</span> <span class="pl-k">defaultFontName</span> <span class="pl-s">Iosevka Term SS08</span>
<span class=pl-k>==</span> <span class=pl-s>Admin handle</span> <span class=pl-k>==</span>
<span class="pl-k">participant</span> <span class="pl-c1">admin</span>
<span class="pl-k">participant</span> <span class="pl-c1">other_service</span>
<span class="pl-k">participant</span> <span class="pl-c1">config_service</span>
<span class="pl-k">participant</span> <span class="pl-c1">storage</span>
<span class=pl-c1>admin</span> <span class=pl-k>-&gt;</span> <span class=pl-c1>config_service</span>: set/update/delete config
<span class="pl-k">==</span> <span class="pl-s">Admin handle</span> <span class="pl-k">==</span>
<span class=pl-c1>config_service</span> <span class=pl-k>-&gt;</span> <span class=pl-c1>storage</span>: set/update/delete config
<span class="pl-c1">admin</span> <span class="pl-k">-&gt;</span> <span class="pl-c1">config_service</span>: set/update/delete config
<span class=pl-k>==</span> <span class=pl-s>Other service handle</span> <span class=pl-k>==</span>
<span class="pl-c1">config_service</span> <span class="pl-k">-&gt;</span> <span class="pl-c1">storage</span>: set/update/delete config
<span class=pl-c1>other_service</span> <span class=pl-k>-&gt;</span> <span class=pl-c1>other_service</span>: init service
<span class="pl-k">==</span> <span class="pl-s">Other service handle</span> <span class="pl-k">==</span>
<span class=pl-k>activate</span> <span class=pl-c1>other_service</span>
<span class="pl-c1">other_service</span> <span class="pl-k">-&gt;</span> <span class="pl-c1">other_service</span>: init service
<span class=pl-c1>other_service</span> <span class=pl-k>-&gt;</span> <span class=pl-c1>storage</span>: make connection
<span class="pl-k">activate</span> <span class="pl-c1">other_service</span>
<span class=pl-k>loop</span>
<span class="pl-c1">other_service</span> <span class="pl-k">-&gt;</span> <span class="pl-c1">storage</span>: make connection
<span class=pl-c1>other_service</span> <span class=pl-k>-&gt;</span> <span class=pl-c1>storage</span>: listen on config change
<span class="pl-k">loop</span>
<span class=pl-c1>other_service</span> <span class=pl-k>-&gt;</span> <span class=pl-c1>other_service</span>: save config to memory
<span class="pl-c1">other_service</span> <span class="pl-k">-&gt;</span> <span class="pl-c1">storage</span>: listen on config change
<span class=pl-k>end</span>
<span class="pl-c1">other_service</span> <span class="pl-k">-&gt;</span> <span class="pl-c1">other_service</span>: save config to memory
<span class=pl-k>deactivate</span> <span class=pl-c1>other_service</span>
<span class="pl-k">end</span>
<span class=pl-c1>other_service</span> <span class=pl-k>-&gt;</span> <span class=pl-c1>other_service</span>: do business
<span class="pl-k">deactivate</span> <span class="pl-c1">other_service</span>
<span class=pl-k>activate</span> <span class=pl-c1>other_service</span>
<span class="pl-c1">other_service</span> <span class="pl-k">-&gt;</span> <span class="pl-c1">other_service</span>: do business
<span class=pl-c1>other_service</span> <span class=pl-k>-&gt;</span> <span class=pl-c1>other_service</span>: get config
<span class="pl-k">activate</span> <span class="pl-c1">other_service</span>
<span class=pl-c1>other_service</span> <span class=pl-k>-&gt;</span> <span class=pl-c1>other_service</span>: do other business
<span class="pl-c1">other_service</span> <span class="pl-k">-&gt;</span> <span class="pl-c1">other_service</span>: get config
<span class=pl-k>deactivate</span> <span class=pl-c1>other_service</span>
<span class="pl-c1">other_service</span> <span class="pl-k">-&gt;</span> <span class="pl-c1">other_service</span>: do other business
<span class=pl-k>@enduml</span></pre></div><p>Config storage can be any key value storage or database like etcd, Consul, mySQL, ...<p>If storage is key value storage, maybe there is API to listen on config change.
Otherwise we should create a loop to get all config from storage for some interval, for example each 5 minute.<p>Each <code>other_service</code> need to get config from its memory, not hit <code>storage</code>.
So there is some delay between upstream config (config in <code>storage</code>) and downstream config (config in <code>other_service</code>), but maybe we can forgive that delay (???).<p>Pros:<ul><li><p>Config can be dynamic, service does not need to restart to apply new config.<li><p>Each service only keep 1 connection to <code>storage</code> to listen to config change, not hit <code>storage</code> for each request.</ul><p>Cons:<ul><li>Each service has 1 more dependency, aka <code>storage</code>.<li>Need to handle fallback config, incase <code>storage</code> failure.<li>Delay between upstream/downstream config</ul><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
<span class="pl-k">deactivate</span> <span class="pl-c1">other_service</span>
<span class="pl-k">@enduml</span></pre></div>
<p>Config storage can be any key value storage or database like etcd, Consul, mySQL, ...</p>
<p>If storage is key value storage, maybe there is API to listen on config change.
Otherwise we should create a loop to get all config from storage for some interval, for example each 5 minute.</p>
<p>Each <code>other_service</code> need to get config from its memory, not hit <code>storage</code>.
So there is some delay between upstream config (config in <code>storage</code>) and downstream config (config in <code>other_service</code>), but maybe we can forgive that delay (???).</p>
<p>Pros:</p>
<ul>
<li>
<p>Config can be dynamic, service does not need to restart to apply new config.</p>
</li>
<li>
<p>Each service only keep 1 connection to <code>storage</code> to listen to config change, not hit <code>storage</code> for each request.</p>
</li>
</ul>
<p>Cons:</p>
<ul>
<li>Each service has 1 more dependency, aka <code>storage</code>.</li>
<li>Need to handle fallback config, incase <code>storage</code> failure.</li>
<li>Delay between upstream/downstream config</li>
</ul>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1,102 +1,278 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-install-arch-linux class=anchor aria-hidden=true href=#install-arch-linux><span aria-hidden=true class="octicon octicon-link"></span></a>Install Arch Linux</h1><p>Install Arch Linux is thing I always want to do for my laptop/PC since I had my laptop in ninth grade.<p>This is not a guide for everyone, this is just save for myself in a future and for anyone who want to walk in my shoes.<h2><a id=user-content-installation-guide class=anchor aria-hidden=true href=#installation-guide><span aria-hidden=true class="octicon octicon-link"></span></a><a href=https://wiki.archlinux.org/index.php/Installation_guide rel=nofollow>Installation guide</a></h2><h3><a id=user-content-pre-installation class=anchor aria-hidden=true href=#pre-installation><span aria-hidden=true class="octicon octicon-link"></span></a>Pre-installation</h3><p>Check disks carefully:<div class="highlight highlight-source-shell"><pre>lsblk</pre></div><p><a href=https://wiki.archlinux.org/index.php/USB_flash_installation_medium rel=nofollow>USB flash installation medium</a><h4><a id=user-content-verify-the-boot-mode class=anchor aria-hidden=true href=#verify-the-boot-mode><span aria-hidden=true class="octicon octicon-link"></span></a>Verify the boot mode</h4><p>Check UEFI mode:<div class="highlight highlight-source-shell"><pre>ls /sys/firmware/efi/efivars</pre></div><h4><a id=user-content-connect-to-the-internet class=anchor aria-hidden=true href=#connect-to-the-internet><span aria-hidden=true class="octicon octicon-link"></span></a>Connect to the internet</h4><p>For wifi, use <a href=https://wiki.archlinux.org/index.php/Iwd rel=nofollow>iwd</a>.<h4><a id=user-content-partition-the-disks class=anchor aria-hidden=true href=#partition-the-disks><span aria-hidden=true class="octicon octicon-link"></span></a>Partition the disks</h4><p><a href=https://wiki.archlinux.org/index.php/GPT_fdisk rel=nofollow>GPT fdisk</a>:<div class="highlight highlight-source-shell"><pre>cgdisk /dev/sdx</pre></div><p><a href=https://wiki.archlinux.org/index.php/Partitioning#Partition_scheme rel=nofollow>Partition scheme</a><p>UEFI/GPT layout:<table><thead><tr><th>Mount point<th>Partition<th>Partition type<th>Suggested size<tbody><tr><td><code>/mnt/efi</code><td><code>/dev/efi_system_partition</code><td>EFI System Partition<td>512 MiB<tr><td><code>/mnt/boot</code><td><code>/dev/extended_boot_loader_partition</code><td>Extended Boot Loader Partition<td>1 GiB<tr><td><code>/mnt</code><td><code>/dev/root_partition</code><td>Root Partition<td></table><p>BIOS/GPT layout:<table><thead><tr><th>Mount point<th>Partition<th>Partition type<th>Suggested size<tbody><tr><td><td><td>BIOS boot partition<td>1 MiB<tr><td><code>/mnt</code><td><code>/dev/root_partition</code><td>Root Partition<td></table><p>LVM:<div class="highlight highlight-source-shell"><pre><span class=pl-c><span class=pl-c>#</span> Create physical volumes</span>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-install-arch-linux" class="anchor" aria-hidden="true" href="#install-arch-linux"><span aria-hidden="true" class="octicon octicon-link"></span></a>Install Arch Linux</h1>
<p>Install Arch Linux is thing I always want to do for my laptop/PC since I had my laptop in ninth grade.</p>
<p>This is not a guide for everyone, this is just save for myself in a future and for anyone who want to walk in my shoes.</p>
<h2><a id="user-content-installation-guide" class="anchor" aria-hidden="true" href="#installation-guide"><span aria-hidden="true" class="octicon octicon-link"></span></a><a href="https://wiki.archlinux.org/index.php/Installation_guide" rel="nofollow">Installation guide</a></h2>
<h3><a id="user-content-pre-installation" class="anchor" aria-hidden="true" href="#pre-installation"><span aria-hidden="true" class="octicon octicon-link"></span></a>Pre-installation</h3>
<p>Check disks carefully:</p>
<div class="highlight highlight-source-shell"><pre>lsblk</pre></div>
<p><a href="https://wiki.archlinux.org/index.php/USB_flash_installation_medium" rel="nofollow">USB flash installation medium</a></p>
<h4><a id="user-content-verify-the-boot-mode" class="anchor" aria-hidden="true" href="#verify-the-boot-mode"><span aria-hidden="true" class="octicon octicon-link"></span></a>Verify the boot mode</h4>
<p>Check UEFI mode:</p>
<div class="highlight highlight-source-shell"><pre>ls /sys/firmware/efi/efivars</pre></div>
<h4><a id="user-content-connect-to-the-internet" class="anchor" aria-hidden="true" href="#connect-to-the-internet"><span aria-hidden="true" class="octicon octicon-link"></span></a>Connect to the internet</h4>
<p>For wifi, use <a href="https://wiki.archlinux.org/index.php/Iwd" rel="nofollow">iwd</a>.</p>
<h4><a id="user-content-partition-the-disks" class="anchor" aria-hidden="true" href="#partition-the-disks"><span aria-hidden="true" class="octicon octicon-link"></span></a>Partition the disks</h4>
<p><a href="https://wiki.archlinux.org/index.php/GPT_fdisk" rel="nofollow">GPT fdisk</a>:</p>
<div class="highlight highlight-source-shell"><pre>cgdisk /dev/sdx</pre></div>
<p><a href="https://wiki.archlinux.org/index.php/Partitioning#Partition_scheme" rel="nofollow">Partition scheme</a></p>
<p>UEFI/GPT layout:</p>
<table>
<thead>
<tr>
<th>Mount point</th>
<th>Partition</th>
<th>Partition type</th>
<th>Suggested size</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/mnt/efi</code></td>
<td><code>/dev/efi_system_partition</code></td>
<td>EFI System Partition</td>
<td>512 MiB</td>
</tr>
<tr>
<td><code>/mnt/boot</code></td>
<td><code>/dev/extended_boot_loader_partition</code></td>
<td>Extended Boot Loader Partition</td>
<td>1 GiB</td>
</tr>
<tr>
<td><code>/mnt</code></td>
<td><code>/dev/root_partition</code></td>
<td>Root Partition</td>
<td></td>
</tr>
</tbody>
</table>
<p>BIOS/GPT layout:</p>
<table>
<thead>
<tr>
<th>Mount point</th>
<th>Partition</th>
<th>Partition type</th>
<th>Suggested size</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td>BIOS boot partition</td>
<td>1 MiB</td>
</tr>
<tr>
<td><code>/mnt</code></td>
<td><code>/dev/root_partition</code></td>
<td>Root Partition</td>
<td></td>
</tr>
</tbody>
</table>
<p>LVM:</p>
<div class="highlight highlight-source-shell"><pre><span class="pl-c"><span class="pl-c">#</span> Create physical volumes</span>
pvcreate /dev/sdaX
<span class=pl-c><span class=pl-c>#</span> Create volume groups</span>
<span class="pl-c"><span class="pl-c">#</span> Create volume groups</span>
vgcreate RootGroup /dev/sdaX /dev/sdaY
<span class=pl-c><span class=pl-c>#</span> Create logical volumes</span>
lvcreate -l +100%FREE RootGroup -n rootvol</pre></div><p>Format:<div class="highlight highlight-source-shell"><pre><span class=pl-c><span class=pl-c>#</span> efi</span>
<span class="pl-c"><span class="pl-c">#</span> Create logical volumes</span>
lvcreate -l +100%FREE RootGroup -n rootvol</pre></div>
<p>Format:</p>
<div class="highlight highlight-source-shell"><pre><span class="pl-c"><span class="pl-c">#</span> efi</span>
mkfs.fat -F32 /dev/efi_system_partition
<span class=pl-c><span class=pl-c>#</span> boot</span>
<span class="pl-c"><span class="pl-c">#</span> boot</span>
mkfs.fat -F32 /dev/extended_boot_loader_partition
<span class=pl-c><span class=pl-c>#</span> root</span>
<span class="pl-c"><span class="pl-c">#</span> root</span>
mkfs.ext4 -L ROOT /dev/root_partition
<span class=pl-c><span class=pl-c>#</span> root with btrfs</span>
<span class="pl-c"><span class="pl-c">#</span> root with btrfs</span>
mkfs.btrfs -L ROOT /dev/root_partition
<span class=pl-c><span class=pl-c>#</span> root on lvm</span>
mkfs.ext4 /dev/RootGroup/rootvol</pre></div><p>Mount:<div class="highlight highlight-source-shell"><pre><span class=pl-c><span class=pl-c>#</span> root</span>
<span class="pl-c"><span class="pl-c">#</span> root on lvm</span>
mkfs.ext4 /dev/RootGroup/rootvol</pre></div>
<p>Mount:</p>
<div class="highlight highlight-source-shell"><pre><span class="pl-c"><span class="pl-c">#</span> root</span>
mount /dev/root_partition /mnt
<span class=pl-c><span class=pl-c>#</span> root with btrfs</span>
<span class="pl-c"><span class="pl-c">#</span> root with btrfs</span>
mount -o compress=zstd /dev/root_partition /mnt
<span class=pl-c><span class=pl-c>#</span> root on lvm</span>
<span class="pl-c"><span class="pl-c">#</span> root on lvm</span>
mount /dev/RootGroup/rootvol /mnt
<span class=pl-c><span class=pl-c>#</span> efi</span>
<span class="pl-c"><span class="pl-c">#</span> efi</span>
mount --mkdir /dev/efi_system_partition /mnt/efi
<span class=pl-c><span class=pl-c>#</span> boot</span>
mount --mkdir /dev/extended_boot_loader_partition /mnt/boot</pre></div><h3><a id=user-content-installation class=anchor aria-hidden=true href=#installation><span aria-hidden=true class="octicon octicon-link"></span></a>Installation</h3><div class="highlight highlight-source-shell"><pre>pacstrap -K /mnt base linux linux-firmware
<span class="pl-c"><span class="pl-c">#</span> boot</span>
mount --mkdir /dev/extended_boot_loader_partition /mnt/boot</pre></div>
<h3><a id="user-content-installation" class="anchor" aria-hidden="true" href="#installation"><span aria-hidden="true" class="octicon octicon-link"></span></a>Installation</h3>
<div class="highlight highlight-source-shell"><pre>pacstrap -K /mnt base linux linux-firmware
<span class=pl-c><span class=pl-c>#</span> AMD</span>
<span class="pl-c"><span class="pl-c">#</span> AMD</span>
pacstrap -K /mnt amd-ucode
<span class=pl-c><span class=pl-c>#</span> Intel</span>
<span class="pl-c"><span class="pl-c">#</span> Intel</span>
pacstrap -K /mnt intel-ucode
<span class=pl-c><span class=pl-c>#</span> Btrfs</span>
<span class="pl-c"><span class="pl-c">#</span> Btrfs</span>
pacstrap -K /mnt btrfs-progs
<span class=pl-c><span class=pl-c>#</span> LVM</span>
<span class="pl-c"><span class="pl-c">#</span> LVM</span>
pacstrap -K /mnt lvm2
<span class=pl-c><span class=pl-c>#</span> Text editor</span>
pacstrap -K /mnt neovim</pre></div><h3><a id=user-content-configure class=anchor aria-hidden=true href=#configure><span aria-hidden=true class="octicon octicon-link"></span></a>Configure</h3><h4><a id=user-content-fstab class=anchor aria-hidden=true href=#fstab><span aria-hidden=true class="octicon octicon-link"></span></a><a href=https://wiki.archlinux.org/index.php/Fstab rel=nofollow>fstab</a></h4><div class="highlight highlight-source-shell"><pre>genfstab -U /mnt <span class=pl-k>&gt;&gt;</span> /mnt/etc/fstab</pre></div><h4><a id=user-content-chroot class=anchor aria-hidden=true href=#chroot><span aria-hidden=true class="octicon octicon-link"></span></a>Chroot</h4><div class="highlight highlight-source-shell"><pre>arch-chroot /mnt</pre></div><h4><a id=user-content-time-zone class=anchor aria-hidden=true href=#time-zone><span aria-hidden=true class="octicon octicon-link"></span></a>Time zone</h4><div class="highlight highlight-source-shell"><pre>ln -sf /usr/share/zoneinfo/Region/City /etc/localtime
<span class="pl-c"><span class="pl-c">#</span> Text editor</span>
pacstrap -K /mnt neovim</pre></div>
<h3><a id="user-content-configure" class="anchor" aria-hidden="true" href="#configure"><span aria-hidden="true" class="octicon octicon-link"></span></a>Configure</h3>
<h4><a id="user-content-fstab" class="anchor" aria-hidden="true" href="#fstab"><span aria-hidden="true" class="octicon octicon-link"></span></a><a href="https://wiki.archlinux.org/index.php/Fstab" rel="nofollow">fstab</a></h4>
<div class="highlight highlight-source-shell"><pre>genfstab -U /mnt <span class="pl-k">&gt;&gt;</span> /mnt/etc/fstab</pre></div>
<h4><a id="user-content-chroot" class="anchor" aria-hidden="true" href="#chroot"><span aria-hidden="true" class="octicon octicon-link"></span></a>Chroot</h4>
<div class="highlight highlight-source-shell"><pre>arch-chroot /mnt</pre></div>
<h4><a id="user-content-time-zone" class="anchor" aria-hidden="true" href="#time-zone"><span aria-hidden="true" class="octicon octicon-link"></span></a>Time zone</h4>
<div class="highlight highlight-source-shell"><pre>ln -sf /usr/share/zoneinfo/Region/City /etc/localtime
hwclock --systohc</pre></div><h4><a id=user-content-localization class=anchor aria-hidden=true href=#localization><span aria-hidden=true class="octicon octicon-link"></span></a>Localization:</h4><p>Edit <code>/etc/locale.gen</code>:<div class="highlight highlight-text-adblock"><pre><span class=pl-c># Uncomment en_US.UTF-8 UTF-8</span></pre></div><p>Generate locales:<div class="highlight highlight-source-shell"><pre>locale-gen</pre></div><p>Edit <code>/etc/locale.conf</code>:<div class="highlight highlight-text-adblock"><pre>LANG=en_US.UTF-8</pre></div><h4><a id=user-content-network-configuration class=anchor aria-hidden=true href=#network-configuration><span aria-hidden=true class="octicon octicon-link"></span></a>Network configuration</h4><p>Edit <code>/etc/hostname</code>:<div class="highlight highlight-text-adblock"><pre>myhostname</pre></div><h4><a id=user-content-initramfs class=anchor aria-hidden=true href=#initramfs><span aria-hidden=true class="octicon octicon-link"></span></a>Initramfs</h4><p>Edit <code>/etc/mkinitcpio.conf</code>:<div class="highlight highlight-text-adblock"><pre><span class=pl-c># LVM</span>
<span class=pl-c># https://wiki.archlinux.org/title/Install_Arch_Linux_on_LVM#Adding_mkinitcpio_hooks</span>
hwclock --systohc</pre></div>
<h4><a id="user-content-localization" class="anchor" aria-hidden="true" href="#localization"><span aria-hidden="true" class="octicon octicon-link"></span></a>Localization:</h4>
<p>Edit <code>/etc/locale.gen</code>:</p>
<div class="highlight highlight-text-adblock"><pre><span class="pl-c"># Uncomment en_US.UTF-8 UTF-8</span></pre></div>
<p>Generate locales:</p>
<div class="highlight highlight-source-shell"><pre>locale-gen</pre></div>
<p>Edit <code>/etc/locale.conf</code>:</p>
<div class="highlight highlight-text-adblock"><pre>LANG=en_US.UTF-8</pre></div>
<h4><a id="user-content-network-configuration" class="anchor" aria-hidden="true" href="#network-configuration"><span aria-hidden="true" class="octicon octicon-link"></span></a>Network configuration</h4>
<p>Edit <code>/etc/hostname</code>:</p>
<div class="highlight highlight-text-adblock"><pre>myhostname</pre></div>
<h4><a id="user-content-initramfs" class="anchor" aria-hidden="true" href="#initramfs"><span aria-hidden="true" class="octicon octicon-link"></span></a>Initramfs</h4>
<p>Edit <code>/etc/mkinitcpio.conf</code>:</p>
<div class="highlight highlight-text-adblock"><pre><span class="pl-c"># LVM</span>
<span class="pl-c"># https://wiki.archlinux.org/title/Install_Arch_Linux_on_LVM#Adding_mkinitcpio_hooks</span>
HOOKS=(base udev ... block lvm2 filesystems)
<span class=pl-c># https://wiki.archlinux.org/title/mkinitcpio#Common_hooks</span>
<span class=pl-c># Replace udev with systemd</span></pre></div><div class="highlight highlight-source-shell"><pre>mkinitcpio -P</pre></div><h4><a id=user-content-root-password class=anchor aria-hidden=true href=#root-password><span aria-hidden=true class="octicon octicon-link"></span></a>Root password</h4><div class="highlight highlight-source-shell"><pre>passwd</pre></div><h4><a id=user-content-addition class=anchor aria-hidden=true href=#addition><span aria-hidden=true class="octicon octicon-link"></span></a>Addition</h4><div class="highlight highlight-source-shell"><pre><span class=pl-c><span class=pl-c>#</span> NetworkManager</span>
<span class="pl-c"># https://wiki.archlinux.org/title/mkinitcpio#Common_hooks</span>
<span class="pl-c"># Replace udev with systemd</span></pre></div>
<div class="highlight highlight-source-shell"><pre>mkinitcpio -P</pre></div>
<h4><a id="user-content-root-password" class="anchor" aria-hidden="true" href="#root-password"><span aria-hidden="true" class="octicon octicon-link"></span></a>Root password</h4>
<div class="highlight highlight-source-shell"><pre>passwd</pre></div>
<h4><a id="user-content-addition" class="anchor" aria-hidden="true" href="#addition"><span aria-hidden="true" class="octicon octicon-link"></span></a>Addition</h4>
<div class="highlight highlight-source-shell"><pre><span class="pl-c"><span class="pl-c">#</span> NetworkManager</span>
pacman -Syu networkmanager
systemctl <span class=pl-c1>enable</span> NetworkManager.service
systemctl <span class="pl-c1">enable</span> NetworkManager.service
<span class=pl-c><span class=pl-c>#</span> Bluetooth</span>
<span class="pl-c"><span class="pl-c">#</span> Bluetooth</span>
pacman -Syu bluez
systemctl <span class=pl-c1>enable</span> bluetooth.service
systemctl <span class="pl-c1">enable</span> bluetooth.service
<span class=pl-c><span class=pl-c>#</span> Clock</span>
timedatectl set-ntp <span class=pl-c1>true</span></pre></div><h4><a id=user-content-boot-loader class=anchor aria-hidden=true href=#boot-loader><span aria-hidden=true class="octicon octicon-link"></span></a>Boot loader</h4><p><a href=Applications/System/systemd-boot.md>systemd-boot</a><p><a href=https://wiki.archlinux.org/index.php/GRUB rel=nofollow>GRUB</a><h2><a id=user-content-general-recommendations class=anchor aria-hidden=true href=#general-recommendations><span aria-hidden=true class="octicon octicon-link"></span></a><a href=https://wiki.archlinux.org/index.php/General_recommendations rel=nofollow>General recommendations</a></h2><p>Always remember to check <strong>dependencies</strong> when install packages.<h3><a id=user-content-system-administration class=anchor aria-hidden=true href=#system-administration><span aria-hidden=true class="octicon octicon-link"></span></a>System administration</h3><p><a href=https://wiki.archlinux.org/index.php/sudo rel=nofollow>Sudo</a>:<div class="highlight highlight-source-shell"><pre>pacman -Syu sudo
<span class="pl-c"><span class="pl-c">#</span> Clock</span>
timedatectl set-ntp <span class="pl-c1">true</span></pre></div>
<h4><a id="user-content-boot-loader" class="anchor" aria-hidden="true" href="#boot-loader"><span aria-hidden="true" class="octicon octicon-link"></span></a>Boot loader</h4>
<p><a href="Applications/System/systemd-boot.md">systemd-boot</a></p>
<p><a href="https://wiki.archlinux.org/index.php/GRUB" rel="nofollow">GRUB</a></p>
<h2><a id="user-content-general-recommendations" class="anchor" aria-hidden="true" href="#general-recommendations"><span aria-hidden="true" class="octicon octicon-link"></span></a><a href="https://wiki.archlinux.org/index.php/General_recommendations" rel="nofollow">General recommendations</a></h2>
<p>Always remember to check <strong>dependencies</strong> when install packages.</p>
<h3><a id="user-content-system-administration" class="anchor" aria-hidden="true" href="#system-administration"><span aria-hidden="true" class="octicon octicon-link"></span></a>System administration</h3>
<p><a href="https://wiki.archlinux.org/index.php/sudo" rel="nofollow">Sudo</a>:</p>
<div class="highlight highlight-source-shell"><pre>pacman -Syu sudo
EDITOR=nvim visudo
<span class=pl-c><span class=pl-c>#</span> Uncomment group wheel</span>
<span class="pl-c"><span class="pl-c">#</span> Uncomment group wheel</span>
<span class=pl-c><span class=pl-c>#</span> Add user if don't want to use systemd-homed</span>
useradd -m -G wheel -c <span class=pl-s><span class=pl-pds>"</span>The Joker<span class=pl-pds>"</span></span> joker
<span class="pl-c"><span class="pl-c">#</span> Add user if don't want to use systemd-homed</span>
useradd -m -G wheel -c <span class="pl-s"><span class="pl-pds">"</span>The Joker<span class="pl-pds">"</span></span> joker
<span class=pl-c><span class=pl-c>#</span> Or using zsh</span>
useradd -m -G wheel -s /usr/bin/zsh -c <span class=pl-s><span class=pl-pds>"</span>The Joker<span class=pl-pds>"</span></span> joker
<span class="pl-c"><span class="pl-c">#</span> Or using zsh</span>
useradd -m -G wheel -s /usr/bin/zsh -c <span class="pl-s"><span class="pl-pds">"</span>The Joker<span class="pl-pds">"</span></span> joker
<span class=pl-c><span class=pl-c>#</span> Set password</span>
passwd joker</pre></div><p><a href=https://wiki.archlinux.org/index.php/Systemd-homed rel=nofollow>systemd-homed (WIP)</a>:<div class="highlight highlight-source-shell"><pre>systemctl <span class=pl-c1>enable</span> systemd-homed.service
<span class="pl-c"><span class="pl-c">#</span> Set password</span>
passwd joker</pre></div>
<p><a href="https://wiki.archlinux.org/index.php/Systemd-homed" rel="nofollow">systemd-homed (WIP)</a>:</p>
<div class="highlight highlight-source-shell"><pre>systemctl <span class="pl-c1">enable</span> systemd-homed.service
homectl create joker --real-name=<span class=pl-s><span class=pl-pds>"</span>The Joker<span class=pl-pds>"</span></span> --member-of=wheel
homectl create joker --real-name=<span class="pl-s"><span class="pl-pds">"</span>The Joker<span class="pl-pds">"</span></span> --member-of=wheel
<span class=pl-c><span class=pl-c>#</span> Using zsh</span>
homectl update joker --shell=/usr/bin/zsh</pre></div><p><strong>Note</strong>:
<span class="pl-c"><span class="pl-c">#</span> Using zsh</span>
homectl update joker --shell=/usr/bin/zsh</pre></div>
<p><strong>Note</strong>:
Can not run <code>homectl</code> when install Arch Linux.
Should run on the first boot.<h3><a id=user-content-desktop-environment class=anchor aria-hidden=true href=#desktop-environment><span aria-hidden=true class="octicon octicon-link"></span></a>Desktop Environment</h3><p>Install <a href=https://wiki.archlinux.org/index.php/Xorg rel=nofollow>Xorg</a>:<div class="highlight highlight-source-shell"><pre>pacman -Syu xorg-server</pre></div><h4><a id=user-content-gnome class=anchor aria-hidden=true href=#gnome><span aria-hidden=true class="octicon octicon-link"></span></a><a href=https://wiki.archlinux.org/index.php/GNOME rel=nofollow>GNOME</a></h4><div class="highlight highlight-source-shell"><pre>pacman -Syu gnome-shell \
Should run on the first boot.</p>
<h3><a id="user-content-desktop-environment" class="anchor" aria-hidden="true" href="#desktop-environment"><span aria-hidden="true" class="octicon octicon-link"></span></a>Desktop Environment</h3>
<p>Install <a href="https://wiki.archlinux.org/index.php/Xorg" rel="nofollow">Xorg</a>:</p>
<div class="highlight highlight-source-shell"><pre>pacman -Syu xorg-server</pre></div>
<h4><a id="user-content-gnome" class="anchor" aria-hidden="true" href="#gnome"><span aria-hidden="true" class="octicon octicon-link"></span></a><a href="https://wiki.archlinux.org/index.php/GNOME" rel="nofollow">GNOME</a></h4>
<div class="highlight highlight-source-shell"><pre>pacman -Syu gnome-shell \
gnome-control-center gnome-system-monitor \
gnome-tweaks gnome-backgrounds gnome-screenshot gnome-keyring gnome-logs \
gnome-console gnome-text-editor \
nautilus xdg-user-dirs-gtk file-roller evince eog
<span class=pl-c><span class=pl-c>#</span> Login manager</span>
<span class="pl-c"><span class="pl-c">#</span> Login manager</span>
pacman -Syu gdm
systemctl <span class=pl-c1>enable</span> gdm.service</pre></div><h4><a id=user-content-kde-wip class=anchor aria-hidden=true href=#kde-wip><span aria-hidden=true class="octicon octicon-link"></span></a><a href=https://wiki.archlinux.org/title/KDE rel=nofollow>KDE (WIP)</a></h4><div class="highlight highlight-source-shell"><pre>pacman -Syu plasma-meta \
systemctl <span class="pl-c1">enable</span> gdm.service</pre></div>
<h4><a id="user-content-kde-wip" class="anchor" aria-hidden="true" href="#kde-wip"><span aria-hidden="true" class="octicon octicon-link"></span></a><a href="https://wiki.archlinux.org/title/KDE" rel="nofollow">KDE (WIP)</a></h4>
<div class="highlight highlight-source-shell"><pre>pacman -Syu plasma-meta \
kde-system-meta
<span class=pl-c><span class=pl-c>#</span> Login manager</span>
<span class="pl-c"><span class="pl-c">#</span> Login manager</span>
pacman -Syu sddm
systemctl <span class=pl-c1>enable</span> sddm.service</pre></div><h2><a id=user-content-list-of-applications class=anchor aria-hidden=true href=#list-of-applications><span aria-hidden=true class="octicon octicon-link"></span></a><a href=https://wiki.archlinux.org/index.php/List_of_applications rel=nofollow>List of applications</a></h2><h3><a id=user-content-pacman class=anchor aria-hidden=true href=#pacman><span aria-hidden=true class="octicon octicon-link"></span></a><a href=https://wiki.archlinux.org/index.php/pacman rel=nofollow>pacman</a></h3><p>Uncomment in <code>/etc/pacman.conf</code>:<div class="highlight highlight-text-adblock"><pre><span class=pl-c># Misc options</span>
systemctl <span class="pl-c1">enable</span> sddm.service</pre></div>
<h2><a id="user-content-list-of-applications" class="anchor" aria-hidden="true" href="#list-of-applications"><span aria-hidden="true" class="octicon octicon-link"></span></a><a href="https://wiki.archlinux.org/index.php/List_of_applications" rel="nofollow">List of applications</a></h2>
<h3><a id="user-content-pacman" class="anchor" aria-hidden="true" href="#pacman"><span aria-hidden="true" class="octicon octicon-link"></span></a><a href="https://wiki.archlinux.org/index.php/pacman" rel="nofollow">pacman</a></h3>
<p>Uncomment in <code>/etc/pacman.conf</code>:</p>
<div class="highlight highlight-text-adblock"><pre><span class="pl-c"># Misc options</span>
Color
ParallelDownloads</pre></div><h3><a id=user-content-pipewire-wip class=anchor aria-hidden=true href=#pipewire-wip><span aria-hidden=true class="octicon octicon-link"></span></a><a href=https://wiki.archlinux.org/title/PipeWire rel=nofollow>Pipewire (WIP)</a></h3><div class="highlight highlight-source-shell"><pre>pacman -Syu pipewire wireplumber \
ParallelDownloads</pre></div>
<h3><a id="user-content-pipewire-wip" class="anchor" aria-hidden="true" href="#pipewire-wip"><span aria-hidden="true" class="octicon octicon-link"></span></a><a href="https://wiki.archlinux.org/title/PipeWire" rel="nofollow">Pipewire (WIP)</a></h3>
<div class="highlight highlight-source-shell"><pre>pacman -Syu pipewire wireplumber \
pipewire-alsa pipewire-pulse \
gst-plugin-pipewire pipewire-v4l2</pre></div><h3><a id=user-content-flatpak-wip class=anchor aria-hidden=true href=#flatpak-wip><span aria-hidden=true class="octicon octicon-link"></span></a><a href=https://wiki.archlinux.org/title/Flatpak rel=nofollow>Flatpak (WIP)</a></h3><div class="highlight highlight-source-shell"><pre>pacman -Syu flatpak</pre></div><h2><a id=user-content-improving-performance class=anchor aria-hidden=true href=#improving-performance><span aria-hidden=true class="octicon octicon-link"></span></a><a href=https://wiki.archlinux.org/index.php/improving_performance rel=nofollow>Improving performance</a></h2><p><a href=https://wiki.archlinux.org/index.php/swap#Swap_file rel=nofollow>https://wiki.archlinux.org/index.php/swap#Swap_file</a><p><a href=https://wiki.archlinux.org/index.php/swap#Swappiness rel=nofollow>https://wiki.archlinux.org/index.php/swap#Swappiness</a><p><a href=https://wiki.archlinux.org/index.php/Systemd/Journal#Journal_size_limit rel=nofollow>https://wiki.archlinux.org/index.php/Systemd/Journal#Journal_size_limit</a><p><a href=https://wiki.archlinux.org/index.php/Core_dump#Disabling_automatic_core_dumps rel=nofollow>https://wiki.archlinux.org/index.php/Core_dump#Disabling_automatic_core_dumps</a><p><a href=https://wiki.archlinux.org/index.php/Solid_state_drive#Periodic_TRIM rel=nofollow>https://wiki.archlinux.org/index.php/Solid_state_drive#Periodic_TRIM</a><p><a href=https://wiki.archlinux.org/index.php/Silent_boot rel=nofollow>https://wiki.archlinux.org/index.php/Silent_boot</a><p><a href=https://wiki.archlinux.org/title/Improving_performance#Watchdogs rel=nofollow>https://wiki.archlinux.org/title/Improving_performance#Watchdogs</a><p><a href=https://wiki.archlinux.org/title/PRIME rel=nofollow>https://wiki.archlinux.org/title/PRIME</a><h2><a id=user-content-in-the-end class=anchor aria-hidden=true href=#in-the-end><span aria-hidden=true class="octicon octicon-link"></span></a>In the end</h2><p>This guide is updated regularly I promise.</p><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
gst-plugin-pipewire pipewire-v4l2</pre></div>
<h3><a id="user-content-flatpak-wip" class="anchor" aria-hidden="true" href="#flatpak-wip"><span aria-hidden="true" class="octicon octicon-link"></span></a><a href="https://wiki.archlinux.org/title/Flatpak" rel="nofollow">Flatpak (WIP)</a></h3>
<div class="highlight highlight-source-shell"><pre>pacman -Syu flatpak</pre></div>
<h2><a id="user-content-improving-performance" class="anchor" aria-hidden="true" href="#improving-performance"><span aria-hidden="true" class="octicon octicon-link"></span></a><a href="https://wiki.archlinux.org/index.php/improving_performance" rel="nofollow">Improving performance</a></h2>
<p><a href="https://wiki.archlinux.org/index.php/swap#Swap_file" rel="nofollow">https://wiki.archlinux.org/index.php/swap#Swap_file</a></p>
<p><a href="https://wiki.archlinux.org/index.php/swap#Swappiness" rel="nofollow">https://wiki.archlinux.org/index.php/swap#Swappiness</a></p>
<p><a href="https://wiki.archlinux.org/index.php/Systemd/Journal#Journal_size_limit" rel="nofollow">https://wiki.archlinux.org/index.php/Systemd/Journal#Journal_size_limit</a></p>
<p><a href="https://wiki.archlinux.org/index.php/Core_dump#Disabling_automatic_core_dumps" rel="nofollow">https://wiki.archlinux.org/index.php/Core_dump#Disabling_automatic_core_dumps</a></p>
<p><a href="https://wiki.archlinux.org/index.php/Solid_state_drive#Periodic_TRIM" rel="nofollow">https://wiki.archlinux.org/index.php/Solid_state_drive#Periodic_TRIM</a></p>
<p><a href="https://wiki.archlinux.org/index.php/Silent_boot" rel="nofollow">https://wiki.archlinux.org/index.php/Silent_boot</a></p>
<p><a href="https://wiki.archlinux.org/title/Improving_performance#Watchdogs" rel="nofollow">https://wiki.archlinux.org/title/Improving_performance#Watchdogs</a></p>
<p><a href="https://wiki.archlinux.org/title/PRIME" rel="nofollow">https://wiki.archlinux.org/title/PRIME</a></p>
<h2><a id="user-content-in-the-end" class="anchor" aria-hidden="true" href="#in-the-end"><span aria-hidden="true" class="octicon octicon-link"></span></a>In the end</h2>
<p>This guide is updated regularly I promise.</p>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1,4 +1,39 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-integration-go-grpc-with-buf class=anchor aria-hidden=true href=#integration-go-grpc-with-buf><span aria-hidden=true class="octicon octicon-link"></span></a>Integration Go gRPC with Buf</h1><p>There are 2 questions here.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-integration-go-grpc-with-buf" class="anchor" aria-hidden="true" href="#integration-go-grpc-with-buf"><span aria-hidden="true" class="octicon octicon-link"></span></a>Integration Go gRPC with Buf</h1>
<p>There are 2 questions here.
What is Buf?
And why is Buf?</p><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
And why is Buf?</p>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1,203 +1,268 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-speed-up-writing-go-test-asap class=anchor aria-hidden=true href=#speed-up-writing-go-test-asap><span aria-hidden=true class="octicon octicon-link"></span></a>Speed up writing Go test ASAP</h1><p>Imagine your project currently have 0% unit test code coverage.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-speed-up-writing-go-test-asap" class="anchor" aria-hidden="true" href="#speed-up-writing-go-test-asap"><span aria-hidden="true" class="octicon octicon-link"></span></a>Speed up writing Go test ASAP</h1>
<p>Imagine your project currently have 0% unit test code coverage.
And your boss keep pushing it to 80% or even 90%?
What do you do?
Give up?<p>What if I tell you there is a way?
Not entirely cheating but ... you know, there is always trade off.<p>If your purpose is to test carefully all path, check if all return is correctly.
Give up?</p>
<p>What if I tell you there is a way?
Not entirely cheating but ... you know, there is always trade off.</p>
<p>If your purpose is to test carefully all path, check if all return is correctly.
Sadly this post is not for you, I guess.
If you only want good number on test coverage, with minimum effort as possible, I hope this will show you some idea you can use :)<p>In my opinion, unit test is not that important (like must must have).
If you only want good number on test coverage, with minimum effort as possible, I hope this will show you some idea you can use :)</p>
<p>In my opinion, unit test is not that important (like must must have).
It's just make sure your code is running excatly as you intent it to be.
If you don't think about edge case before, unit test won't help you.<h2><a id=user-content-first-rewrite-the-impossible-to-test-out class=anchor aria-hidden=true href=#first-rewrite-the-impossible-to-test-out><span aria-hidden=true class="octicon octicon-link"></span></a>First, rewrite the impossible (to test) out</h2><p>When I learn programming, I encounter very interesting idea, which become mainly my mindset when I dev later.
If you don't think about edge case before, unit test won't help you.</p>
<h2><a id="user-content-first-rewrite-the-impossible-to-test-out" class="anchor" aria-hidden="true" href="#first-rewrite-the-impossible-to-test-out"><span aria-hidden="true" class="octicon octicon-link"></span></a>First, rewrite the impossible (to test) out</h2>
<p>When I learn programming, I encounter very interesting idea, which become mainly my mindset when I dev later.
I don't recall it clearly, kinda like: "Don't just fix bugs, rewrite it so that kind of bugs will not appear again".
So in our context, there is some thing we hardly or can not write test in Go.
My suggestion is don't use that thing.<p>In my experience, I can list a few here:<ul><li>Read config each time call func (<code>viper.Get...</code>). You can and you should init all config when project starts.<li>Not use Dependency Injection (DI). There are too many posts in Internet tell you how to do DI properly.<li>Use global var (Except global var <code>Err...</code>). You should move all global var to fields inside some struct.</ul><h2><a id=user-content-let-the-fun-writing-test-begin class=anchor aria-hidden=true href=#let-the-fun-writing-test-begin><span aria-hidden=true class="octicon octicon-link"></span></a>Let the fun (writing test) begin</h2><p>If you code Go long enough, you know table driven tests and how is that so useful.
My suggestion is don't use that thing.</p>
<p>In my experience, I can list a few here:</p>
<ul>
<li>Read config each time call func (<code>viper.Get...</code>). You can and you should init all config when project starts.</li>
<li>Not use Dependency Injection (DI). There are too many posts in Internet tell you how to do DI properly.</li>
<li>Use global var (Except global var <code>Err...</code>). You should move all global var to fields inside some struct.</li>
</ul>
<h2><a id="user-content-let-the-fun-writing-test-begin" class="anchor" aria-hidden="true" href="#let-the-fun-writing-test-begin"><span aria-hidden="true" class="octicon octicon-link"></span></a>Let the fun (writing test) begin</h2>
<p>If you code Go long enough, you know table driven tests and how is that so useful.
You set up test data, then you test.
Somewhere in the future, you change the func, then you need to update test data, then you good!<p>In simple case, your func only have 2 or 3 inputs so table drive tests is still looking good.
But real world is ugly (maybe not, idk I'm just too young in this industry). Your func can have 5 or 10 inputs, also your func call many third party services.<p>Imagine having below func to upload image:<div class="highlight highlight-source-go"><pre><span class=pl-k>type</span> <span class=pl-smi>service</span> <span class=pl-k>struct</span> {
<span class=pl-c1>db</span> <span class=pl-smi>DB</span>
<span class=pl-c1>redis</span> <span class=pl-smi>Redis</span>
<span class=pl-c1>minio</span> <span class=pl-smi>MinIO</span>
<span class=pl-c1>logService</span> <span class=pl-smi>LogService</span>
<span class=pl-c1>verifyService</span> <span class=pl-smi>VerifyService</span>
Somewhere in the future, you change the func, then you need to update test data, then you good!</p>
<p>In simple case, your func only have 2 or 3 inputs so table drive tests is still looking good.
But real world is ugly (maybe not, idk I'm just too young in this industry). Your func can have 5 or 10 inputs, also your func call many third party services.</p>
<p>Imagine having below func to upload image:</p>
<div class="highlight highlight-source-go"><pre><span class="pl-k">type</span> <span class="pl-smi">service</span> <span class="pl-k">struct</span> {
<span class="pl-c1">db</span> <span class="pl-smi">DB</span>
<span class="pl-c1">redis</span> <span class="pl-smi">Redis</span>
<span class="pl-c1">minio</span> <span class="pl-smi">MinIO</span>
<span class="pl-c1">logService</span> <span class="pl-smi">LogService</span>
<span class="pl-c1">verifyService</span> <span class="pl-smi">VerifyService</span>
}
<span class=pl-k>func</span> (<span class=pl-s1>s</span> <span class=pl-c1>*</span><span class=pl-smi>service</span>) <span class=pl-en>Upload</span>(<span class=pl-s1>ctx</span> context.<span class=pl-smi>Context</span>, <span class=pl-s1>req</span> <span class=pl-smi>Request</span>) <span class=pl-smi>error</span> {
<span class=pl-c>// I simplify by omitting the response, only care error for now</span>
<span class=pl-k>if</span> <span class=pl-s1>err</span> <span class=pl-c1>:=</span> <span class=pl-s1>s</span>.<span class=pl-c1>verifyService</span>.<span class=pl-en>Verify</span>(<span class=pl-s1>req</span>); <span class=pl-s1>err</span> <span class=pl-c1>!=</span> <span class=pl-c1>nil</span> {
<span class=pl-k>return</span> <span class=pl-s1>err</span>
<span class="pl-k">func</span> (<span class="pl-s1">s</span> <span class="pl-c1">*</span><span class="pl-smi">service</span>) <span class="pl-en">Upload</span>(<span class="pl-s1">ctx</span> context.<span class="pl-smi">Context</span>, <span class="pl-s1">req</span> <span class="pl-smi">Request</span>) <span class="pl-smi">error</span> {
<span class="pl-c">// I simplify by omitting the response, only care error for now</span>
<span class="pl-k">if</span> <span class="pl-s1">err</span> <span class="pl-c1">:=</span> <span class="pl-s1">s</span>.<span class="pl-c1">verifyService</span>.<span class="pl-en">Verify</span>(<span class="pl-s1">req</span>); <span class="pl-s1">err</span> <span class="pl-c1">!=</span> <span class="pl-c1">nil</span> {
<span class="pl-k">return</span> <span class="pl-s1">err</span>
}
<span class=pl-k>if</span> <span class=pl-s1>err</span> <span class=pl-c1>:=</span> <span class=pl-s1>s</span>.<span class=pl-c1>minio</span>.<span class=pl-en>Put</span>(<span class=pl-s1>req</span>); <span class=pl-s1>err</span> <span class=pl-c1>!=</span> <span class=pl-c1>nil</span> {
<span class=pl-k>return</span> <span class=pl-s1>err</span>
<span class="pl-k">if</span> <span class="pl-s1">err</span> <span class="pl-c1">:=</span> <span class="pl-s1">s</span>.<span class="pl-c1">minio</span>.<span class="pl-en">Put</span>(<span class="pl-s1">req</span>); <span class="pl-s1">err</span> <span class="pl-c1">!=</span> <span class="pl-c1">nil</span> {
<span class="pl-k">return</span> <span class="pl-s1">err</span>
}
<span class=pl-k>if</span> <span class=pl-s1>err</span> <span class=pl-c1>:=</span> <span class=pl-s1>s</span>.<span class=pl-c1>redis</span>.<span class=pl-en>Set</span>(<span class=pl-s1>req</span>); <span class=pl-s1>err</span> <span class=pl-c1>!=</span> <span class=pl-c1>nil</span> {
<span class=pl-k>return</span> <span class=pl-s1>err</span>
<span class="pl-k">if</span> <span class="pl-s1">err</span> <span class="pl-c1">:=</span> <span class="pl-s1">s</span>.<span class="pl-c1">redis</span>.<span class="pl-en">Set</span>(<span class="pl-s1">req</span>); <span class="pl-s1">err</span> <span class="pl-c1">!=</span> <span class="pl-c1">nil</span> {
<span class="pl-k">return</span> <span class="pl-s1">err</span>
}
<span class=pl-k>if</span> <span class=pl-s1>err</span> <span class=pl-c1>:=</span> <span class=pl-s1>s</span>.<span class=pl-c1>db</span>.<span class=pl-en>Save</span>(<span class=pl-s1>req</span>); <span class=pl-s1>err</span> <span class=pl-c1>!=</span> <span class=pl-c1>nil</span> {
<span class=pl-k>return</span> <span class=pl-s1>err</span>
<span class="pl-k">if</span> <span class="pl-s1">err</span> <span class="pl-c1">:=</span> <span class="pl-s1">s</span>.<span class="pl-c1">db</span>.<span class="pl-en">Save</span>(<span class="pl-s1">req</span>); <span class="pl-s1">err</span> <span class="pl-c1">!=</span> <span class="pl-c1">nil</span> {
<span class="pl-k">return</span> <span class="pl-s1">err</span>
}
<span class=pl-k>if</span> <span class=pl-s1>err</span> <span class=pl-c1>:=</span> <span class=pl-s1>s</span>.<span class=pl-c1>logService</span>.<span class=pl-en>Save</span>(<span class=pl-s1>req</span>); <span class=pl-s1>err</span> <span class=pl-c1>!=</span> <span class=pl-c1>nil</span> {
<span class=pl-k>return</span> <span class=pl-s1>err</span>
<span class="pl-k">if</span> <span class="pl-s1">err</span> <span class="pl-c1">:=</span> <span class="pl-s1">s</span>.<span class="pl-c1">logService</span>.<span class="pl-en">Save</span>(<span class="pl-s1">req</span>); <span class="pl-s1">err</span> <span class="pl-c1">!=</span> <span class="pl-c1">nil</span> {
<span class="pl-k">return</span> <span class="pl-s1">err</span>
}
<span class=pl-k>return</span> <span class=pl-c1>nil</span>
}</pre></div><p>With table driven test and thanks to <a href=https://github.com/stretchr/testify>stretchr/testify</a>, I usually write like this:<div class="highlight highlight-source-go"><pre><span class=pl-k>type</span> <span class=pl-smi>ServiceSuite</span> <span class=pl-k>struct</span> {
suite.<span class=pl-smi>Suite</span>
<span class="pl-k">return</span> <span class="pl-c1">nil</span>
}</pre></div>
<p>With table driven test and thanks to <a href="https://github.com/stretchr/testify">stretchr/testify</a>, I usually write like this:</p>
<div class="highlight highlight-source-go"><pre><span class="pl-k">type</span> <span class="pl-smi">ServiceSuite</span> <span class="pl-k">struct</span> {
suite.<span class="pl-smi">Suite</span>
<span class=pl-c1>db</span> <span class=pl-smi>DBMock</span>
<span class=pl-c1>redis</span> <span class=pl-smi>RedisMock</span>
<span class=pl-c1>minio</span> <span class=pl-smi>MinIOMock</span>
<span class=pl-c1>logService</span> <span class=pl-smi>LogServiceMock</span>
<span class=pl-c1>verifyService</span> <span class=pl-smi>VerifyServiceMock</span>
<span class="pl-c1">db</span> <span class="pl-smi">DBMock</span>
<span class="pl-c1">redis</span> <span class="pl-smi">RedisMock</span>
<span class="pl-c1">minio</span> <span class="pl-smi">MinIOMock</span>
<span class="pl-c1">logService</span> <span class="pl-smi">LogServiceMock</span>
<span class="pl-c1">verifyService</span> <span class="pl-smi">VerifyServiceMock</span>
<span class=pl-c1>s</span> <span class=pl-smi>service</span>
<span class="pl-c1">s</span> <span class="pl-smi">service</span>
}
<span class=pl-k>func</span> (<span class=pl-s1>s</span> <span class=pl-c1>*</span><span class=pl-smi>ServiceSuite</span>) <span class=pl-en>SetupTest</span>() {
<span class=pl-c>// Init mock</span>
<span class=pl-c>// Init service</span>
<span class="pl-k">func</span> (<span class="pl-s1">s</span> <span class="pl-c1">*</span><span class="pl-smi">ServiceSuite</span>) <span class="pl-en">SetupTest</span>() {
<span class="pl-c">// Init mock</span>
<span class="pl-c">// Init service</span>
}
<span class=pl-k>func</span> (<span class=pl-s1>s</span> <span class=pl-c1>*</span><span class=pl-smi>ServiceSuite</span>) <span class=pl-en>TestUpload</span>() {
<span class=pl-s1>tests</span> <span class=pl-c1>:=</span> []<span class=pl-k>struct</span>{
<span class=pl-c1>name</span> <span class=pl-smi>string</span>
<span class=pl-c1>req</span> <span class=pl-smi>Request</span>
<span class=pl-c1>verifyErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>minioErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>redisErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>dbErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>logErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>wantErr</span> <span class=pl-smi>error</span>
<span class="pl-k">func</span> (<span class="pl-s1">s</span> <span class="pl-c1">*</span><span class="pl-smi">ServiceSuite</span>) <span class="pl-en">TestUpload</span>() {
<span class="pl-s1">tests</span> <span class="pl-c1">:=</span> []<span class="pl-k">struct</span>{
<span class="pl-c1">name</span> <span class="pl-smi">string</span>
<span class="pl-c1">req</span> <span class="pl-smi">Request</span>
<span class="pl-c1">verifyErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">minioErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">redisErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">dbErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">logErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">wantErr</span> <span class="pl-smi">error</span>
}{
{
<span class=pl-c>// Init test case</span>
<span class="pl-c">// Init test case</span>
}
}
<span class=pl-k>for</span> <span class=pl-s1>_</span>, <span class=pl-s1>tc</span> <span class=pl-c1>:=</span> <span class=pl-k>range</span> <span class=pl-s1>tests</span> {
<span class=pl-s1>s</span>.<span class=pl-en>Run</span>(<span class=pl-s1>tc</span>.<span class=pl-c1>name</span>, <span class=pl-k>func</span>(){
<span class=pl-c>// Mock all error depends on test case</span>
<span class=pl-k>if</span> <span class=pl-s1>tc</span>.<span class=pl-c1>verifyErr</span> <span class=pl-c1>!=</span> <span class=pl-c1>nil</span> {
<span class=pl-s1>s</span>.<span class=pl-c1>verifyService</span>.<span class=pl-en>MockVerify</span>().<span class=pl-en>Return</span>(<span class=pl-s1>tc</span>.<span class=pl-c1>verifyErr</span>)
<span class="pl-k">for</span> <span class="pl-s1">_</span>, <span class="pl-s1">tc</span> <span class="pl-c1">:=</span> <span class="pl-k">range</span> <span class="pl-s1">tests</span> {
<span class="pl-s1">s</span>.<span class="pl-en">Run</span>(<span class="pl-s1">tc</span>.<span class="pl-c1">name</span>, <span class="pl-k">func</span>(){
<span class="pl-c">// Mock all error depends on test case</span>
<span class="pl-k">if</span> <span class="pl-s1">tc</span>.<span class="pl-c1">verifyErr</span> <span class="pl-c1">!=</span> <span class="pl-c1">nil</span> {
<span class="pl-s1">s</span>.<span class="pl-c1">verifyService</span>.<span class="pl-en">MockVerify</span>().<span class="pl-en">Return</span>(<span class="pl-s1">tc</span>.<span class="pl-c1">verifyErr</span>)
}
<span class=pl-c>// ...</span>
<span class="pl-c">// ...</span>
<span class=pl-s1>gotErr</span> <span class=pl-c1>:=</span> <span class=pl-s1>s</span>.<span class=pl-c1>service</span>.<span class=pl-en>Upload</span>(<span class=pl-s1>tc</span>.<span class=pl-c1>req</span>)
<span class=pl-s1>s</span>.<span class=pl-en>Equal</span>(<span class=pl-s1>wantErr</span>, <span class=pl-s1>gotErr</span>)
<span class="pl-s1">gotErr</span> <span class="pl-c1">:=</span> <span class="pl-s1">s</span>.<span class="pl-c1">service</span>.<span class="pl-en">Upload</span>(<span class="pl-s1">tc</span>.<span class="pl-c1">req</span>)
<span class="pl-s1">s</span>.<span class="pl-en">Equal</span>(<span class="pl-s1">wantErr</span>, <span class="pl-s1">gotErr</span>)
})
}
}</pre></div><p>Looks good right?
}</pre></div>
<p>Looks good right?
Be careful with this.
It can go from 0 to 100 ugly real quick.<p>What if req is a struct with many fields?
It can go from 0 to 100 ugly real quick.</p>
<p>What if req is a struct with many fields?
So in each test case you need to set up req.
They are almost the same, but with some error case you must alter req.
It's easy to be init with wrong value here (typing maybe ?).
Also all req looks similiar, kinda duplicated.<div class="highlight highlight-source-go"><pre><span class=pl-s1>tests</span> <span class=pl-c1>:=</span> []<span class=pl-k>struct</span>{
<span class=pl-c1>name</span> <span class=pl-smi>string</span>
<span class=pl-c1>req</span> <span class=pl-smi>Request</span>
<span class=pl-c1>verifyErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>minioErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>redisErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>dbErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>logErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>wantErr</span> <span class=pl-smi>error</span>
Also all req looks similiar, kinda duplicated.</p>
<div class="highlight highlight-source-go"><pre><span class="pl-s1">tests</span> <span class="pl-c1">:=</span> []<span class="pl-k">struct</span>{
<span class="pl-c1">name</span> <span class="pl-smi">string</span>
<span class="pl-c1">req</span> <span class="pl-smi">Request</span>
<span class="pl-c1">verifyErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">minioErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">redisErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">dbErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">logErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">wantErr</span> <span class="pl-smi">error</span>
}{
{
<span class=pl-c1>req</span>: <span class=pl-smi>Request</span> {
<span class=pl-c1>a</span>: <span class=pl-s>"a"</span>,
<span class=pl-c1>b</span>: {
<span class=pl-c1>c</span>: <span class=pl-s>"c"</span>,
<span class=pl-c1>d</span>: {
<span class=pl-s>"e"</span>: <span class=pl-s1>e</span>
<span class="pl-c1">req</span>: <span class="pl-smi">Request</span> {
<span class="pl-c1">a</span>: <span class="pl-s">"a"</span>,
<span class="pl-c1">b</span>: {
<span class="pl-c1">c</span>: <span class="pl-s">"c"</span>,
<span class="pl-c1">d</span>: {
<span class="pl-s">"e"</span>: <span class="pl-s1">e</span>
}
}
}
<span class=pl-c>// Other fieles</span>
<span class="pl-c">// Other fieles</span>
},
{
<span class=pl-c1>req</span>: <span class=pl-smi>Request</span> {
<span class=pl-c1>a</span>: <span class=pl-s>"a"</span>,
<span class=pl-c1>b</span>: {
<span class=pl-c1>c</span>: <span class=pl-s>"c"</span>,
<span class=pl-c1>d</span>: {
<span class=pl-s>"e"</span>: <span class=pl-s1>e</span>
<span class="pl-c1">req</span>: <span class="pl-smi">Request</span> {
<span class="pl-c1">a</span>: <span class="pl-s">"a"</span>,
<span class="pl-c1">b</span>: {
<span class="pl-c1">c</span>: <span class="pl-s">"c"</span>,
<span class="pl-c1">d</span>: {
<span class="pl-s">"e"</span>: <span class="pl-s1">e</span>
}
}
}
<span class=pl-c>// Other fieles</span>
<span class="pl-c">// Other fieles</span>
},
{
<span class=pl-c1>req</span>: <span class=pl-smi>Request</span> {
<span class=pl-c1>a</span>: <span class=pl-s>"a"</span>,
<span class=pl-c1>b</span>: {
<span class=pl-c1>c</span>: <span class=pl-s>"c"</span>,
<span class=pl-c1>d</span>: {
<span class=pl-s>"e"</span>: <span class=pl-s1>e</span>
<span class="pl-c1">req</span>: <span class="pl-smi">Request</span> {
<span class="pl-c1">a</span>: <span class="pl-s">"a"</span>,
<span class="pl-c1">b</span>: {
<span class="pl-c1">c</span>: <span class="pl-s">"c"</span>,
<span class="pl-c1">d</span>: {
<span class="pl-s">"e"</span>: <span class="pl-s1">e</span>
}
}
}
<span class=pl-c>// Other fieles</span>
<span class="pl-c">// Other fieles</span>
}
}</pre></div><p>What if dependencies of service keep growing?
More mock error to test data of course.<div class="highlight highlight-source-go"><pre> <span class=pl-s1>tests</span> <span class=pl-c1>:=</span> []<span class=pl-k>struct</span>{
<span class=pl-c1>name</span> <span class=pl-smi>string</span>
<span class=pl-c1>req</span> <span class=pl-smi>Request</span>
<span class=pl-c1>verifyErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>minioErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>redisErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>dbErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>logErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>wantErr</span> <span class=pl-smi>error</span>
<span class=pl-c>// Murr error</span>
<span class=pl-c1>aErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>bErr</span> <span class=pl-smi>error</span>
<span class=pl-c1>cErr</span> <span class=pl-smi>error</span>
<span class=pl-c>// ...</span>
}</pre></div>
<p>What if dependencies of service keep growing?
More mock error to test data of course.</p>
<div class="highlight highlight-source-go"><pre> <span class="pl-s1">tests</span> <span class="pl-c1">:=</span> []<span class="pl-k">struct</span>{
<span class="pl-c1">name</span> <span class="pl-smi">string</span>
<span class="pl-c1">req</span> <span class="pl-smi">Request</span>
<span class="pl-c1">verifyErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">minioErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">redisErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">dbErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">logErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">wantErr</span> <span class="pl-smi">error</span>
<span class="pl-c">// Murr error</span>
<span class="pl-c1">aErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">bErr</span> <span class="pl-smi">error</span>
<span class="pl-c1">cErr</span> <span class="pl-smi">error</span>
<span class="pl-c">// ...</span>
}{
{
<span class=pl-c>// Init test case</span>
<span class="pl-c">// Init test case</span>
}
}</pre></div><p>The test file keep growing longer and longer until I feel sick about it.<p>See <a href=https://github.com/tektoncd/pipeline/blob/main/pkg/pod/pod_test.go>tektoncd/pipeline unit test</a> to get a feeling about this.
When I see it, <code>TestPodBuild</code> has almost 2000 lines.<p>The solution I propose here is simple (absolutely not perfect, but good with my usecase) thanks to <strong>stretchr/testify</strong>.
}</pre></div>
<p>The test file keep growing longer and longer until I feel sick about it.</p>
<p>See <a href="https://github.com/tektoncd/pipeline/blob/main/pkg/pod/pod_test.go">tektoncd/pipeline unit test</a> to get a feeling about this.
When I see it, <code>TestPodBuild</code> has almost 2000 lines.</p>
<p>The solution I propose here is simple (absolutely not perfect, but good with my usecase) thanks to <strong>stretchr/testify</strong>.
I init all <strong>default</strong> action on <strong>success</strong> case.
Then I <strong>alter</strong> request or mock error for unit test to hit on other case.
Remember if unit test is hit, code coverate is surely increaesed, and that my <strong>goal</strong>.<div class="highlight highlight-source-go"><pre><span class=pl-c>// Init ServiceSuite as above</span>
Remember if unit test is hit, code coverate is surely increaesed, and that my <strong>goal</strong>.</p>
<div class="highlight highlight-source-go"><pre><span class="pl-c">// Init ServiceSuite as above</span>
<span class=pl-k>func</span> (<span class=pl-s1>s</span> <span class=pl-c1>*</span><span class=pl-smi>ServiceSuite</span>) <span class=pl-en>TestUpload</span>() {
<span class=pl-c>// Init success request</span>
<span class=pl-s1>req</span> <span class=pl-c1>:=</span> <span class=pl-smi>Request</span>{
<span class=pl-c>// ...</span>
<span class="pl-k">func</span> (<span class="pl-s1">s</span> <span class="pl-c1">*</span><span class="pl-smi">ServiceSuite</span>) <span class="pl-en">TestUpload</span>() {
<span class="pl-c">// Init success request</span>
<span class="pl-s1">req</span> <span class="pl-c1">:=</span> <span class="pl-smi">Request</span>{
<span class="pl-c">// ...</span>
}
<span class=pl-c>// Init success action</span>
<span class=pl-s1>s</span>.<span class=pl-c1>verifyService</span>.<span class=pl-en>MockVerify</span>().<span class=pl-en>Return</span>(<span class=pl-c1>nil</span>)
<span class=pl-c>// ...</span>
<span class="pl-c">// Init success action</span>
<span class="pl-s1">s</span>.<span class="pl-c1">verifyService</span>.<span class="pl-en">MockVerify</span>().<span class="pl-en">Return</span>(<span class="pl-c1">nil</span>)
<span class="pl-c">// ...</span>
<span class=pl-s1>gotErr</span> <span class=pl-c1>:=</span> <span class=pl-s1>s</span>.<span class=pl-c1>service</span>.<span class=pl-en>Upload</span>(<span class=pl-s1>tc</span>.<span class=pl-c1>req</span>)
<span class=pl-s1>s</span>.<span class=pl-en>NoError</span>(<span class=pl-s1>gotErr</span>)
<span class="pl-s1">gotErr</span> <span class="pl-c1">:=</span> <span class="pl-s1">s</span>.<span class="pl-c1">service</span>.<span class="pl-en">Upload</span>(<span class="pl-s1">tc</span>.<span class="pl-c1">req</span>)
<span class="pl-s1">s</span>.<span class="pl-en">NoError</span>(<span class="pl-s1">gotErr</span>)
<span class=pl-s1>s</span>.<span class=pl-en>Run</span>(<span class=pl-s>"failed"</span>, <span class=pl-k>func</span>(){
<span class=pl-c>// Alter failed request from default</span>
<span class=pl-s1>req</span> <span class=pl-c1>:=</span> <span class=pl-smi>Request</span>{
<span class=pl-c>// ...</span>
<span class="pl-s1">s</span>.<span class="pl-en">Run</span>(<span class="pl-s">"failed"</span>, <span class="pl-k">func</span>(){
<span class="pl-c">// Alter failed request from default</span>
<span class="pl-s1">req</span> <span class="pl-c1">:=</span> <span class="pl-smi">Request</span>{
<span class="pl-c">// ...</span>
}
<span class=pl-s1>gotErr</span> <span class=pl-c1>:=</span> <span class=pl-s1>s</span>.<span class=pl-c1>service</span>.<span class=pl-en>Upload</span>(<span class=pl-s1>tc</span>.<span class=pl-c1>req</span>)
<span class=pl-s1>s</span>.<span class=pl-en>Error</span>(<span class=pl-s1>gotErr</span>)
<span class="pl-s1">gotErr</span> <span class="pl-c1">:=</span> <span class="pl-s1">s</span>.<span class="pl-c1">service</span>.<span class="pl-en">Upload</span>(<span class="pl-s1">tc</span>.<span class="pl-c1">req</span>)
<span class="pl-s1">s</span>.<span class="pl-en">Error</span>(<span class="pl-s1">gotErr</span>)
})
<span class=pl-s1>s</span>.<span class=pl-en>Run</span>(<span class=pl-s>"another failed"</span>, <span class=pl-k>func</span>(){
<span class=pl-c>// Alter verify return</span>
<span class=pl-s1>s</span>.<span class=pl-c1>verifyService</span>.<span class=pl-en>MockVerify</span>().<span class=pl-en>Return</span>(<span class=pl-s1>someErr</span>)
<span class="pl-s1">s</span>.<span class="pl-en">Run</span>(<span class="pl-s">"another failed"</span>, <span class="pl-k">func</span>(){
<span class="pl-c">// Alter verify return</span>
<span class="pl-s1">s</span>.<span class="pl-c1">verifyService</span>.<span class="pl-en">MockVerify</span>().<span class="pl-en">Return</span>(<span class="pl-s1">someErr</span>)
<span class=pl-s1>gotErr</span> <span class=pl-c1>:=</span> <span class=pl-s1>s</span>.<span class=pl-c1>service</span>.<span class=pl-en>Upload</span>(<span class=pl-s1>tc</span>.<span class=pl-c1>req</span>)
<span class=pl-s1>s</span>.<span class=pl-en>Error</span>(<span class=pl-s1>gotErr</span>)
<span class="pl-s1">gotErr</span> <span class="pl-c1">:=</span> <span class="pl-s1">s</span>.<span class="pl-c1">service</span>.<span class="pl-en">Upload</span>(<span class="pl-s1">tc</span>.<span class="pl-c1">req</span>)
<span class="pl-s1">s</span>.<span class="pl-en">Error</span>(<span class="pl-s1">gotErr</span>)
})
<span class=pl-c>// ...</span>
}</pre></div><p>If you think this is not quick enough, just <strong>ignore</strong> the response.
You only need to check error or not if you want code coverage only.<p>So if request change fields or more dependencies, I need to update success case, and maybe add corresponding error case if need.<p>Same idea but still with table, you can find here <a href=https://arslan.io/2022/12/04/functional-table-driven-tests-in-go/ rel=nofollow>Functional table-driven tests in Go - Fatih Arslan</a>.</p><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
<span class="pl-c">// ...</span>
}</pre></div>
<p>If you think this is not quick enough, just <strong>ignore</strong> the response.
You only need to check error or not if you want code coverage only.</p>
<p>So if request change fields or more dependencies, I need to update success case, and maybe add corresponding error case if need.</p>
<p>Same idea but still with table, you can find here <a href="https://arslan.io/2022/12/04/functional-table-driven-tests-in-go/" rel="nofollow">Functional table-driven tests in Go - Fatih Arslan</a>.</p>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1,2 +1,50 @@
<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown-dark.css><style>.markdown-body{box-sizing:border-box;min-width:200px;max-width:980px;margin:0 auto;padding:45px}@media(max-width:767px){.markdown-body{padding:15px}}</style><body class=markdown-body><a href=index>Index</a><h1><a id=user-content-index class=anchor aria-hidden=true href=#index><span aria-hidden=true class="octicon octicon-link"></span></a>Index</h1><p>This is where I dump my thoughts.<ul><li><a href=2022-06-08-backup>Backup my way</a><li><a href=2022-06-08-dockerfile-go>Dockerfile for Go</a><li><a href=2022-07-10-bootstrap-go>Bootstrap Go</a><li><a href=2022-07-12-uuid-or-else>UUID or else</a><li><a href=2022-07-19-migrate-to-buf>Migrate to buf</a><li><a href=2022-07-31-sql>SQL</a><li><a href=2022-07-31-experiment-go>Experiment go</a><li><a href=2022-08-10-gitignore>gitignore</a><li><a href=2022-10-26-reload-config>Reload config</a><li><a href=2022-12-25-archlinux>Install Arch Linux</a><li><a href=2022-12-25-go-test-asap>Speed up writing Go test ASAP</a></ul><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
</head>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
<h1><a id="user-content-index" class="anchor" aria-hidden="true" href="#index"><span aria-hidden="true" class="octicon octicon-link"></span></a>Index</h1>
<p>This is where I dump my thoughts.</p>
<ul>
<li><a href="2022-06-08-backup">Backup my way</a></li>
<li><a href="2022-06-08-dockerfile-go">Dockerfile for Go</a></li>
<li><a href="2022-07-10-bootstrap-go">Bootstrap Go</a></li>
<li><a href="2022-07-12-uuid-or-else">UUID or else</a></li>
<li><a href="2022-07-19-migrate-to-buf">Migrate to buf</a></li>
<li><a href="2022-07-31-sql">SQL</a></li>
<li><a href="2022-07-31-experiment-go">Experiment go</a></li>
<li><a href="2022-08-10-gitignore">gitignore</a></li>
<li><a href="2022-10-26-reload-config">Reload config</a></li>
<li><a href="2022-12-25-archlinux">Install Arch Linux</a></li>
<li><a href="2022-12-25-go-test-asap">Speed up writing Go test ASAP</a></li>
</ul>
<a href="mailto:hauvipapro+posts@gmail.com"
>Feel free to ask me via email</a
>
<a rel="me" href="https://hachyderm.io/@haunguyen">Mastodon</a>
</body>
</html>

View File

@ -1 +0,0 @@
body{font-family:recursive,sans-serif;font-variation-settings:"MONO" 0,"CASL" 1;margin:5% auto;max-width:75%;line-height:1.8;color:#24292f;background:#fff}code{font-family:iosevka term ss08 web;display:inline-block;background:#f6f8fa}a{color:#0969da}@media(prefers-color-scheme:dark){:root{color-scheme:dark}body{color:#c9d1d9;background:#0d1117}code{background:#161b22}a{color:#58a6ff}}

14
main.go
View File

@ -16,9 +16,6 @@ const (
postFilesPath = "posts"
templatePostPath = "templates/post.html"
templateCSSPath = "templates/styles.css"
cssFilename = "styles.css"
generatedPath = "docs"
extHTML = ".html"
@ -111,15 +108,4 @@ func main() {
htmlFile.Close()
}
// Copy css file from templates to generated
templateCSSBytes, err := os.ReadFile(templateCSSPath)
if err != nil {
log.Fatalln("Failed to open file", templateCSSPath, err)
}
generatedCSSPath := filepath.Join(generatedPath, cssFilename)
if err := os.WriteFile(generatedCSSPath, templateCSSBytes, 0o600); err != nil {
log.Fatalln("Failed to write file", generatedCSSPath, err)
}
}

View File

@ -3,23 +3,27 @@
<head>
<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
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Recursive:wght,CASL,MONO@300..800,0..1,0..1&display=swap"
/>
<link
rel="stylesheet"
href="https://haunt98.github.io/iosevka_webfont/iosevka-term-ss08/iosevka-term-ss08.css"
/>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
/>
<link rel="stylesheet" href="styles.css" />
</head>
<style></style>
<style>
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
</style>
<body class="markdown-body">
<a href="index">Index</a>
{{.Body}}

View File

@ -1,31 +0,0 @@
/* https://github.com/sindresorhus/github-markdown-css */
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}
@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}
/* Custom style */
body {
font-family: "Recursive", sans-serif;
font-variation-settings: "MONO" 0, "CASL" 1;
margin: 5% auto;
max-width: 75%;
line-height: 1.8;
color: #24292f;
background: #ffffff;
}
code {
font-family: "Iosevka Term SS08 Web";
display: inline-block;
background: #f6f8fa;
}