feat: use deno fmt instead of prettier

main
sudo pacman -Syu 2023-08-06 01:56:25 +07:00
parent 5bdf10ff38
commit d82cc62524
26 changed files with 498 additions and 365 deletions

View File

@ -40,9 +40,7 @@ gen:
go run . go run .
format-html: format-html:
bun upgrade deno fmt ./posts ./templates ./docs
bun install --global prettier
prettier --write ./posts ./templates ./docs
srht: srht:
# https://srht.site/quickstart # https://srht.site/quickstart

View File

@ -1,14 +1,23 @@
# Backup my way # Backup my way
First thing first, I want to list my own devices, which I have through the years: First thing first, I want to list my own devices, which I have through the
years:
- ~~Laptop Samsung NP300E4Z-S06VN (Old laptop which I give to my mom)~~ - ~~Laptop Samsung NP300E4Z-S06VN (Old laptop which I give to my mom)~~
- ~~[Laptop Dell Inspiron 15 3567](https://www.dell.com/support/home/en-vn/product-support/product/inspiron-15-3567-laptop/drivers) [LVFS](https://fwupd.org/lvfs/devices/com.dell.uefi1d4362ca.firmware) (My mom bought it for me when I go to college, I give it to my mom afterward)~~ - ~~[Laptop Dell Inspiron 15 3567](https://www.dell.com/support/home/en-vn/product-support/product/inspiron-15-3567-laptop/drivers)
- ~~[Laptop Acer Nitro AN515-45](https://www.acer.com/ac/en/US/content/support-product/8841) (Gaming laptop which I buy for gaming, I give it to my sister)~~ [LVFS](https://fwupd.org/lvfs/devices/com.dell.uefi1d4362ca.firmware) (My mom
bought it for me when I go to college, I give it to my mom afterward)~~
- ~~[Laptop Acer Nitro AN515-45](https://www.acer.com/ac/en/US/content/support-product/8841)
(Gaming laptop which I buy for gaming, I give it to my sister)~~
- MacBook Pro M1 2020 (My company laptop) - MacBook Pro M1 2020 (My company laptop)
- ~~Phone [LG G3 D851 T-Mobile](https://forum.xda-developers.com/c/lg-g3.3147/) (Bought long time ago, now is a brick)~~ - ~~Phone [LG G3 D851 T-Mobile](https://forum.xda-developers.com/c/lg-g3.3147/)
- ~~Phone [Xiaomi Redmi 6A](https://forum.xda-developers.com/c/xiaomi-redmi-6a.7881/) (I give it to my sister too)~~ (Bought long time ago, now is a brick)~~
- Phone [Xiaomi Poco X3 NFC](https://forum.xda-developers.com/c/xiaomi-poco-x3-nfc.11523/) (Primary phone which I use daily) - ~~Phone
[Xiaomi Redmi 6A](https://forum.xda-developers.com/c/xiaomi-redmi-6a.7881/) (I
give it to my sister too)~~
- Phone
[Xiaomi Poco X3 NFC](https://forum.xda-developers.com/c/xiaomi-poco-x3-nfc.11523/)
(Primary phone which I use daily)
App/Service I use daily: App/Service I use daily:
@ -22,31 +31,44 @@ App/Service I use daily:
- Google Drive (currently use 200GB plan) - Google Drive (currently use 200GB plan)
- GMail < [SimpleLogin](https://simplelogin.io/) < Proton Mail - GMail < [SimpleLogin](https://simplelogin.io/) < Proton Mail
The purpose is that I want my data to be safe, secure, and can be easily recovered if I lost some devices; The purpose is that I want my data to be safe, secure, and can be easily
or in the worst situation, I lost all. recovered if I lost some devices; or in the worst situation, I lost all. Because
Because you know, it is hard to guess what is waiting for us in the future. you know, it is hard to guess what is waiting for us in the future.
There are 2 sections which I want to share, the first is **How to backup**, the second is **Recover strategy**. There are 2 sections which I want to share, the first is **How to backup**, the
second is **Recover strategy**.
## How to backup ## How to backup
Before I talk about backup, I want to talk about data. Before I talk about backup, I want to talk about data. In specifically, which
In specifically, which data should I backup? data should I backup?
I use Arch Linux and macOS, primarily work in the terminal so I have too many dotfiles, for example, `~/.config/nvim/init.lua`. I use Arch Linux and macOS, primarily work in the terminal so I have too many
Each time I reinstall Arch Linux (I like it a lot), I need to reconfigure all the settings, and it is time-consuming. dotfiles, for example, `~/.config/nvim/init.lua`. Each time I reinstall Arch
Linux (I like it a lot), I need to reconfigure all the settings, and it is
time-consuming.
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. So for the DE and UI settings, I keep it as default as possible, unless it's
The others are dotfiles, which I write my own [dotfiles tool](https://github.com/haunt98/dotfiles) to backup and reconfigure easily and quickly. getting in my way, I leave the default setting there and forget about it. The
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). others are dotfiles, which I write my own
Not because it is hard, but as life goes on, the [official install guide](https://wiki.archlinux.org/title/installation_guide) keeps getting new update and covering too many cases for my own personal use, so I write my own [guide](https://github.com/haunt98/til/blob/main/install-archlinux.md) to quickly capture what I need to do. [dotfiles tool](https://github.com/haunt98/dotfiles) to backup and reconfigure
I back up all my dotfiles in GitHub and GitLab as I trust them both. easily and quickly. Also, I know that installing Arch Linux is not easy, despite
Also as I travel the Internet, I discover [Codeberg](https://codeberg.org/), [Treehouse](https://gitea.treehouse.systems/) and use them as another backup for git repo. 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
[official install guide](https://wiki.archlinux.org/title/installation_guide)
keeps getting new update and covering too many cases for my own personal use, so
I write my own
[guide](https://github.com/haunt98/til/blob/main/install-archlinux.md) to
quickly capture what I need to do. I back up all my dotfiles in GitHub and
GitLab as I trust them both. Also as I travel the Internet, I discover
[Codeberg](https://codeberg.org/), [Treehouse](https://gitea.treehouse.systems/)
and use them as another backup for git repo.
So that is my dotfiles, for my regular data, like Wallpaper or Books, Images, I use Google Drive (Actually I pay for it). So that is my dotfiles, for my regular data, like Wallpaper or Books, Images, I
But the step: open the webpage, click the upload button and choose files seems boring and time-consuming. use Google Drive (Actually I pay for it). But the step: open the webpage, click
So I use Rclone, it supports Google Drive, One Drive and many providers but I only use Google Drive for now. the upload button and choose files seems boring and time-consuming. So I use
The commands are simple: Rclone, it supports Google Drive, One Drive and many providers but I only use
Google Drive for now. The commands are simple:
```sh ```sh
# Sync from local to remote # Sync from local to remote
@ -57,7 +79,8 @@ rclone sync MyBooks remote:MyBooks -P --exclude .DS_Store
rclone sync remote:MyBooks MyBooks -P --exclude .DS_Store rclone sync remote:MyBooks MyBooks -P --exclude .DS_Store
``` ```
Before you use Rclone to sync to Google Drive, you should read [Google Drive rclone configuration](https://rclone.org/drive/) first. Before you use Rclone to sync to Google Drive, you should read
[Google Drive rclone configuration](https://rclone.org/drive/) first.
For private data, I use restic which use Rclone as backend: For private data, I use restic which use Rclone as backend:
@ -77,23 +100,28 @@ restic -r rclone:remote:PrivateData forget --keep-last 1 --prune
restic -r rclone:remote:PrivateData restore latest --target ~ restic -r rclone:remote:PrivateData restore latest --target ~
``` ```
The next data is my passwords and my OTPs. The next data is my passwords and my OTPs. These are the things which I'm scare
These are the things which I'm scare to lose the most. to lose the most. First thing first, I enable 2-Step Verification for all of my
First thing first, I enable 2-Step Verification for all of my important accounts, should use both OTP and phone method. important accounts, should use both OTP and phone method.
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. I use Bitwarden for passwords (That is a long story, coming from Google Password
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. manager to Firefox Lockwise and then settle down with Bitwarden) and Aegis for
OTPs. The reason I choose Aegis, not Authy (I use Authy for so long but Aegis is
definitely better) is because Aegis allows me to extract all the OTPs to a
single file (Can be encrypted), which I use to transfer or backup easily.
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. As long as Bitwarden provides free passwords stored, I use all of its apps,
The thing I need to remember is the master password of Bitwarden in my head. extensions so that I can easily sync passwords between laptops and phones. The
thing I need to remember is the master password of Bitwarden in my head.
With Aegis, I export the data, then: With Aegis, I export the data, then:
- Sync it to Google Drive - Sync it to Google Drive
- Store it locally in my phone. - Store it locally in my phone.
The main problem here is the OTP, I can not store all of my OTPs in the cloud completely. The main problem here is the OTP, I can not store all of my OTPs in the cloud
Because if I want to access my OTPs in the cloud, I should log in, and then input my OTP, this is a loop, my friends. completely. Because if I want to access my OTPs in the cloud, I should log in,
and then input my OTP, this is a loop, my friends.
### Backup work related data ### Backup work related data
@ -105,23 +133,28 @@ APIs tools:
- [HTTPie](https://httpie.io/app) - [HTTPie](https://httpie.io/app)
- Already sync online (for now). - Already sync online (for now).
Stay away from Postman, it's lag and you can accidentally upload private data publicly. Stay away from Postman, it's lag and you can accidentally upload private data
publicly.
## Recovery strategy ## Recovery strategy
There are many strategies that I process to react as if something strange is happening to my devices. There are many strategies that I process to react as if something strange is
happening to my devices.
- If I lost my laptops, single laptop or all, do not panic as long as I have my phones. - If I lost my laptops, single laptop or all, do not panic as long as I have my
The OTPs are in there, the passwords are in Bitwarden cloud, other data is in Google Drive so nothing is lost here. phones. The OTPs are in there, the passwords are in Bitwarden cloud, other
- If I lost my phone, but not my laptops, I use the OTPs which are stored locally in my laptops. data is in Google Drive so nothing is lost here.
- In the worst situation, I lost everything, my laptops, my phone. - If I lost my phone, but not my laptops, I use the OTPs which are stored
The first step is to recover my SIM, then log in to Google account using the password and SMS OTP. locally in my laptops.
After that, log in to Bitwarden account using the master password and OTP from Gmail, which I open previously. - In the worst situation, I lost everything, my laptops, my phone. The first
step is to recover my SIM, then log in to Google account using the password
and SMS OTP. After that, log in to Bitwarden account using the master password
and OTP from Gmail, which I open previously.
## Misc ## Misc
To backup everything is hard, so keep it simple and only backup important things. To backup everything is hard, so keep it simple and only backup important
Pick one then stay away from other cloud services: things. Pick one then stay away from other cloud services:
- TODOis, Evernote, ... -> Google Keep/Notion - TODOis, Evernote, ... -> Google Keep/Notion
- Dropbox, OneDrive, ... -> Google Drive - Dropbox, OneDrive, ... -> Google Drive

View File

@ -1,7 +1,7 @@
# Dockerfile for Go # Dockerfile for Go
Each time I start a new Go project, I repeat many steps. Each time I start a new Go project, I repeat many steps. Like set up
Like set up `.gitignore`, CI configs, Dockerfile, ... `.gitignore`, CI configs, Dockerfile, ...
So I decide to have a baseline Dockerfile like this: So I decide to have a baseline Dockerfile like this:
@ -27,31 +27,35 @@ COPY --from=builder /build/app /app
ENTRYPOINT ["/app"] ENTRYPOINT ["/app"]
``` ```
I use [multi-stage build](https://docs.docker.com/develop/develop-images/multistage-build/) to keep my image size small. I use
First stage is [Go official image](https://hub.docker.com/_/golang), [multi-stage build](https://docs.docker.com/develop/develop-images/multistage-build/)
second stage is [Distroless](https://github.com/GoogleContainerTools/distroless). to keep my image size small. First stage is
[Go official image](https://hub.docker.com/_/golang), second stage is
[Distroless](https://github.com/GoogleContainerTools/distroless).
Before Distroless, I use [Alpine official image](https://hub.docker.com/_/alpine), Before Distroless, I use
There is a whole discussion on the Internet to choose which is the best base image for Go. [Alpine official image](https://hub.docker.com/_/alpine), There is a whole
After reading some blogs, I discover Distroless as a small and secure base image. discussion on the Internet to choose which is the best base image for Go. After
So I stick with it for a while. reading some blogs, I discover Distroless as a small and secure base image. So I
stick with it for a while.
Also, remember to match Distroless Debian version with Go official image Debian version. Also, remember to match Distroless Debian version with Go official image Debian
version.
```Dockerfile ```Dockerfile
FROM golang:1.20-bullseye as builder FROM golang:1.20-bullseye as builder
``` ```
This is Go image I use as a build stage. This is Go image I use as a build stage. This can be official Go image or custom
This can be official Go image or custom image is required in some companies. image is required in some companies.
```Dockerfile ```Dockerfile
RUN go install golang.org/dl/go1.20@latest \ RUN go install golang.org/dl/go1.20@latest \
&& go1.20 download && go1.20 download
``` ```
This is optional. This is optional. In my case, my company is slow to update Go image so I use
In my case, my company is slow to update Go image so I use this trick to install latest Go version. this trick to install latest Go version.
```Dockerfile ```Dockerfile
WORKDIR /build WORKDIR /build
@ -64,9 +68,10 @@ COPY . .
I use `/build` to emphasize that I am building something in that directory. I use `/build` to emphasize that I am building something in that directory.
The 4 `COPY` lines are familiar if you use Go enough. The 4 `COPY` lines are familiar if you use Go enough. First is `go.mod` and
First is `go.mod` and `go.sum` because it defines Go modules. `go.sum` because it defines Go modules. The second is `vendor`, this is optional
The second is `vendor`, this is optional but I use it because I don't want each time I build Dockerfile, I need to redownload Go modules. but I use it because I don't want each time I build Dockerfile, I need to
redownload Go modules.
```Dockerfile ```Dockerfile
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 go build -o ./app -tags timetzdata -trimpath -ldflags="-s -w" . RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOAMD64=v3 go build -o ./app -tags timetzdata -trimpath -ldflags="-s -w" .
@ -76,10 +81,12 @@ This is where I build Go program.
- `CGO_ENABLED=0` because I don't want to mess with C libraries. - `CGO_ENABLED=0` because I don't want to mess with C libraries.
- `GOOS=linux GOARCH=amd64` is easy to explain, Linux with x86-64. - `GOOS=linux GOARCH=amd64` is easy to explain, Linux with x86-64.
- `GOAMD64=v3` is new since [Go 1.18](https://go.dev/doc/go1.18#amd64), - `GOAMD64=v3` is new since [Go 1.18](https://go.dev/doc/go1.18#amd64), I use v3
I use v3 because I read about AMD64 version in [Arch Linux rfcs](https://gitlab.archlinux.org/archlinux/rfcs/-/blob/master/rfcs/0002-march.rst). because I read about AMD64 version in
[Arch Linux rfcs](https://gitlab.archlinux.org/archlinux/rfcs/-/blob/master/rfcs/0002-march.rst).
TLDR's newer computers are already x86-64-v3. TLDR's newer computers are already x86-64-v3.
- `-tags timetzdata` to embed timezone database in case base image does not have. - `-tags timetzdata` to embed timezone database in case base image does not
have.
- `-trimpath` to support reproduce build. - `-trimpath` to support reproduce build.
- `-ldflags="-s -w"` to strip debugging information. - `-ldflags="-s -w"` to strip debugging information.

View File

@ -1,9 +1,9 @@
# Bootstrap Go # Bootstrap Go
It is hard to write bootstrap tool to quickly create Go service. It is hard to write bootstrap tool to quickly create Go service. So I write this
So I write this guide instead. guide instead. This is a quick checklist for me every damn time I need to write
This is a quick checklist for me every damn time I need to write a Go service from scratch. a Go service from scratch. Also, this is my personal opinion, so feel free to
Also, this is my personal opinion, so feel free to comment. comment.
## Structure ## Structure
@ -28,8 +28,8 @@ internal
models.go models.go
``` ```
All business codes are inside `internal`. All business codes are inside `internal`. Each business has a different
Each business has a different directory `business`. directory `business`.
Inside each business, there are 2 handlers: `http`, `grpc`: Inside each business, there are 2 handlers: `http`, `grpc`:
@ -37,29 +37,37 @@ Inside each business, there are 2 handlers: `http`, `grpc`:
- `grpc` is for internal APIs (other services are clients). - `grpc` is for internal APIs (other services are clients).
- `consumer` is for consuming messages from queue (Kafka, RabbitMQ, ...). - `consumer` is for consuming messages from queue (Kafka, RabbitMQ, ...).
For each handler, there are usually 3 layers: `handler`, `service`, `repository`: For each handler, there are usually 3 layers: `handler`, `service`,
`repository`:
- `handler` interacts directly with gRPC, 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. - `handler` interacts directly with gRPC, REST or consumer using specific codes
- `service` is where we write business/logic codes, and only business/logic codes is written here. (cookies, ...) In case gRPC, there are frameworks outside handle for us so we
- `repository` is where we write codes which interacts with database/cache like MySQL, Redis, ... can write business/logic codes here too. But remember, gRPC only.
- `service` is where we write business/logic codes, and only business/logic
codes is written here.
- `repository` is where we write codes which interacts with database/cache like
MySQL, Redis, ...
- `models` is where we put all request, response, data models. - `models` is where we put all request, response, data models.
Location: Location:
- `handler` must exist inside `grpc`, `http`, `consumer`. - `handler` must exist inside `grpc`, `http`, `consumer`.
- `service`, `models` can exist directly inside of `business` if both `grpc`, `http`, `consumer` has same business/logic. - `service`, `models` can exist directly inside of `business` if both `grpc`,
`http`, `consumer` has same business/logic.
- `repository` should be placed directly inside of `business`. - `repository` should be placed directly inside of `business`.
## Do not repeat! ## Do not repeat!
If we have too many services, some of the logic will be overlapped. If we have too many services, some of the logic will be overlapped.
For example, service A and service B both need to make POST call API to service C. For example, service A and service B both need to make POST call API to service
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. C. If service A and service B both have libs to call service C to do that API,
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. we need to move the libs to some common pkg libs. So in the future, service D
which needs to call C will not need to copy libs to handle service C api but
only need to import from common pkg libs.
Another bad practice is adapter service. Another bad practice is adapter service. No need to write a new service if what
No need to write a new service if what we need is just common pkg libs. we need is just common pkg libs.
## Taste on style guide ## Taste on style guide
@ -112,17 +120,18 @@ func NewS(opts ...OptionS) *S {
} }
``` ```
In above example, I construct `s` with `WithA` and `WithB` option. In above example, I construct `s` with `WithA` and `WithB` option. No need to
No need to pass direct field inside `s`. pass direct field inside `s`.
### Use [errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) as much as possible ### Use [errgroup](https://pkg.go.dev/golang.org/x/sync/errgroup) as much as possible
If business logic involves calling too many APIs, but they are not depend on each other. If business logic involves calling too many APIs, but they are not depend on
We can fire them parallel :) each other. We can fire them parallel :)
Personally, I prefer `errgroup` to `WaitGroup` (https://pkg.go.dev/sync#WaitGroup). Personally, I prefer `errgroup` to `WaitGroup`
Because I always need deal with error. (https://pkg.go.dev/sync#WaitGroup). Because I always need deal with error. Be
Be super careful with `egCtx`, should use this instead of parent `ctx` inside `eg.Go`. super careful with `egCtx`, should use this instead of parent `ctx` inside
`eg.Go`.
Example: Example:
@ -146,7 +155,8 @@ if err := eg.Wait(); err != nil {
### Use [semaphore](https://pkg.go.dev/golang.org/x/sync/semaphore) when need to implement WorkerPool ### Use [semaphore](https://pkg.go.dev/golang.org/x/sync/semaphore) when need to implement WorkerPool
Please don't use external libs for WorkerPool, I don't want to deal with dependency hell. Please don't use external libs for WorkerPool, I don't want to deal with
dependency hell.
### Use [sync.Pool](https://pkg.go.dev/sync#Pool) when need to reuse object, mainly for `bytes.Buffer` ### Use [sync.Pool](https://pkg.go.dev/sync#Pool) when need to reuse object, mainly for `bytes.Buffer`
@ -184,7 +194,8 @@ func MarshalWithoutEscapeHTML(v any) ([]byte, error) {
### No need `vendor` ### No need `vendor`
Only need if you need something from `vendor`, to generate mock or something else. Only need if you need something from `vendor`, to generate mock or something
else.
### Use `build.go` to include build tools in go.mod ### Use `build.go` to include build tools in go.mod
@ -215,21 +226,26 @@ Future contributors will not cry anymore.
### Don't use cli libs ([spf13/cobra](https://github.com/spf13/cobra), [urfave/cli](https://github.com/urfave/cli)) just for Go service ### Don't use cli libs ([spf13/cobra](https://github.com/spf13/cobra), [urfave/cli](https://github.com/urfave/cli)) just for Go service
What is the point to pass many params (`do-it`, `--abc`, `--xyz`) when what we only need is start service? What is the point to pass many params (`do-it`, `--abc`, `--xyz`) when what we
only need is start service?
In my case, service starts with only config, and config should be read from file or environment like [The Twelve Factors](https://12factor.net/) guide. In my case, service starts with only config, and config should be read from file
or environment like [The Twelve Factors](https://12factor.net/) guide.
### Don't use [grpc-ecosystem/grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway) ### Don't use [grpc-ecosystem/grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway)
Just don't. Just don't.
Use [protocolbuffers/protobuf-go](https://github.com/protocolbuffers/protobuf-go), [grpc/grpc-go](https://github.com/grpc/grpc-go) for gRPC. Use
[protocolbuffers/protobuf-go](https://github.com/protocolbuffers/protobuf-go),
[grpc/grpc-go](https://github.com/grpc/grpc-go) for gRPC.
Write 1 for both gRPC, REST sounds good, but in the end, it is not worth it. Write 1 for both gRPC, REST sounds good, but in the end, it is not worth it.
### Don't use [uber/prototool](https://github.com/uber/prototool), use [bufbuild/buf](https://github.com/bufbuild/buf) ### Don't use [uber/prototool](https://github.com/uber/prototool), use [bufbuild/buf](https://github.com/bufbuild/buf)
prototool is deprecated, and buf can generate, lint, format as good as prototool. prototool is deprecated, and buf can generate, lint, format as good as
prototool.
### Use [gin-gonic/gin](https://github.com/gin-gonic/gin) for REST. ### Use [gin-gonic/gin](https://github.com/gin-gonic/gin) for REST.
@ -252,53 +268,57 @@ defer func() {
It is fast! It is fast!
- Don't overuse `func (*Logger) With`. Because if log line is too long, there is a possibility that we can lost it. - Don't overuse `func (*Logger) With`. Because if log line is too long, there is
- Use `MarshalLogObject` when we need to hide some field of object when log (field is long or has sensitive value) a possibility that we can lost it.
- Don't use `Panic`. Use `Fatal` for errors when start service to check dependencies. If you really need panic level, use `DPanic`. - Use `MarshalLogObject` when we need to hide some field of object when log
(field is long or has sensitive value)
- Don't use `Panic`. Use `Fatal` for errors when start service to check
dependencies. If you really need panic level, use `DPanic`.
- If doubt, use `zap.Any`. - If doubt, use `zap.Any`.
- Use `contextID` or `traceID` in every log lines for easily debug. - Use `contextID` or `traceID` in every log lines for easily debug.
### To read config, use [spf13/viper](https://github.com/spf13/viper) ### To read config, use [spf13/viper](https://github.com/spf13/viper)
Only init config in main or cmd layer. Only init config in main or cmd layer. Do not use `viper.Get...` in business
Do not use `viper.Get...` in business layer or inside business layer. layer or inside business layer.
Why? Why?
- Hard to mock and test - Hard to mock and test
- Put all config in single place for easily tracking - Put all config in single place for easily tracking
Also, be careful if config value is empty. Also, be careful if config value is empty. You should decide to continue or stop
You should decide to continue or stop the service if there is empty config. the service if there is empty config.
### Don't overuse ORM libs, no need to handle another layer above SQL. ### Don't overuse ORM libs, no need to handle another layer above SQL.
Each ORM libs has each different syntax. Each ORM libs has each different syntax. To learn and use those libs correctly
To learn and use those libs correctly is time consuming. is time consuming. So just stick to plain SQL. It is easier to debug when
So just stick to plain SQL. something is wrong.
It is easier to debug when something is wrong.
Also please use [prepared statement](https://go.dev/doc/database/prepared-statements) as much as possible. Also please use
Idealy, we should init all prepared statement when we init database connection to cached it, not create it every time we need it. [prepared statement](https://go.dev/doc/database/prepared-statements) as much as
possible. Idealy, we should init all prepared statement when we init database
connection to cached it, not create it every time we need it.
But `database/sql` has its own limit. But `database/sql` has its own limit. For example, it is hard to get primary key
For example, it is hard to get primary key after insert/update. after insert/update. So may be you want to use ORM for those cases. I hear that
So may be you want to use ORM for those cases. [go-gorm/gorm](https://github.com/go-gorm/gorm),
I hear that [go-gorm/gorm](https://github.com/go-gorm/gorm), [ent/ent](https://github.com/ent/ent) is good. [ent/ent](https://github.com/ent/ent) is good.
### Connect Redis with [redis/go-redis](https://github.com/redis/go-redis) ### Connect Redis with [redis/go-redis](https://github.com/redis/go-redis)
Be careful when use [HGETALL](https://redis.io/commands/hgetall/). Be careful when use [HGETALL](https://redis.io/commands/hgetall/). If key not
If key not found, empty data will be returned not nil error. found, empty data will be returned not nil error. See
See [redis/go-redis/issues/1668](https://github.com/redis/go-redis/issues/1668) [redis/go-redis/issues/1668](https://github.com/redis/go-redis/issues/1668)
Use [Pipelines](https://redis.uptrace.dev/guide/go-redis-pipelines.html) for: Use [Pipelines](https://redis.uptrace.dev/guide/go-redis-pipelines.html) for:
- HSET and EXPIRE in 1 command. - HSET and EXPIRE in 1 command.
- Multiple GET in 1 command. - Multiple GET in 1 command.
Prefer to use `Pipelined` instead of `Pipeline`. Prefer to use `Pipelined` instead of `Pipeline`. Inside `Pipelined`, please
Inside `Pipelined`, please return `redis.Cmder` for each command. return `redis.Cmder` for each command.
Example: Example:
@ -355,22 +375,24 @@ Remember to config:
- Write-Ahead Logging: `PRAGMA journal_mode=WAL` - Write-Ahead Logging: `PRAGMA journal_mode=WAL`
- Disable connections pool with `SetMaxOpenConns` sets to 1 - Disable connections pool with `SetMaxOpenConns` sets to 1
Don't use [mattn/go-sqlite3](https://github.com/mattn/go-sqlite3), it's required `CGO_ENABLED`. Don't use [mattn/go-sqlite3](https://github.com/mattn/go-sqlite3), it's required
`CGO_ENABLED`.
### Connect Kafka with [Shopify/sarama](https://github.com/Shopify/sarama) ### Connect Kafka with [Shopify/sarama](https://github.com/Shopify/sarama)
Don't use [confluentinc/confluent-kafka-go](https://github.com/confluentinc/confluent-kafka-go), it's required `CGO_ENABLED`. Don't use
[confluentinc/confluent-kafka-go](https://github.com/confluentinc/confluent-kafka-go),
it's required `CGO_ENABLED`.
### If you want test, just use [stretchr/testify](https://github.com/stretchr/testify). ### If you want test, just use [stretchr/testify](https://github.com/stretchr/testify).
It is easy to write a suite test, thanks to testify. It is easy to write a suite test, thanks to testify. Also, for mocking, there
Also, for mocking, there are many options out there. are many options out there. Pick 1 then sleep peacefully.
Pick 1 then sleep peacefully.
### If need to mock, choose [matryer/moq](https://github.com/matryer/moq) or [uber/mock](https://github.com/uber/mock) ### If need to mock, choose [matryer/moq](https://github.com/matryer/moq) or [uber/mock](https://github.com/uber/mock)
The first is easy to use but not powerful as the later. The first is easy to use but not powerful as the later. If you want to make sure
If you want to make sure mock func is called with correct times, use the later. mock func is called with correct times, use the later.
Example with `matryer/moq`: Example with `matryer/moq`:
@ -414,7 +436,8 @@ stringer -type=Drink
### Don't waste your time rewrite rate limiter if your use case is simple, use [rate](https://pkg.go.dev/golang.org/x/time/rate) or [go-redis/redis_rate](https://github.com/go-redis/redis_rate) ### Don't waste your time rewrite rate limiter if your use case is simple, use [rate](https://pkg.go.dev/golang.org/x/time/rate) or [go-redis/redis_rate](https://github.com/go-redis/redis_rate)
**rate** if you want rate limiter locally in your single instance of service. **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. **redis_rate** if you want rate limiter distributed across all your instances of
service.
### Replace `go fmt`, `goimports` with [mvdan/gofumpt](https://github.com/mvdan/gofumpt). ### Replace `go fmt`, `goimports` with [mvdan/gofumpt](https://github.com/mvdan/gofumpt).
@ -422,10 +445,11 @@ stringer -type=Drink
### Use [golangci/golangci-lint](https://github.com/golangci/golangci-lint). ### Use [golangci/golangci-lint](https://github.com/golangci/golangci-lint).
No need to say more. No need to say more. Lint or get the f out!
Lint or get the f out!
If you get `fieldalignment` error, use [fieldalignment](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment) to fix them. If you get `fieldalignment` error, use
[fieldalignment](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/fieldalignment)
to fix them.
```sh ```sh
# Install # Install

View File

@ -1,9 +1,10 @@
# UUID or else # UUID or else
There are many use cases where we need to use a unique ID. There are many use cases where we need to use a unique ID. In my experience, I
In my experience, I only encouter 2 cases: only encouter 2 cases:
- ID to trace request from client to server, from service to service (microservice architecture or nanoservice I don't know). - ID to trace request from client to server, from service to service
(microservice architecture or nanoservice I don't know).
- Primary key for database. - Primary key for database.
In my Go universe, there are some libs to help us with this: In my Go universe, there are some libs to help us with this:
@ -15,24 +16,26 @@ In my Go universe, there are some libs to help us with this:
## First use case is trace ID, or context aware ID ## First use case is trace ID, or context aware ID
The ID is used only for trace and log. The ID is used only for trace and log. If same ID is generated twice (because
If same ID is generated twice (because maybe the possibilty is too small but not 0), honestly I don't care. maybe the possibilty is too small but not 0), honestly I don't care. When I use
When I use that ID to search log , if it pops more than things I care for, it is still no harm to me. that ID to search log , if it pops more than things I care for, it is still no
harm to me.
My choice for this use case is **rs/xid**. My choice for this use case is **rs/xid**. Because it is small (not span too
Because it is small (not span too much on log line) and copy friendly. much on log line) and copy friendly.
## Second use case is primary key, also hard choice ## Second use case is primary key, also hard choice
Why I don't use auto increment key for primary key? Why I don't use auto increment key for primary key? The answer is simple, I
The answer is simple, I don't want to write database specific SQL. don't want to write database specific SQL. SQLite has some different syntax from
SQLite has some different syntax from MySQL, and PostgreSQL and so on. MySQL, and PostgreSQL and so on. Every logic I can move to application layer
Every logic I can move to application layer from database layer, I will. from database layer, I will.
In the past and present, I use **google/uuid**, specificially I use UUID v4. In the past and present, I use **google/uuid**, specificially I use UUID v4. In
In the future I will look to use **segmentio/ksuid** and **oklog/ulid** (trial and error of course). the future I will look to use **segmentio/ksuid** and **oklog/ulid** (trial and
Both are sortable, but **google/uuid** is not. error of course). Both are sortable, but **google/uuid** is not. The reason I'm
The reason I'm afraid because the database is sensitive subject, and I need more testing and battle test proof to trust those libs. afraid because the database is sensitive subject, and I need more testing and
battle test proof to trust those libs.
## What else? ## What else?

View File

@ -4,7 +4,8 @@ Why? Because `prototool` is outdated, and can not run on M1 mac.
We need 3 files: We need 3 files:
- `build.go`: need to install protoc-gen-\* binaries with pin version in `go.mod` - `build.go`: need to install protoc-gen-\* binaries with pin version in
`go.mod`
- `buf.yaml` - `buf.yaml`
- `buf.gen.yaml` - `buf.gen.yaml`
@ -84,7 +85,8 @@ gen:
Run `make gen` to have fun of course. Run `make gen` to have fun of course.
If using `bufbuild/protoc-gen-validate`, `kei2100/protoc-gen-marshal-zap`, better make a raw copy of proto file for other services to integrate: If using `bufbuild/protoc-gen-validate`, `kei2100/protoc-gen-marshal-zap`,
better make a raw copy of proto file for other services to integrate:
```Makefile ```Makefile
raw: raw:
@ -98,7 +100,9 @@ raw:
## FAQ ## FAQ
Remember `bufbuild/protoc-gen-validate`, `kei2100/protoc-gen-marshal-zap`, `grpc-ecosystem/grpc-gateway` is optional, so feel free to delete if you don't use theme. Remember `bufbuild/protoc-gen-validate`, `kei2100/protoc-gen-marshal-zap`,
`grpc-ecosystem/grpc-gateway` is optional, so feel free to delete if you don't
use theme.
If use `vendor`: If use `vendor`:
@ -107,24 +111,29 @@ If use `vendor`:
If you use grpc-gateway: If you use grpc-gateway:
- Replace `import "third_party/googleapis/google/api/annotations.proto";` with `import "google/api/annotations.proto";` - Replace `import "third_party/googleapis/google/api/annotations.proto";` with
- Delete `security_definitions`, `security`, in `option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger)`. `import "google/api/annotations.proto";`
- Delete `security_definitions`, `security`, in
`option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger)`.
The last step is delete `prototool.yaml`. The last step is delete `prototool.yaml`.
If you are not migrate but start from scratch: If you are not migrate but start from scratch:
- Add `buf lint` to make sure your proto is good. - Add `buf lint` to make sure your proto is good.
- Add `buf breaking --against "https://your-grpc-repo-goes-here.git"` to make sure each time you update proto, you don't break backward compatibility. - Add `buf breaking --against "https://your-grpc-repo-goes-here.git"` to make
sure each time you update proto, you don't break backward compatibility.
# Tips # Tips
Some experience I got after writing proto files for a living: Some experience I got after writing proto files for a living:
- Ignore DRY (Do not Repeat Yourself) when handling proto, don't split proto into many files. - Ignore DRY (Do not Repeat Yourself) when handling proto, don't split proto
Trust me, it saves you from wasting time to debug how to import Go after generated. into many files. Trust me, it saves you from wasting time to debug how to
Because proto import and Go import is [2](https://github.com/golang/protobuf/issues/895) different things. import Go after generated. Because proto import and Go import is
If someone already have split proto files, you should use `sed` to fix the damn things. [2](https://github.com/golang/protobuf/issues/895) different things. If
someone already have split proto files, you should use `sed` to fix the damn
things.
## Thanks ## Thanks

View File

@ -1,7 +1,7 @@
# Experiment Go # Experiment Go
There come a time when you need to experiment new things, new style, new approach. There come a time when you need to experiment new things, new style, new
So this post serves as it is named. approach. So this post serves as it is named.
# Design API by trimming down the interface/struct or whatever # Design API by trimming down the interface/struct or whatever
@ -45,12 +45,12 @@ c.Account.Remove()
The difference is `c.GetUser()` -> `c.User.Get()`. The difference is `c.GetUser()` -> `c.User.Get()`.
For example we have client which connect to bank. For example we have client which connect to bank. There are many functions like
There are many functions like `GetUser`, `GetTransaction`, `VerifyAccount`, ... `GetUser`, `GetTransaction`, `VerifyAccount`, ... So split big client to many
So split big client to many children, each child handle single aspect, like user or transaction. children, each child handle single aspect, like user or transaction.
My concert is we replace an interface with a struct which contains multiple interfaces aka children. My concert is we replace an interface with a struct which contains multiple
I don't know if this is the right call. interfaces aka children. I don't know if this is the right call.
This pattern is used by [google/go-github](https://github.com/google/go-github). This pattern is used by [google/go-github](https://github.com/google/go-github).
@ -61,12 +61,14 @@ Why?
Also read: Also read:
- [A new Go API for Protocol Buffers](https://go.dev/blog/protobuf-apiv2) to know why `v1.20.0` is `v2`. - [A new Go API for Protocol Buffers](https://go.dev/blog/protobuf-apiv2) to
know why `v1.20.0` is `v2`.
- [Go Protobuf Plugin Versioning](https://jbrandhorst.com/post/plugin-versioning/). - [Go Protobuf Plugin Versioning](https://jbrandhorst.com/post/plugin-versioning/).
Currently there are some: Currently there are some:
- [bufbuild/connect-go](https://github.com/bufbuild/connect-go). Comming from buf, trust worthy but need time to make it match feature parity with grpc-go. - [bufbuild/connect-go](https://github.com/bufbuild/connect-go). Comming from
buf, trust worthy but need time to make it match feature parity with grpc-go.
- [twitchtv/twirp](https://github.com/twitchtv/twirp) - [twitchtv/twirp](https://github.com/twitchtv/twirp)
- [storj/drpc](https://github.com/storj/drpc) - [storj/drpc](https://github.com/storj/drpc)

View File

@ -1,28 +1,28 @@
# SQL # SQL
Previously in my fresher software developer time, I rarely write SQL, I always use ORM to wrap SQL. Previously in my fresher software developer time, I rarely write SQL, I always
But time past and too much abstraction bites me. use ORM to wrap SQL. But time past and too much abstraction bites me. So I
So I decide to only write SQL from now as much as possible, no more ORM for me. decide to only write SQL from now as much as possible, no more ORM for me. But
But if there is any cool ORM for Go, I guess I try. if there is any cool ORM for Go, I guess I try.
This guide is not kind of guide which cover all cases. This guide is not kind of guide which cover all cases. Just my little tricks
Just my little tricks when I work with SQL. when I work with SQL.
## Stay away from database unique id ## Stay away from database unique id
Use UUID instead. Use UUID instead. If you can, and you should, choose UUID type which can be
If you can, and you should, choose UUID type which can be sortable. sortable.
## Stay away from database timestamp ## Stay away from database timestamp
Stay away from all kind of database timestamp (MySQL timestmap, SQLite timestamp, ...) Stay away from all kind of database timestamp (MySQL timestmap, SQLite
Just use int64 then pass the timestamp in service layer not database layer. timestamp, ...) Just use int64 then pass the timestamp in service layer not
database layer.
Why? Because time and date and location are too much complex to handle. Why? Because time and date and location are too much complex to handle. In my
In my business, I use timestamp in milliseconds. business, I use timestamp in milliseconds. Then I save timestamp as int64 value
Then I save timestamp as int64 value to database. to database. Each time I get timestamp from database, I parse to time struct in
Each time I get timestamp from database, I parse to time struct in Go with location or format I want. Go with location or format I want. No more hassle!
No more hassle!
It looks like this: It looks like this:
@ -32,9 +32,9 @@ It looks like this:
## Extra field for extra things ## Extra field for extra things
Create new column in database is scary, so I suggest avoid it if you can. Create new column in database is scary, so I suggest avoid it if you can. How to
How to avoid, first design table with extra field. avoid, first design table with extra field. It is black hole, put everything in
It is black hole, put everything in there if you want. there if you want.
I always use MySQL json data type for extra field. I always use MySQL json data type for extra field.
@ -42,9 +42,8 @@ JSON data type also used for dumping request, response data.
## Use index!!! ## Use index!!!
You should use index for faster query, but not too much. You should use index for faster query, but not too much. Don't create index for
Don't create index for every fields in table. every fields in table. Choose wisely!
Choose wisely!
For example, create index in MySQL: For example, create index in MySQL:
@ -53,7 +52,8 @@ CREATE INDEX idx_user_id
ON user_upload (user_id); ON user_upload (user_id);
``` ```
If create index inside `CREATE TABLE`, [prefer `INDEX` to `KEY`](https://stackoverflow.com/a/1401615): If create index inside `CREATE TABLE`,
[prefer `INDEX` to `KEY`](https://stackoverflow.com/a/1401615):
```sql ```sql
CREATE TABLE user_upload CREATE TABLE user_upload
@ -92,8 +92,9 @@ Need clarify why this happpen? Idk :(
## `VARCHAR` or `TEXT` ## `VARCHAR` or `TEXT`
Prefer `VARCHAR` if you need to query and of course use index, and make sure size of value will never hit the limit. Prefer `VARCHAR` if you need to query and of course use index, and make sure
Prefer `TEXT` if you don't care, just want to store something. size of value will never hit the limit. Prefer `TEXT` if you don't care, just
want to store something.
## `LIMIT` ## `LIMIT`
@ -101,15 +102,20 @@ Prefer `LIMIT 10 OFFSET 5` to `LIMIT 5, 10` to avoid misunderstanding.
## Be super careful when migrate, update database on production and online!!! ## Be super careful when migrate, update database on production and online!!!
Plase read docs about online ddl operations before do anything online (keep database running the same time update it, for example create index, ...) Plase read docs about online ddl operations before do anything online (keep
database running the same time update it, for example create index, ...)
- [For MySQL 5.7](https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html), [Limitations](https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-limitations.html) - [For MySQL 5.7](https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-operations.html),
- [For MySQL 8.0](https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html), [Limitations](https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-limitations.html) [Limitations](https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl-limitations.html)
- [For MySQL 8.0](https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html),
[Limitations](https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-limitations.html)
## Tools ## Tools
- Use [sqlfluff/sqlfluff](https://github.com/sqlfluff/sqlfluff) to check your SQL. - Use [sqlfluff/sqlfluff](https://github.com/sqlfluff/sqlfluff) to check your
- Use [k1LoW/tbls](https://github.com/k1LoW/tbls) to grasp your database reality :) SQL.
- Use [k1LoW/tbls](https://github.com/k1LoW/tbls) to grasp your database reality
:)
## Thanks ## Thanks

View File

@ -49,19 +49,23 @@ deactivate other_service
@enduml @enduml
``` ```
Config storage can be any key value storage or database like etcd, Consul, mySQL, ... Config storage can be any key value storage or database like etcd, Consul,
mySQL, ...
If storage is key value storage, maybe there is API to listen on config change. 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. Otherwise we should create a loop to get all config from storage for some
interval, for example each 5 minute.
Each `other_service` need to get config from its memory, not hit `storage`. Each `other_service` need to get config from its memory, not hit `storage`. So
So there is some delay between upstream config (config in `storage`) and downstream config (config in `other_service`), but maybe we can forgive that delay (???). there is some delay between upstream config (config in `storage`) and downstream
config (config in `other_service`), but maybe we can forgive that delay (???).
Pros: Pros:
- Config can be dynamic, service does not need to restart to apply new config. - Config can be dynamic, service does not need to restart to apply new config.
- Each service only keep 1 connection to `storage` to listen to config change, not hit `storage` for each request. - Each service only keep 1 connection to `storage` to listen to config change,
not hit `storage` for each request.
Cons: Cons:

View File

@ -1,8 +1,10 @@
# Install Arch Linux # Install Arch Linux
Install Arch Linux is thing I always want to do for my laptop/PC since I had my laptop in ninth grade. Install Arch Linux is thing I always want to do for my laptop/PC since I had my
laptop in ninth grade.
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. 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.
## [Installation guide](https://wiki.archlinux.org/index.php/Installation_guide) ## [Installation guide](https://wiki.archlinux.org/index.php/Installation_guide)
@ -46,8 +48,8 @@ UEFI/GPT layout:
| `/mnt/boot` | `/dev/extended_boot_loader_partition` | Extended Boot Loader Partition | 1 GiB | | `/mnt/boot` | `/dev/extended_boot_loader_partition` | Extended Boot Loader Partition | 1 GiB |
| `/mnt` | `/dev/root_partition` | Root Partition | | | `/mnt` | `/dev/root_partition` | Root Partition | |
Why not `/boot/efi`? Why not `/boot/efi`? See
See [Lennart Poettering comment](https://github.com/systemd/systemd/pull/3757#issuecomment-234290236). [Lennart Poettering comment](https://github.com/systemd/systemd/pull/3757#issuecomment-234290236).
BIOS/GPT layout: BIOS/GPT layout:
@ -338,9 +340,8 @@ homectl create joker --real-name="The Joker" --member-of=wheel
homectl update joker --shell=/usr/bin/zsh homectl update joker --shell=/usr/bin/zsh
``` ```
**Note**: **Note**: Can not run `homectl` when install Arch Linux. Should run on the first
Can not run `homectl` when install Arch Linux. boot.
Should run on the first boot.
### Desktop Environment ### Desktop Environment
@ -384,7 +385,8 @@ pacman -Syu pipewire wireplumber \
gst-plugin-pipewire pipewire-v4l2 gst-plugin-pipewire pipewire-v4l2
``` ```
See [Advanced Linux Sound Architecture](https://wiki.archlinux.org/title/Advanced_Linux_Sound_Architecture) See
[Advanced Linux Sound Architecture](https://wiki.archlinux.org/title/Advanced_Linux_Sound_Architecture)
```sh ```sh
pacman -Syu sof-firmware pacman -Syu sof-firmware

View File

@ -1,42 +1,47 @@
# Speed up writing Go test ASAP # Speed up writing Go test ASAP
Imagine your project currently have 0% unit test code coverage. Imagine your project currently have 0% unit test code coverage. And your boss
And your boss keep pushing it to 80% or even 90%? keep pushing it to 80% or even 90%? What do you do? Give up?
What do you do?
Give up?
What if I tell you there is a way? What if I tell you there is a way? Not entirely cheating but ... you know, there
Not entirely cheating but ... you know, there is always trade off. is always trade off.
If your purpose is to test carefully all path, check if all return is correctly. 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
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 :) coverage, with minimum effort as possible, I hope this will show you some idea
you can use :)
In my opinion, unit test is not that important (like must must have). In my opinion, unit test is not that important (like must must have). It's just
It's just make sure your code is running excatly as you intent it to be. make sure your code is running excatly as you intent it to be. If you don't
If you don't think about edge case before, unit test won't help you. think about edge case before, unit test won't help you.
## First, rewrite the impossible (to test) out ## First, rewrite the impossible (to test) out
When I learn programming, I encounter very interesting idea, which become mainly my mindset when I dev later. When I learn programming, I encounter very interesting idea, which become mainly
I don't recall it clearly, kinda like: "Don't just fix bugs, rewrite it so that kind of bugs will not appear again". my mindset when I dev later. I don't recall it clearly, kinda like: "Don't just
So in our context, there is some thing we hardly or can not write test in Go. fix bugs, rewrite it so that kind of bugs will not appear again". So in our
My suggestion is don't use that thing. context, there is some thing we hardly or can not write test in Go. My
suggestion is don't use that thing.
In my experience, I can list a few here: In my experience, I can list a few here:
- Read config each time call func (`viper.Get...`). You can and you should init all config when project starts. - Read config each time call func (`viper.Get...`). You can and you should init
- Not use Dependency Injection (DI). There are too many posts in Internet tell you how to do DI properly. all config when project starts.
- Use global var (Except global var `Err...`). You should move all global var to fields inside some struct. - Not use Dependency Injection (DI). There are too many posts in Internet tell
you how to do DI properly.
- Use global var (Except global var `Err...`). You should move all global var to
fields inside some struct.
## Let the fun (writing test) begin ## Let the fun (writing test) begin
If you code Go long enough, you know table driven tests and how is that so useful. If you code Go long enough, you know table driven tests and how is that so
You set up test data, then you test. useful. You set up test data, then you test. Somewhere in the future, you change
Somewhere in the future, you change the func, then you need to update test data, then you good! the func, then you need to update test data, then you good!
In simple case, your func only have 2 or 3 inputs so table drive tests is still looking good. In simple case, your func only have 2 or 3 inputs so table drive tests is still
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. looking good. But real world is ugly (maybe not, idk I'm just too young in this
industry). Your func can have 5 or 10 inputs, also your func call many third
party services.
Imagine having below func to upload image: Imagine having below func to upload image:
@ -75,7 +80,9 @@ func (s *service) Upload(ctx context.Context, req Request) error {
} }
``` ```
With table driven test and thanks to [stretchr/testify](https://github.com/stretchr/testify), I usually write like this: With table driven test and thanks to
[stretchr/testify](https://github.com/stretchr/testify), I usually write like
this:
```go ```go
type ServiceSuite struct { type ServiceSuite struct {
@ -126,15 +133,12 @@ func (s *ServiceSuite) TestUpload() {
} }
``` ```
Looks good right? Looks good right? Be careful with this. It can go from 0 to 100 ugly real quick.
Be careful with this.
It can go from 0 to 100 ugly real quick.
What if req is a struct with many fields? What if req is a struct with many fields? So in each test case you need to set
So in each test case you need to set up req. 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 ?). Also all req looks
It's easy to be init with wrong value here (typing maybe ?). similiar, kinda duplicated.
Also all req looks similiar, kinda duplicated.
```go ```go
tests := []struct{ tests := []struct{
@ -186,40 +190,43 @@ tests := []struct{
} }
``` ```
What if dependencies of service keep growing? What if dependencies of service keep growing? More mock error to test data of
More mock error to test data of course. course.
```go ```go
tests := []struct{ tests := []struct{
name string name string
req Request req Request
verifyErr error verifyErr error
minioErr error minioErr error
redisErr error redisErr error
dbErr error dbErr error
logErr error logErr error
wantErr error wantErr error
// Murr error // Murr error
aErr error aErr error
bErr error bErr error
cErr error cErr error
// ... // ...
}{ }{
{ {
// Init test case // Init test case
}
} }
}
``` ```
The test file keep growing longer and longer until I feel sick about it. The test file keep growing longer and longer until I feel sick about it.
See [tektoncd/pipeline unit test](https://github.com/tektoncd/pipeline/blob/main/pkg/pod/pod_test.go) to get a feeling about this. See
When I see it, `TestPodBuild` has almost 2000 lines. [tektoncd/pipeline unit test](https://github.com/tektoncd/pipeline/blob/main/pkg/pod/pod_test.go)
to get a feeling about this. When I see it, `TestPodBuild` has almost 2000
lines.
The solution I propose here is simple (absolutely not perfect, but good with my usecase) thanks to **stretchr/testify**. The solution I propose here is simple (absolutely not perfect, but good with my
I init all **default** action on **success** case. usecase) thanks to **stretchr/testify**. I init all **default** action on
Then I **alter** request or mock error for unit test to hit on other case. **success** case. Then I **alter** request or mock error for unit test to hit on
Remember if unit test is hit, code coverage is surely increased, and that my **goal**. other case. Remember if unit test is hit, code coverage is surely increased, and
that my **goal**.
```go ```go
// Init ServiceSuite as above // Init ServiceSuite as above
@ -260,9 +267,11 @@ func (s *ServiceSuite) TestUpload() {
} }
``` ```
If you think this is not quick enough, just **ignore** the response. If you think this is not quick enough, just **ignore** the response. You only
You only need to check error or not if you want code coverage only. need to check error or not if you want code coverage only.
So if request change fields or more dependencies, I need to update success case, and maybe add corresponding error case if need. So if request change fields or more dependencies, I need to update success case,
and maybe add corresponding error case if need.
Same idea but still with table, you can find here [Functional table-driven tests in Go - Fatih Arslan](https://arslan.io/2022/12/04/functional-table-driven-tests-in-go/). Same idea but still with table, you can find here
[Functional table-driven tests in Go - Fatih Arslan](https://arslan.io/2022/12/04/functional-table-driven-tests-in-go/).

View File

@ -6,7 +6,8 @@ This is collect of all incidents I created in the past :(
Because all configs is read from file. Because all configs is read from file.
But the port config is empty -> So when service inits, it use that empty port somehow. But the port config is empty -> So when service inits, it use that empty port
somehow.
**Solution**: For some configs, make sure to failed first if it's empty. **Solution**: For some configs, make sure to failed first if it's empty.
@ -17,24 +18,24 @@ For example I have 2 APIs:
- API upload: allow user to upload image - API upload: allow user to upload image
- API submit: submit data to server - API submit: submit data to server
API upload is slow, it takes 10s to finish. API upload is slow, it takes 10s to finish. API submit is fast, only takes 2s.
API submit is fast, only takes 2s.
The problem is submit use data from upload too. The problem is submit use data from upload too. When user calls API upload,
When user calls API upload, image is stored in cache. image is stored in cache. When user calls API submit, it use whatever image is
When user calls API submit, it use whatever image is stored in cache. stored in cache.
It's when the fun begins. It's when the fun begins.
Imagine user Trong already upload image. Imagine user Trong already upload image. So he is ready to submit. But for the
So he is ready to submit. same time, he re-call API upload to upload another image too.
But for the same time, he re-call API upload to upload another image too.
So if API upload is finished first, which is kinda impossible (U know upload file is not fast right?), everything right. So if API upload is finished first, which is kinda impossible (U know upload
But for most cases, API submit is finished first. file is not fast right?), everything right. But for most cases, API submit is
It means Trong's data is submitted with the old image. finished first. It means Trong's data is submitted with the old image. Then API
Then API upload is finished, it will replace the old image with the new one.So the old one, aka image in submitted data, is gone. upload is finished, it will replace the old image with the new one.So the old
one, aka image in submitted data, is gone.
Chaos right there! Chaos right there!
**Solution**: Use a lock, if user enter API upload, lock it to prevent user call other APIs. Rememeber to unlock after finished **Solution**: Use a lock, if user enter API upload, lock it to prevent user call
other APIs. Rememeber to unlock after finished

View File

@ -1,22 +1,21 @@
# Fonts # Fonts
I always want the best fonts for my terminal, my text editor, my ... I always want the best fonts for my terminal, my text editor, my ... But I'm not
But I'm not satisfied easily, so I keep trying new fonts. satisfied easily, so I keep trying new fonts. Prefer free fonts of course :D
Prefer free fonts of course :D
TLDR: TLDR:
If use macOS, use [San Francisco](https://developer.apple.com/fonts/) for everything. If use macOS, use [San Francisco](https://developer.apple.com/fonts/) for
Remember each time macOS release new version/new update, everything. Remember each time macOS release new version/new update, you should
you should download again to get (maybe) latest version. download again to get (maybe) latest version.
Otherwise: Otherwise:
- Use [JetBrains Mono](https://github.com/JetBrains/JetBrainsMono) for code. - Use [JetBrains Mono](https://github.com/JetBrains/JetBrainsMono) for code.
- Use [Inter](https://github.com/rsms/inter) for everything else. - Use [Inter](https://github.com/rsms/inter) for everything else.
All images belows is either official images I got from fonts website or my own screenshots. All images belows is either official images I got from fonts website or my own
I'm too lazy to screenshot anw :D screenshots. I'm too lazy to screenshot anw :D
## [JetBrains Mono](https://github.com/JetBrains/JetBrainsMono) ## [JetBrains Mono](https://github.com/JetBrains/JetBrainsMono)
@ -28,15 +27,16 @@ I'm too lazy to screenshot anw :D
## [Iosevka](https://github.com/be5invis/Iosevka) ## [Iosevka](https://github.com/be5invis/Iosevka)
I often choose SS08 variant because I also love [PragmataPro](https://fsd.it/shop/fonts/pragmatapro/) too. I often choose SS08 variant because I also love
[PragmataPro](https://fsd.it/shop/fonts/pragmatapro/) too.
- Support Vietnamese - Support Vietnamese
- Support bold, italic - Support bold, italic
- Support ligatures - Support ligatures
- Support display font (non mono) - Support display font (non mono)
Font is narrow, can display much more on small screen. Font is narrow, can display much more on small screen. But too much
But too much config/variant for ligatures scares me. config/variant for ligatures scares me.
![img01](https://raw.githubusercontent.com/be5invis/Iosevka/v21.1.1/images/iosevka-ss08.dark.svg#gh-dark-mode-only) ![img01](https://raw.githubusercontent.com/be5invis/Iosevka/v21.1.1/images/iosevka-ss08.dark.svg#gh-dark-mode-only)
@ -60,8 +60,8 @@ Looks good on my phone.
- No italic - No italic
- No ligatures - No ligatures
Font is wide, remember to edit line height to make it looks good. Font is wide, remember to edit line height to make it looks good. I like its
I like its bold, strong look. bold, strong look.
![img02](https://github.com/evilmartians/mono/raw/main/documentation/martian-mono-character-set.png) ![img02](https://github.com/evilmartians/mono/raw/main/documentation/martian-mono-character-set.png)
@ -72,8 +72,7 @@ I like its bold, strong look.
- No italic - No italic
- No ligatures - No ligatures
Font is small, can display much more on small screen. Font is small, can display much more on small screen. I like its curved look.
I like its curved look.
## [Hermit](https://github.com/pcaro90/hermit) ## [Hermit](https://github.com/pcaro90/hermit)
@ -97,18 +96,19 @@ I love its wide look, also it's feel nostalgic.
- No bold, italic - No bold, italic
- No ligatures - No ligatures
Feel 8-bit vibe, mono to death. Feel 8-bit vibe, mono to death. But sometimes it's hard to read.
But sometimes it's hard to read.
![img03](https://github.com/slavfox/Cozette/raw/master/img/sample.png) ![img03](https://github.com/slavfox/Cozette/raw/master/img/sample.png)
## Murr fonts, but I don't use much ## Murr fonts, but I don't use much
- [Fira Code](https://github.com/tonsky/FiraCode): best ligatures. - [Fira Code](https://github.com/tonsky/FiraCode): best ligatures.
- [Cascadia Code](https://github.com/microsoft/cascadia-code): seems discontinued :(. - [Cascadia Code](https://github.com/microsoft/cascadia-code): seems
discontinued :(.
- [Input](https://input.djr.com/): seems discontinued :(. - [Input](https://input.djr.com/): seems discontinued :(.
- [Monoid](https://github.com/larsenwork/monoid): seems discontinued :(. - [Monoid](https://github.com/larsenwork/monoid): seems discontinued :(.
- [Fantasque Sans Mono](https://github.com/belluzj/fantasque-sans): Comic fonts vibe, seems discontinued :(. - [Fantasque Sans Mono](https://github.com/belluzj/fantasque-sans): Comic fonts
vibe, seems discontinued :(.
- [mononoki](https://github.com/madmalik/mononoki): share same vibe with agave. - [mononoki](https://github.com/madmalik/mononoki): share same vibe with agave.
"Costing money" fonts, but I like it, will buy it if I have money: "Costing money" fonts, but I like it, will buy it if I have money:

View File

@ -1,7 +1,7 @@
# Games 4 fun # Games 4 fun
Just a little note about apps, games, settings for next time playing :D Just a little note about apps, games, settings for next time playing :D Please
Please have fun, of course :D have fun, of course :D
I have tested all software below on: I have tested all software below on:
@ -11,8 +11,8 @@ If below links die, I will try to scrape Internet to get a new link.
## PS2 emulator ## PS2 emulator
I use [PCSX2](https://github.com/PCSX2/pcsx2). I use [PCSX2](https://github.com/PCSX2/pcsx2). Currently it supports macOS on
Currently it supports macOS on nightly builds, but it's good enough. nightly builds, but it's good enough.
![pcsx2-000](https://raw.githubusercontent.com/haunt98/posts-images/main/pcsx2-000.png) ![pcsx2-000](https://raw.githubusercontent.com/haunt98/posts-images/main/pcsx2-000.png)
@ -25,23 +25,28 @@ Should enable cheats:
![pcsx2-001](https://raw.githubusercontent.com/haunt98/posts-images/main/pcsx2-001.png) ![pcsx2-001](https://raw.githubusercontent.com/haunt98/posts-images/main/pcsx2-001.png)
For cover art of games, please use [xlenore/ps2-covers](https://github.com/xlenore/ps2-covers). For cover art of games, please use
[xlenore/ps2-covers](https://github.com/xlenore/ps2-covers).
### [Resident Evil 4](https://wiki.pcsx2.net/Resident_Evil_4) ### [Resident Evil 4](https://wiki.pcsx2.net/Resident_Evil_4)
You can download it [here](https://cdromance.com/ps2-iso/resident-evil-4-usa/). You can download it [here](https://cdromance.com/ps2-iso/resident-evil-4-usa/).
I recommend to use [HD textures](https://gbatemp.net/threads/resident-evil-4-hd-textures-update-2.615869/), it's better for your eyes. I recommend to use
[Direct download link](https://www.mediafire.com/file/eyspelayfqtfz7a/R.4.hd.textures.xXthe.RockoXx.rar/file) if forum dies. [HD textures](https://gbatemp.net/threads/resident-evil-4-hd-textures-update-2.615869/),
Please give thanks to [xXtheRockoXx](https://ko-fi.com/xxtherockoxx) for his work. it's better for your eyes.
[Direct download link](https://www.mediafire.com/file/eyspelayfqtfz7a/R.4.hd.textures.xXthe.RockoXx.rar/file)
if forum dies. Please give thanks to
[xXtheRockoXx](https://ko-fi.com/xxtherockoxx) for his work.
After download HD textures, please extract then copy to PCSX2 texture folder. After download HD textures, please extract then copy to PCSX2 texture folder.
Remember to rename it to serial name (SLUS-21134, ...), because different region has different serial name. Remember to rename it to serial name (SLUS-21134, ...), because different region
has different serial name.
Settings below are for Resident Evil 4 only. Settings below are for Resident Evil 4 only.
If using macOS please switch Graphics/Renderer to Vulkan. If using macOS please switch Graphics/Renderer to Vulkan. For other OS, I
For other OS, I haven't tested yet. haven't tested yet.
In Graphics/Rendering: In Graphics/Rendering:
@ -65,8 +70,8 @@ In Graphics/Post-Processing:
![pcsx2-004](https://raw.githubusercontent.com/haunt98/posts-images/main/pcsx2-004.png) ![pcsx2-004](https://raw.githubusercontent.com/haunt98/posts-images/main/pcsx2-004.png)
For hacking, create file with content below in PCSX2 cheat folder. For hacking, create file with content below in PCSX2 cheat folder. Remember to
Remember to rename it to crc.pnach (013E349D.pnach, ...). rename it to crc.pnach (013E349D.pnach, ...).
```txt ```txt
// Money // Money
@ -88,10 +93,11 @@ Beautiful result!
## PS3 emulator ## PS3 emulator
I use [RPCS3](https://github.com/RPCS3/rpcs3). I use [RPCS3](https://github.com/RPCS3/rpcs3). Currently it supports macOS on
Currently it supports macOS on nightly builds. nightly builds.
Download [PS3 Firmwares](https://www.playstation.com/en-us/support/hardware/ps3/system-software/). Download
[PS3 Firmwares](https://www.playstation.com/en-us/support/hardware/ps3/system-software/).
Links to download games, ... for PS3: Links to download games, ... for PS3:

View File

@ -35,7 +35,8 @@ I bought it from my friend.
- Plate: PC - Plate: PC
- **Gasket mount** - **Gasket mount**
- PCB: DZ60 RGB-WKL Hot-Swap - PCB: DZ60 RGB-WKL Hot-Swap
- **South facing** (mạch xuôi), but 2 switches in the top left, near USB-C port, are **North facing** (mạch ngược). - **South facing** (mạch xuôi), but 2 switches in the top left, near USB-C
port, are **North facing** (mạch ngược).
#### Layout #### Layout
@ -57,14 +58,15 @@ My layout's **quirk/gotcha**:
#### Review #### Review
Things I don't like, also [honest review from Reddit](https://www.reddit.com/r/HHKB/comments/xmcbkq/comment/j1625fy): Things I don't like, also
[honest review from Reddit](https://www.reddit.com/r/HHKB/comments/xmcbkq/comment/j1625fy):
- The sides don't have any gaskets, so the keys on the far left and right bend down more. - The sides don't have any gaskets, so the keys on the far left and right bend
They will pop out of the hotswap PCB or the plate if pressing too hard. down more. They will pop out of the hotswap PCB or the plate if pressing too
hard.
- PCB: - PCB:
- Not all keys are **South facing**. - Not all keys are **South facing**.
- Can not config RGB per key for real. - Can not config RGB per key for real. Only support RGB mode switching.
Only support RGB mode switching.
#### Support links #### Support links
@ -105,9 +107,8 @@ I choose this switch because I prefer linear (please be silent).
### SKYLOONG Glacier Silent Red Switch ### SKYLOONG Glacier Silent Red Switch
I was given this switch by my friend. I was given this switch by my friend. Currently using for alpha keys. Love the
Currently using for alpha keys. silent.
Love the silent.
![keeb-008](https://raw.githubusercontent.com/haunt98/posts-images/main/keeb-008.webp) ![keeb-008](https://raw.githubusercontent.com/haunt98/posts-images/main/keeb-008.webp)
@ -146,8 +147,8 @@ I was given this switch by my friend, full mod (lube + film).
## Keycap ## Keycap
Currently, I use Akko 9009 Cherry Profile and EnjoyPBT 9009 Cherry Profile. Currently, I use Akko 9009 Cherry Profile and EnjoyPBT 9009 Cherry Profile. The
The space of EnjoyPBT 9009 is not straight so I use Akko 9009 space. space of EnjoyPBT 9009 is not straight so I use Akko 9009 space.
I know I know, I love 9009 color too much. I know I know, I love 9009 color too much.

View File

@ -4,13 +4,16 @@
Always have year, month, day in filename to easily sort it out. Always have year, month, day in filename to easily sort it out.
If file is uploaded by user, add `user_id` in filename, or some other unique identifier depends on your business in which you require to upload. If file is uploaded by user, add `user_id` in filename, or some other unique
identifier depends on your business in which you require to upload.
Personally, I always add timestamp and extra data in filename to avoid duplicate. Personally, I always add timestamp and extra data in filename to avoid
duplicate.
Example filename: `yyyy/mm/dd/{user_id}-{timestamp}-{extra}.ext` Example filename: `yyyy/mm/dd/{user_id}-{timestamp}-{extra}.ext`
Be careful with `/`, too much nested folder is no good for backup (as they say, idk if true or not, but less folder mean less complicated to me). Be careful with `/`, too much nested folder is no good for backup (as they say,
idk if true or not, but less folder mean less complicated to me).
## Time variable ## Time variable

View File

@ -6,6 +6,8 @@ All configs are in [my dotfiles](https://github.com/haunt98/dotfiles).
## Trick or treat ## Trick or treat
Search current word: `*`
Search multiple words: Search multiple words:
```vim ```vim
@ -59,7 +61,8 @@ Advance:
- `M`: middle of screen - `M`: middle of screen
- `L`: bottom of screen - `L`: bottom of screen
- `CTRL-]`, `CTRL-T`: jump to tag/jump back from tag - `CTRL-]`, `CTRL-T`: jump to tag/jump back from tag
- Support jump to Go definition with [fatih/vim-go](https://github.com/fatih/vim-go). - Support jump to Go definition with
[fatih/vim-go](https://github.com/fatih/vim-go).
## Keymap ## Keymap
@ -85,7 +88,8 @@ vim.keymap.set("n", "q", ":q<CR>")
- `<Leader>f`: find files - `<Leader>f`: find files
- `<Leader>rg`: grep files - `<Leader>rg`: grep files
- `<Space>s`: find lsp symbols - `<Space>s`: find lsp symbols
- With [nvim-tree/nvim-tree.lua](https://github.com/nvim-tree/nvim-tree.lua), inside nvim-tree: - With [nvim-tree/nvim-tree.lua](https://github.com/nvim-tree/nvim-tree.lua),
inside nvim-tree:
- `<C-n>`: toggle - `<C-n>`: toggle
- `<Leader>n`: locate file - `<Leader>n`: locate file
- `a`: create - `a`: create
@ -106,7 +110,8 @@ vim.keymap.set("n", "q", ":q<CR>")
- `[D`, `]D`, `[d`, `]d`: diagnostic backward/forward - `[D`, `]D`, `[d`, `]d`: diagnostic backward/forward
- `[Q`, `]Q`, `[q`, `]q`: quickfix backward/forward - `[Q`, `]Q`, `[q`, `]q`: quickfix backward/forward
- `[T`, `]T`, `[t`, `]t`: tree-sitter backward/forward - `[T`, `]T`, `[t`, `]t`: tree-sitter backward/forward
- Support more languages with [nvim-treesitter/nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) - Support more languages with
[nvim-treesitter/nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter)
- With mini-comment - With mini-comment
- `gcc`: comment/uncomment current line - `gcc`: comment/uncomment current line
- `gc`: comment/uncomment selected lines - `gc`: comment/uncomment selected lines
@ -116,7 +121,8 @@ vim.keymap.set("n", "q", ":q<CR>")
- `sr`: replace surround - `sr`: replace surround
- With mini-trailspace - With mini-trailspace
- `<Leader>tr`: trim trailing whitespace - `<Leader>tr`: trim trailing whitespace
- With [nvim-treesitter/nvim-treesitter-textobjects](https://github.com/nvim-treesitter/nvim-treesitter-textobjects) - With
[nvim-treesitter/nvim-treesitter-textobjects](https://github.com/nvim-treesitter/nvim-treesitter-textobjects)
- `vif`, `vaf`: select inner/outer function - `vif`, `vaf`: select inner/outer function
- `vic`, `vac`: select inner/outer class - `vic`, `vac`: select inner/outer class
- With [neovim/nvim-lspconfig](https://github.com/neovim/nvim-lspconfig) - With [neovim/nvim-lspconfig](https://github.com/neovim/nvim-lspconfig)
@ -130,6 +136,8 @@ vim.keymap.set("n", "q", ":q<CR>")
## References / Thanks ## References / Thanks
- vim docs:
- [Seven habits of effective text editing 2.0](https://moolenaar.net/habits_2007.pdf)
- neovim official docs: - neovim official docs:
- [neovim Motion](https://neovim.io/doc/user/motion.html) - [neovim Motion](https://neovim.io/doc/user/motion.html)
- [neovim Tagsrch](http://neovim.io/doc/user/tagsrch.html) - [neovim Tagsrch](http://neovim.io/doc/user/tagsrch.html)

View File

@ -2,11 +2,12 @@
## Discord (old) naming ## Discord (old) naming
The way Discord naming user like`Joker#1234` is so interesting. The way Discord naming user like`Joker#1234` is so interesting. If user A
If user A register first with name `ABCXYZ`, and later user B register with name `ABCXYZ` too, user A, user B will have name `ABCXYZ#1`, `ABCXYZ#2` respectively. register first with name `ABCXYZ`, and later user B register with name `ABCXYZ`
too, user A, user B will have name `ABCXYZ#1`, `ABCXYZ#2` respectively.
Why it's interesting? Why it's interesting? Each time I join a new platform, all names I want are
Each time I join a new platform, all names I want are taken :D taken :D
## Interesting website ## Interesting website

View File

@ -12,8 +12,8 @@ So step by step:
- Render spec in local. - Render spec in local.
- Push to company host for other teams to see. - Push to company host for other teams to see.
Only step 1 is manual, aka I write my API spec completely with my hand (no auto gen from code whatever). Only step 1 is manual, aka I write my API spec completely with my hand (no auto
The others can be done with tools: gen from code whatever). The others can be done with tools:
```sh ```sh
# Convert # Convert

View File

@ -11,8 +11,8 @@ Imagine a chain of APIs:
- Calling API A - Calling API A
- Calling API B - Calling API B
Normally, if API A fails, API B should not be called. Normally, if API A fails, API B should not be called. But what if API A is
But what if API A is **optional**, whether it successes or fails, API B should be called anyway. **optional**, whether it successes or fails, API B should be called anyway.
My buggy code is like this: My buggy code is like this:
@ -25,8 +25,9 @@ if err := doA(ctx); err != nil {
doB(ctx) doB(ctx)
``` ```
The problem is `doA` taking too long, so `ctx` is canceled, and the parent of `ctx` is canceled too. The problem is `doA` taking too long, so `ctx` is canceled, and the parent of
So when `doB` is called with `ctx`, it will be canceled too (not what we want but sadly that what we got). `ctx` is canceled too. So when `doB` is called with `ctx`, it will be canceled
too (not what we want but sadly that what we got).
Example buggy code ([The Go Playground](https://go.dev/play/p/p4S27Su16VH)): Example buggy code ([The Go Playground](https://go.dev/play/p/p4S27Su16VH)):
@ -75,14 +76,21 @@ As you see both `doA` and `doB` are canceled.
## The (temporary) solution ## The (temporary) solution
Quick Google search leads me to [context: add WithoutCancel #40221](https://github.com/golang/go/issues/40221) and I quote: Quick Google search leads me to
[context: add WithoutCancel #40221](https://github.com/golang/go/issues/40221)
and I quote:
> This is useful in multiple frequently recurring and important scenarios: > This is useful in multiple frequently recurring and important scenarios:
> >
> - Handling of rollback/cleanup operations in the context of an event (e.g., HTTP request) that has to continue regardless of whether the triggering event is canceled (e.g., due to timeout or the client going away) > - Handling of rollback/cleanup operations in the context of an event (e.g.,
> - Handling of long-running operations triggered by an event (e.g., HTTP request) that terminates before the termination of the long-running operation > HTTP request) that has to continue regardless of whether the triggering
> event is canceled (e.g., due to timeout or the client going away)
> - Handling of long-running operations triggered by an event (e.g., HTTP
> request) that terminates before the termination of the long-running
> operation
So beside waiting to upgrade to Go `1.21` to use `context.WithoutCancel`, you can use this [workaround code](https://pkg.go.dev/context@master#WithoutCancel): So beside waiting to upgrade to Go `1.21` to use `context.WithoutCancel`, you
can use this [workaround code](https://pkg.go.dev/context@master#WithoutCancel):
```go ```go
func DisconnectContext(parent context.Context) context.Context { func DisconnectContext(parent context.Context) context.Context {
@ -116,7 +124,8 @@ func (ctx disconnectedContext) Value(key any) any {
} }
``` ```
So the buggy code becomes ([The Go Playground](https://go.dev/play/p/oIU-WxEJ_F3)): So the buggy code becomes
([The Go Playground](https://go.dev/play/p/oIU-WxEJ_F3)):
```go ```go
func main() { func main() {
@ -158,8 +167,8 @@ doA context deadline exceeded
doB doB
``` ```
As you see only `doA` is canceled, `doB` is done perfectly. As you see only `doA` is canceled, `doB` is done perfectly. And that what we
And that what we want in this case. want in this case.
## Thanks ## Thanks

View File

@ -2,7 +2,8 @@
The title is a joke. The title is a joke.
But after digging a few holes on the wall, I think I should leave a few notes for the future me. But after digging a few holes on the wall, I think I should leave a few notes
for the future me.
- Be careful to choose countersink drill bit (aka mũi khoan). - Be careful to choose countersink drill bit (aka mũi khoan).
- I pick **wood** type to drill on the wall, and ... it's broken :( - I pick **wood** type to drill on the wall, and ... it's broken :(

View File

@ -212,7 +212,8 @@ xcode-select --install
### Linux ### Linux
Fix black screen when open game in fullscreen in external monitor with [kazysmaster/gnome-shell-extension-disable-unredirect](https://github.com/kazysmaster/gnome-shell-extension-disable-unredirect) Fix black screen when open game in fullscreen in external monitor with
[kazysmaster/gnome-shell-extension-disable-unredirect](https://github.com/kazysmaster/gnome-shell-extension-disable-unredirect)
### Firefox ### Firefox

View File

@ -280,8 +280,10 @@ Commonly flags:
Be careful flags (need dry run if not sure): Be careful flags (need dry run if not sure):
- `-u`: skip if files in **dst** is already newer than in **src**, if you want to sync both ways - `-u`: skip if files in **dst** is already newer than in **src**, if you want
- `--delete`: delete files in **dst** if not exist in **src**, useful to sync dst with src to sync both ways
- `--delete`: delete files in **dst** if not exist in **src**, useful to sync
dst with src
## [F2](https://github.com/ayoisaiah/f2) ## [F2](https://github.com/ayoisaiah/f2)

View File

@ -6,11 +6,14 @@ Just a collection/checklist while using Android phone.
All Android phones are bloat. All Android phones are bloat.
So first thing first, use [Universal Android Debloater GUI](https://github.com/0x192/universal-android-debloater). So first thing first, use
[Universal Android Debloater GUI](https://github.com/0x192/universal-android-debloater).
## Apps ## Apps
Use [F-Droid](https://f-droid.org/en/) with [Droid-ify](https://github.com/Droid-ify/client) to replace Google Play as much as you can. Use [F-Droid](https://f-droid.org/en/) with
[Droid-ify](https://github.com/Droid-ify/client) to replace Google Play as much
as you can.
Daily: Daily:

View File

@ -4,7 +4,6 @@ Just to save my noted for future me using Redis again.
## Redis does not store creation time of keys ## Redis does not store creation time of keys
Why? Why? Because TODO
Because TODO
https://stackoverflow.com/questions/9917331/time-of-creation-of-key-in-redis https://stackoverflow.com/questions/9917331/time-of-creation-of-key-in-redis

View File

@ -5,7 +5,8 @@ My notes/mistakes/... when using cache (mainly Redis) from time to time
My default strategy is: My default strategy is:
- Write to database first then to cache second - Write to database first then to cache second
- Read from cache first, if not found then read from database second, then re-write to cache - Read from cache first, if not found then read from database second, then
re-write to cache
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
@ -38,10 +39,10 @@ sequenceDiagram
It's good for general cases, for example with CRUD action. It's good for general cases, for example with CRUD action.
The bad things happen when cache and database are not consistent. The bad things happen when cache and database are not consistent. For example
For example what happen if writing database OK then writing cache failed? what happen if writing database OK then writing cache failed? Now database has
Now database has new value, but cache has old value new value, but cache has old value Then when we read again, we read cache first
Then when we read again, we read cache first with old value, and that is disaster. with old value, and that is disaster.
## Thanks ## Thanks