parent
e60e6852ad
commit
4e70059c4f
13
README.md
13
README.md
|
@ -2,16 +2,15 @@
|
||||||
|
|
||||||
Write markdown, convert to html, then publish using Github Pages.
|
Write markdown, convert to html, then publish using Github Pages.
|
||||||
|
|
||||||
|
First add GitHub access token in `.github_access_token`.
|
||||||
|
|
||||||
Steps:
|
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
|
- Generate HTML by running `make`
|
||||||
make
|
|
||||||
```
|
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
|
@ -26,3 +25,5 @@ make
|
||||||
- https://github.com/be5invis/Iosevka
|
- https://github.com/be5invis/Iosevka
|
||||||
- https://github.com/ntk148v/iosevkawebfont
|
- https://github.com/ntk148v/iosevkawebfont
|
||||||
- https://gist.github.com/JoeyBurzynski/617fb6201335779f8424ad9528b72c41
|
- https://gist.github.com/JoeyBurzynski/617fb6201335779f8424ad9528b72c41
|
||||||
|
- https://github.com/sindresorhus/github-markdown-css
|
||||||
|
- https://github.com/google/go-github
|
||||||
|
|
|
@ -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.
|
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.
|
Because you know, it is hard to guess what is waiting for us in the future.</p>
|
||||||
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>.
|
<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>
|
||||||
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.
|
<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>
|
||||||
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.
|
<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).
|
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.
|
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.
|
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.
|
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
|
rclone sync MyBooks remote:MyBooks -P --exclude .DS_Store
|
||||||
|
|
||||||
<span class=pl-c><span class=pl-c>#</span> Sync from remote to local</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>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>
|
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
|
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
|
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
|
restic -r rclone:remote:PrivateData forget --keep-last 1 --prune
|
||||||
|
|
||||||
<span class=pl-c><span class=pl-c>#</span> Restore</span>
|
<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.
|
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.
|
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.
|
First thing first, I enable 2-Step Verification for all of my important accounts, should use both OTP and phone method.</p>
|
||||||
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.
|
<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 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.
|
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>
|
||||||
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.
|
<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 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.
|
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.
|
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>
|
After that, log in to Bitwarden account using the master password and OTP from Gmail, which I open previously.</p>
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
|
<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>
|
||||||
|
|
|
@ -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.
|
<!DOCTYPE html>
|
||||||
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
|
<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 \
|
||||||
&& go1.19 download
|
&& 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.mod .
|
||||||
<span class=pl-k>COPY</span> go.sum .
|
<span class="pl-k">COPY</span> go.sum .
|
||||||
<span class=pl-k>COPY</span> vendor .
|
<span class="pl-k">COPY</span> vendor .
|
||||||
<span class=pl-k>COPY</span> . .
|
<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.
|
<span class="pl-k">ENTRYPOINT</span> [<span class="pl-s">"/app"</span>]</pre></div>
|
||||||
First stage is <a href=https://hub.docker.com/_/golang rel=nofollow>Go official image</a>,
|
<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.
|
||||||
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>,
|
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.
|
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.
|
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.
|
So I stick with it for a while.</p>
|
||||||
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 \
|
<p>Also, remember to match Distroless Debian version with Go official image Debian version.</p>
|
||||||
&& go1.19 download</pre></div><p>This is optional.
|
<div class="highlight highlight-source-dockerfile"><pre><span class="pl-k">FROM</span> golang:1.19-bullseye as builder</pre></div>
|
||||||
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
|
<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 \
|
||||||
|
&& 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.mod .
|
||||||
<span class=pl-k>COPY</span> go.sum .
|
<span class="pl-k">COPY</span> go.sum .
|
||||||
<span class=pl-k>COPY</span> vendor .
|
<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> . .</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.
|
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>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>,
|
<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.
|
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>-trimpath</code> to support reproduce build.<div class="highlight highlight-source-dockerfile"><pre><span class=pl-k>FROM</span> gcr.io/distroless/base-debian11
|
<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>
|
<span class="pl-k">ENTRYPOINT</span> [<span class="pl-s">"/app"</span>]</pre></div>
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
|
<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>
|
||||||
|
|
|
@ -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.
|
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.
|
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
|
internal
|
||||||
<span class=pl-k>|</span> business
|
<span class="pl-k">|</span> business
|
||||||
<span class=pl-k>|</span> | http
|
<span class="pl-k">|</span> | http
|
||||||
<span class=pl-k>|</span> | | handler.go
|
<span class="pl-k">|</span> | | handler.go
|
||||||
<span class=pl-k>|</span> | | service.go
|
<span class="pl-k">|</span> | | service.go
|
||||||
<span class=pl-k>|</span> | | models.go
|
<span class="pl-k">|</span> | | models.go
|
||||||
<span class=pl-k>|</span> | grpc
|
<span class="pl-k">|</span> | grpc
|
||||||
<span class=pl-k>|</span> | | handler.go
|
<span class="pl-k">|</span> | | handler.go
|
||||||
<span class=pl-k>|</span> | | models.go
|
<span class="pl-k">|</span> | | models.go
|
||||||
<span class=pl-k>|</span> | consumer
|
<span class="pl-k">|</span> | consumer
|
||||||
<span class=pl-k>|</span> | | handler.go
|
<span class="pl-k">|</span> | | handler.go
|
||||||
<span class=pl-k>|</span> | | service.go
|
<span class="pl-k">|</span> | | service.go
|
||||||
<span class=pl-k>|</span> | | models.go
|
<span class="pl-k">|</span> | | models.go
|
||||||
<span class=pl-k>|</span> | service.go
|
<span class="pl-k">|</span> | service.go
|
||||||
<span class=pl-k>|</span> | repository.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>.
|
<span class="pl-k">|</span> | models.go</pre></div>
|
||||||
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.
|
<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.
|
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.
|
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>
|
||||||
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>() {
|
<p>Another bad practice is adapter service.
|
||||||
<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>))
|
No need to write a new service if what we need is just common pkg libs.</p>
|
||||||
<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>)
|
<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-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">fieldA</span> <span class="pl-smi">int</span>
|
||||||
<span class=pl-c1>fieldB</span> <span class=pl-smi>string</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">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-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-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">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-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-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-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>&</span><span class=pl-smi>S</span>{}
|
<span class="pl-s1">s</span> <span class="pl-c1">:=</span> <span class="pl-c1">&</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-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-en">opt</span>(<span class="pl-s1">s</span>)
|
||||||
}
|
}
|
||||||
<span class=pl-k>return</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.
|
}</pre></div>
|
||||||
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.
|
<p>In above example, I construct <code>s</code> with <code>WithA</code> and <code>WithB</code> option.
|
||||||
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>).
|
No need to pass direct field inside <code>s</code>.</p>
|
||||||
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>)
|
<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-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-c">// Do some thing</span>
|
||||||
<span class=pl-k>return</span> <span class=pl-c1>nil</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-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-c">// Do other thing</span>
|
||||||
<span class=pl-k>return</span> <span class=pl-c1>nil</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-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>
|
<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>
|
}</pre></div>
|
||||||
<span class=pl-c>// +build tools</span>
|
<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-k">import</span> (
|
||||||
_ <span class=pl-s>"github.com/golang/protobuf/protoc-gen-go"</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>:
|
)</pre></div>
|
||||||
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.
|
<p>And then in <code>Makefile</code>:</p>
|
||||||
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.
|
<div class="highlight highlight-source-makefile"><pre><span class="pl-en">build</span>:
|
||||||
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.
|
go install github.com/golang/protobuf/protoc-gen-go</pre></div>
|
||||||
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.
|
<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.
|
To learn and use those libs correctly is time consuming.
|
||||||
So just stick to plain SQL.
|
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.
|
For example, it is hard to get primary key after insert/update.
|
||||||
So may be you want to use ORM for those cases.
|
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.
|
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.
|
Pick 1 then sleep peacefully.</p>
|
||||||
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>
|
<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>
|
||||||
<span class=pl-c>// https://jonwillia.ms/2019/12/22/conditional-gomock-mockgen</span>
|
</h3>
|
||||||
<span class=pl-c>//go:generate sh -c "test service_mock_generated.go -nt $GOFILE && 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>
|
<p>The first is easy to use but not powerful as the later.
|
||||||
<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>)
|
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 && 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-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-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-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">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">Water</span>
|
||||||
<span class=pl-s1>OrangeJuice</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
|
)</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>
|
<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.
|
stringer -type=Drink</pre></div>
|
||||||
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.
|
<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>
|
||||||
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>
|
</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
|
go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest
|
||||||
|
|
||||||
<span class=pl-c><span class=pl-c>#</span> Fix</span>
|
<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>
|
fieldalignment -fix ./internal/business/<span class="pl-k">*</span>.go</pre></div>
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
|
<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>
|
||||||
|
|
|
@ -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.
|
<!DOCTYPE html>
|
||||||
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.
|
<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.
|
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>.
|
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>
|
||||||
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?
|
<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.
|
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.
|
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).
|
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.
|
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>
|
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>
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
|
<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>
|
||||||
|
|
|
@ -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>
|
<!DOCTYPE html>
|
||||||
<span class=pl-c>// +build tools</span>
|
<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> (
|
@media (max-width: 767px) {
|
||||||
_ <span class=pl-s>"github.com/envoyproxy/protoc-gen-validate"</span>
|
.markdown-body {
|
||||||
_ <span class=pl-s>"github.com/golang/protobuf/protoc-gen-go"</span>
|
padding: 15px;
|
||||||
_ <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>
|
</style>
|
||||||
)</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>
|
<body class="markdown-body">
|
||||||
<span class=pl-ent>deps</span>:
|
<a href="index">Index</a>
|
||||||
- <span class=pl-s>buf.build/haunt98/googleapis:b38d93f7ade94a698adff9576474ae7c</span>
|
<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>
|
||||||
- <span class=pl-s>buf.build/haunt98/grpc-gateway:ecf4f0f58aa8496f8a76ed303c6e06c7</span>
|
</h1>
|
||||||
- <span class=pl-s>buf.build/haunt98/protoc-gen-validate:2686264610fc4ad4a9fcc932647e279d</span>
|
<p>Why? Because <code>prototool</code> is outdated, and can not run on M1 mac.</p>
|
||||||
- <span class=pl-s>buf.build/haunt98/marshal-zap:2a593ca925134680a5820d3f13c1be5a</span>
|
<p>We need 3 files:</p>
|
||||||
<span class=pl-ent>breaking</span>:
|
<ul>
|
||||||
<span class=pl-ent>use</span>:
|
<li>
|
||||||
- <span class=pl-s>FILE</span>
|
<code>build.go</code>: need to install protoc-gen-* binaries with pin version in <code>go.mod</code>
|
||||||
<span class=pl-ent>lint</span>:
|
</li>
|
||||||
<span class=pl-ent>use</span>:
|
<li><code>buf.yaml</code></li>
|
||||||
- <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>
|
<li><code>buf.gen.yaml</code></li>
|
||||||
<span class=pl-ent>plugins</span>:
|
</ul>
|
||||||
- <span class=pl-ent>name</span>: <span class=pl-s>go</span>
|
<p>FYI, the libs version I use:</p>
|
||||||
<span class=pl-ent>out</span>: <span class=pl-s>pkg</span>
|
<ul>
|
||||||
<span class=pl-ent>opt</span>:
|
<li><a href="https://github.com/golang/protobuf/releases/tag/v1.5.2">golang/protobuf v1.5.2</a></li>
|
||||||
- <span class=pl-s>plugins=grpc</span>
|
<li><a href="https://github.com/grpc-ecosystem/grpc-gateway/releases/tag/v1.16.0">grpc-ecosystem/grpc-gateway v1.16.0</a></li>
|
||||||
- <span class=pl-ent>name</span>: <span class=pl-s>grpc-gateway</span>
|
<li><a href="github.com/envoyproxy/protoc-gen-validate">envoyproxy/protoc-gen-validate</a></li>
|
||||||
<span class=pl-ent>out</span>: <span class=pl-s>pkg</span>
|
<li><a href="github.com/kei2100/protoc-gen-marshal-zap">kei2100/protoc-gen-marshal-zap</a></li>
|
||||||
<span class=pl-ent>opt</span>:
|
</ul>
|
||||||
- <span class=pl-s>logtostderr=true</span>
|
<p><code>build.go</code>:</p>
|
||||||
- <span class=pl-ent>name</span>: <span class=pl-s>swagger</span>
|
<div class="highlight highlight-source-go"><pre><span class="pl-c">//go:build tools</span>
|
||||||
<span class=pl-ent>out</span>: <span class=pl-s>.</span>
|
<span class="pl-c">// +build tools</span>
|
||||||
<span class=pl-ent>opt</span>:
|
|
||||||
- <span class=pl-s>logtostderr=true</span>
|
<span class="pl-k">import</span> (
|
||||||
- <span class=pl-ent>name</span>: <span class=pl-s>validate</span>
|
_ <span class="pl-s">"github.com/envoyproxy/protoc-gen-validate"</span>
|
||||||
<span class=pl-ent>out</span>: <span class=pl-s>pkg</span>
|
_ <span class="pl-s">"github.com/golang/protobuf/protoc-gen-go"</span>
|
||||||
<span class=pl-ent>opt</span>:
|
_ <span class="pl-s">"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway"</span>
|
||||||
- <span class=pl-s>lang=go</span>
|
_ <span class="pl-s">"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger"</span>
|
||||||
- <span class=pl-ent>name</span>: <span class=pl-s>marshal-zap</span>
|
_ <span class="pl-s">"github.com/kei2100/protoc-gen-marshal-zap/plugin/protoc-gen-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>:
|
)</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/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-grpc-gateway
|
||||||
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
|
go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
|
||||||
|
@ -46,5 +99,36 @@
|
||||||
go install github.com/bufbuild/buf/cmd/buf@latest
|
go install github.com/bufbuild/buf/cmd/buf@latest
|
||||||
buf mod update
|
buf mod update
|
||||||
buf format -w
|
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>
|
buf generate</pre></div>
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
|
<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>
|
||||||
|
|
|
@ -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.
|
<!DOCTYPE html>
|
||||||
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> {
|
<html>
|
||||||
<span class=pl-c1>GetUser</span>()
|
<head>
|
||||||
<span class=pl-c1>AddUser</span>()
|
<meta charset="utf-8" />
|
||||||
<span class=pl-c1>GetAccount</span>()
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<span class=pl-c1>RemoveAccount</span>()
|
<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-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">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-s1">c</span>.<span class="pl-en">RemoveAccount</span>()</pre></div>
|
||||||
<span class=pl-c1>User</span> <span class=pl-smi>ClientUser</span>
|
<p>Try:</p>
|
||||||
<span class=pl-c1>Account</span> <span class=pl-smi>ClientAccount</span>
|
<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-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">Get</span>()
|
||||||
<span class=pl-c1>Add</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-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">Get</span>()
|
||||||
<span class=pl-c1>Remove</span>()
|
<span class="pl-c1">Remove</span>()
|
||||||
}
|
}
|
||||||
|
|
||||||
<span class=pl-c>// c is Client</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">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-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>
|
||||||
|
<p>For example we have client which connect to bank.
|
||||||
There are many functions like <code>GetUser</code>, <code>GetTransaction</code>, <code>VerifyAccount</code>, ...
|
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.
|
So split big client to many children, each child handle single aspect, like user or transaction.</p>
|
||||||
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?
|
<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>.
|
<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>
|
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>
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
|
<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>
|
||||||
|
|
|
@ -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.
|
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.
|
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.
|
But if there is any cool ORM for Go, I guess I try.</p>
|
||||||
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.
|
<p>This guide is not kind of guide which cover all cases.
|
||||||
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 my little tricks when I work with SQL.</p>
|
||||||
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.
|
<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.
|
In my business, I use timestamp in milliseconds.
|
||||||
Then I save timestamp as int64 value to database.
|
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.
|
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 -> convert to unix timestamp milliseconds -> [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 -> convert to unix timestamp milliseconds -> [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.
|
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>`
|
Choose wisely!</p>
|
||||||
<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>
|
<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-c"><span class="pl-c">--</span> Bad</span>
|
||||||
<span class=pl-k>SELECT</span> <span class=pl-k>*</span>
|
<span class="pl-k">SELECT</span> <span class="pl-k">*</span>
|
||||||
<span class=pl-k>FROM</span> table
|
<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-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-c"><span class="pl-c">--</span> Good</span>
|
||||||
<span class=pl-k>SELECT</span> <span class=pl-k>*</span>
|
<span class="pl-k">SELECT</span> <span class="pl-k">*</span>
|
||||||
<span class=pl-k>FROM</span> table
|
<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>
|
<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>
|
||||||
<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.
|
<p>Need clarify why this happpen? Idk :(</p>
|
||||||
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>
|
<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>
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</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>
|
||||||
|
|
|
@ -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
|
.DS_Store
|
||||||
|
|
||||||
<span class=pl-c># Windows</span>
|
<span class="pl-c"># Windows</span>
|
||||||
<span class=pl-k>*</span>.exe
|
<span class="pl-k">*</span>.exe
|
||||||
|
|
||||||
<span class=pl-c># IntelliJ</span>
|
<span class="pl-c"># IntelliJ</span>
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
<span class=pl-c># VSCode</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>
|
.vscode/</pre></div>
|
||||||
<span class=pl-c># Test coverage</span>
|
<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
|
coverage.out
|
||||||
|
|
||||||
<span class=pl-c># Should ignore vendor</span>
|
<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>
|
vendor</pre></div>
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
|
<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>
|
||||||
|
|
|
@ -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">skinparam</span> <span class="pl-k">defaultFontName</span> <span class="pl-s">Iosevka Term SS08</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>==</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>-></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>-></span> <span class=pl-c1>storage</span>: set/update/delete config
|
<span class="pl-c1">admin</span> <span class="pl-k">-></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">-></span> <span class="pl-c1">storage</span>: set/update/delete config
|
||||||
|
|
||||||
<span class=pl-c1>other_service</span> <span class=pl-k>-></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">-></span> <span class="pl-c1">other_service</span>: init service
|
||||||
|
|
||||||
<span class=pl-c1>other_service</span> <span class=pl-k>-></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">-></span> <span class="pl-c1">storage</span>: make connection
|
||||||
|
|
||||||
<span class=pl-c1>other_service</span> <span class=pl-k>-></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>-></span> <span class=pl-c1>other_service</span>: save config to memory
|
<span class="pl-c1">other_service</span> <span class="pl-k">-></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">-></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>-></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">-></span> <span class="pl-c1">other_service</span>: do business
|
||||||
|
|
||||||
<span class=pl-c1>other_service</span> <span class=pl-k>-></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>-></span> <span class=pl-c1>other_service</span>: do other business
|
<span class="pl-c1">other_service</span> <span class="pl-k">-></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">-></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.
|
<span class="pl-k">deactivate</span> <span class="pl-c1">other_service</span>
|
||||||
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>
|
<span class="pl-k">@enduml</span></pre></div>
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
|
<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>
|
||||||
|
|
|
@ -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
|
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
|
vgcreate RootGroup /dev/sdaX /dev/sdaY
|
||||||
|
|
||||||
<span class=pl-c><span class=pl-c>#</span> Create logical volumes</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:<div class="highlight highlight-source-shell"><pre><span class=pl-c><span class=pl-c>#</span> efi</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
|
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
|
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
|
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
|
mkfs.btrfs -L ROOT /dev/root_partition
|
||||||
|
|
||||||
<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>
|
||||||
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>
|
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
|
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
|
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
|
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
|
mount --mkdir /dev/efi_system_partition /mnt/efi
|
||||||
|
|
||||||
<span class=pl-c><span class=pl-c>#</span> boot</span>
|
<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
|
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
|
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
|
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
|
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
|
pacstrap -K /mnt lvm2
|
||||||
|
|
||||||
<span class=pl-c><span class=pl-c>#</span> Text editor</span>
|
<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>>></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
|
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">>></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>
|
hwclock --systohc</pre></div>
|
||||||
<span class=pl-c># https://wiki.archlinux.org/title/Install_Arch_Linux_on_LVM#Adding_mkinitcpio_hooks</span>
|
<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)
|
HOOKS=(base udev ... block lvm2 filesystems)
|
||||||
|
|
||||||
<span class=pl-c># https://wiki.archlinux.org/title/mkinitcpio#Common_hooks</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>
|
<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
|
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
|
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>
|
<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
|
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
|
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>
|
<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
|
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>
|
<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
|
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>
|
<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
|
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>
|
<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>:
|
homectl update joker --shell=/usr/bin/zsh</pre></div>
|
||||||
|
<p><strong>Note</strong>:
|
||||||
Can not run <code>homectl</code> when install Arch Linux.
|
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-control-center gnome-system-monitor \
|
||||||
gnome-tweaks gnome-backgrounds gnome-screenshot gnome-keyring gnome-logs \
|
gnome-tweaks gnome-backgrounds gnome-screenshot gnome-keyring gnome-logs \
|
||||||
gnome-console gnome-text-editor \
|
gnome-console gnome-text-editor \
|
||||||
nautilus xdg-user-dirs-gtk file-roller evince eog
|
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
|
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
|
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
|
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
|
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 \
|
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>
|
gst-plugin-pipewire pipewire-v4l2</pre></div>
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
|
<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>
|
||||||
|
|
|
@ -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?
|
What is Buf?
|
||||||
And why is Buf?</p><a href=mailto:hauvipapro+posts@gmail.com>Feel free to ask me via email</a>
|
And why is Buf?</p>
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
|
|
||||||
|
<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>
|
||||||
|
|
|
@ -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%?
|
And your boss keep pushing it to 80% or even 90%?
|
||||||
What do you do?
|
What do you do?
|
||||||
Give up?<p>What if I tell you there is a way?
|
Give up?</p>
|
||||||
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.
|
<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.
|
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.
|
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".
|
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.
|
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.
|
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.
|
Somewhere in the future, you change the func, then you need to update test data, then you good!</p>
|
||||||
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> {
|
<p>In simple case, your func only have 2 or 3 inputs so table drive tests is still looking good.
|
||||||
<span class=pl-c1>db</span> <span class=pl-smi>DB</span>
|
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>
|
||||||
<span class=pl-c1>redis</span> <span class=pl-smi>Redis</span>
|
<p>Imagine having below func to upload image:</p>
|
||||||
<span class=pl-c1>minio</span> <span class=pl-smi>MinIO</span>
|
<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>logService</span> <span class=pl-smi>LogService</span>
|
<span class="pl-c1">db</span> <span class="pl-smi">DB</span>
|
||||||
<span class=pl-c1>verifyService</span> <span class=pl-smi>VerifyService</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-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-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">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">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">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">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">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">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">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">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">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-s1">err</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
<span class=pl-k>return</span> <span class=pl-c1>nil</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> {
|
}</pre></div>
|
||||||
suite.<span class=pl-smi>Suite</span>
|
<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">db</span> <span class="pl-smi">DBMock</span>
|
||||||
<span class=pl-c1>redis</span> <span class=pl-smi>RedisMock</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">minio</span> <span class="pl-smi">MinIOMock</span>
|
||||||
<span class=pl-c1>logService</span> <span class=pl-smi>LogServiceMock</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">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-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 mock</span>
|
||||||
<span class=pl-c>// Init service</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-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-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">name</span> <span class="pl-smi">string</span>
|
||||||
<span class=pl-c1>req</span> <span class=pl-smi>Request</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">verifyErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c1>minioErr</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">redisErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c1>dbErr</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">logErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c1>wantErr</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-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-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-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-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-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">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">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.
|
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.
|
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.
|
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 ?).
|
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>{
|
Also all req looks similiar, kinda duplicated.</p>
|
||||||
<span class=pl-c1>name</span> <span class=pl-smi>string</span>
|
<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>req</span> <span class=pl-smi>Request</span>
|
<span class="pl-c1">name</span> <span class="pl-smi">string</span>
|
||||||
<span class=pl-c1>verifyErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">req</span> <span class="pl-smi">Request</span>
|
||||||
<span class=pl-c1>minioErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">verifyErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c1>redisErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">minioErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c1>dbErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">redisErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c1>logErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">dbErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c1>wantErr</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">req</span>: <span class="pl-smi">Request</span> {
|
||||||
<span class=pl-c1>a</span>: <span class=pl-s>"a"</span>,
|
<span class="pl-c1">a</span>: <span class="pl-s">"a"</span>,
|
||||||
<span class=pl-c1>b</span>: {
|
<span class="pl-c1">b</span>: {
|
||||||
<span class=pl-c1>c</span>: <span class=pl-s>"c"</span>,
|
<span class="pl-c1">c</span>: <span class="pl-s">"c"</span>,
|
||||||
<span class=pl-c1>d</span>: {
|
<span class="pl-c1">d</span>: {
|
||||||
<span class=pl-s>"e"</span>: <span class=pl-s1>e</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">req</span>: <span class="pl-smi">Request</span> {
|
||||||
<span class=pl-c1>a</span>: <span class=pl-s>"a"</span>,
|
<span class="pl-c1">a</span>: <span class="pl-s">"a"</span>,
|
||||||
<span class=pl-c1>b</span>: {
|
<span class="pl-c1">b</span>: {
|
||||||
<span class=pl-c1>c</span>: <span class=pl-s>"c"</span>,
|
<span class="pl-c1">c</span>: <span class="pl-s">"c"</span>,
|
||||||
<span class=pl-c1>d</span>: {
|
<span class="pl-c1">d</span>: {
|
||||||
<span class=pl-s>"e"</span>: <span class=pl-s1>e</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">req</span>: <span class="pl-smi">Request</span> {
|
||||||
<span class=pl-c1>a</span>: <span class=pl-s>"a"</span>,
|
<span class="pl-c1">a</span>: <span class="pl-s">"a"</span>,
|
||||||
<span class=pl-c1>b</span>: {
|
<span class="pl-c1">b</span>: {
|
||||||
<span class=pl-c1>c</span>: <span class=pl-s>"c"</span>,
|
<span class="pl-c1">c</span>: <span class="pl-s">"c"</span>,
|
||||||
<span class=pl-c1>d</span>: {
|
<span class="pl-c1">d</span>: {
|
||||||
<span class=pl-s>"e"</span>: <span class=pl-s1>e</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?
|
}</pre></div>
|
||||||
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>{
|
<p>What if dependencies of service keep growing?
|
||||||
<span class=pl-c1>name</span> <span class=pl-smi>string</span>
|
More mock error to test data of course.</p>
|
||||||
<span class=pl-c1>req</span> <span class=pl-smi>Request</span>
|
<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>verifyErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">name</span> <span class="pl-smi">string</span>
|
||||||
<span class=pl-c1>minioErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">req</span> <span class="pl-smi">Request</span>
|
||||||
<span class=pl-c1>redisErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">verifyErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c1>dbErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">minioErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c1>logErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">redisErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c1>wantErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">dbErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c>// Murr error</span>
|
<span class="pl-c1">logErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c1>aErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">wantErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c1>bErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c">// Murr error</span>
|
||||||
<span class=pl-c1>cErr</span> <span class=pl-smi>error</span>
|
<span class="pl-c1">aErr</span> <span class="pl-smi">error</span>
|
||||||
<span class=pl-c>// ...</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.
|
}</pre></div>
|
||||||
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>.
|
<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.
|
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.
|
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-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-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-s1">req</span> <span class="pl-c1">:=</span> <span class="pl-smi">Request</span>{
|
||||||
<span class=pl-c>// ...</span>
|
<span class="pl-c">// ...</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
<span class=pl-c>// Init success action</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-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">// ...</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">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">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-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-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-s1">req</span> <span class="pl-c1">:=</span> <span class="pl-smi">Request</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">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">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-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-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-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">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">Error</span>(<span class="pl-s1">gotErr</span>)
|
||||||
})
|
})
|
||||||
|
|
||||||
<span class=pl-c>// ...</span>
|
<span class="pl-c">// ...</span>
|
||||||
}</pre></div><p>If you think this is not quick enough, just <strong>ignore</strong> the response.
|
}</pre></div>
|
||||||
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>
|
<p>If you think this is not quick enough, just <strong>ignore</strong> the response.
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
|
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>
|
||||||
|
|
|
@ -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>
|
<!DOCTYPE html>
|
||||||
<a rel=me href=https://hachyderm.io/@haunguyen>Mastodon</a>
|
<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>
|
||||||
|
|
|
@ -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
14
main.go
|
@ -16,9 +16,6 @@ const (
|
||||||
postFilesPath = "posts"
|
postFilesPath = "posts"
|
||||||
templatePostPath = "templates/post.html"
|
templatePostPath = "templates/post.html"
|
||||||
|
|
||||||
templateCSSPath = "templates/styles.css"
|
|
||||||
cssFilename = "styles.css"
|
|
||||||
|
|
||||||
generatedPath = "docs"
|
generatedPath = "docs"
|
||||||
|
|
||||||
extHTML = ".html"
|
extHTML = ".html"
|
||||||
|
@ -111,15 +108,4 @@ func main() {
|
||||||
|
|
||||||
htmlFile.Close()
|
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,23 +3,27 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<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
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
|
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.1.0/github-markdown.min.css"
|
||||||
/>
|
/>
|
||||||
<link rel="stylesheet" href="styles.css" />
|
|
||||||
</head>
|
</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">
|
<body class="markdown-body">
|
||||||
<a href="index">Index</a>
|
<a href="index">Index</a>
|
||||||
{{.Body}}
|
{{.Body}}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
Loading…
Reference in New Issue