<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Golang on omegaatt</title>
    <link>https://www.omegaatt.com/tags/golang/</link>
    <description>Recent content in Golang on omegaatt</description>
    <generator>Hugo -- 0.147.7</generator>
    <language>zh-TW</language>
    <managingEditor>raiven.kao@gmail.com (Raiven Kao)</managingEditor>
    <webMaster>raiven.kao@gmail.com (Raiven Kao)</webMaster>
    <copyright>Raiven Kao 2020 - 2026</copyright>
    <lastBuildDate>Thu, 06 Aug 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://www.omegaatt.com/tags/golang/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Golang 1.27 新特性整理：泛型補完與轉正的實驗性功能</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/golang_1_27_new_features/</link>
      <pubDate>Thu, 06 Aug 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/golang_1_27_new_features/</guid>
      <description>&lt;p&gt;半年前寫完 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/golang_1_26_huge_features/&#34;&gt;Go 1.26 新特性&lt;/a&gt; 那篇，文末問了一句「準備好更新你的 &lt;code&gt;go.mod&lt;/code&gt; 到 &lt;code&gt;go 1.26&lt;/code&gt; 了嗎？」，沒想到一轉眼 Go 1.27 也要來了。撰文的當下 Go 1.27 即將正式發布，以下內容是根據 &lt;a href=&#34;https://go.dev/doc/go1.27&#34;&gt;release candidate&lt;/a&gt; 整理，如果正式版有微調，還是以官方 release notes 為準。&lt;/p&gt;
&lt;p&gt;如果說 Go 1.26 給我的感覺是數量多、廣度大，那 Go 1.27 更像是把之前欠的債還一還。generic method 從 issue 一路等到現在終於落地，&lt;code&gt;goroutineleak&lt;/code&gt; profile 從需要 &lt;code&gt;GOEXPERIMENT&lt;/code&gt; 才能開的實驗功能轉正成預設行為，&lt;code&gt;encoding/json/v2&lt;/code&gt; 也從實驗畢業成為預設實作。這篇就挑幾個我覺得值得記錄的改動來聊聊。&lt;/p&gt;
&lt;h2 id=&#34;語言層面泛型設計的最後一塊拼圖&#34;&gt;語言層面：泛型設計的最後一塊拼圖&lt;/h2&gt;
&lt;h3 id=&#34;generic-method-真的來了&#34;&gt;Generic Method 真的來了&lt;/h3&gt;
&lt;p&gt;這件事我在 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2026/golang_generic_method/&#34;&gt;Go 1.27 即將支援 Generic Method&lt;/a&gt; 那篇已經寫得很詳細了，這裡就不重複展開，只放結論：method 現在可以宣告自己的 type parameter，不用再繞道用 package-level function 把 receiver 當參數傳進去。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;] &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt; []&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;NewList&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;](&lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;] {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;]{&lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// MapTo 是 generic method，帶著自己的型別參數 U，&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 跟 receiver 的型別參數 T 是兩回事。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;]) &lt;span style=&#34;color:#a6e22e&#34;&gt;MapTo&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;U&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;](&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;U&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;U&lt;/span&gt;] {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; make([]&lt;span style=&#34;color:#a6e22e&#34;&gt;U&lt;/span&gt;, len(&lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;l&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;] = &lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;List&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;U&lt;/span&gt;]{&lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Cat&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Age&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;cats&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;NewList&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;Cat&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Mittens&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;}, &lt;span style=&#34;color:#a6e22e&#34;&gt;Cat&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Tama&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;names&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cats&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;MapTo&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Cat&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; { &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;names&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;items&lt;/span&gt;) &lt;span style=&#34;color:#75715e&#34;&gt;// [Mittens Tama]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;老話一句：interface 目前還是無法宣告帶 type parameter 的 method，這是 dynamic dispatch 架構性的限制，不是漏掉沒做。想看完整的來龍去脈跟 workaround 的比較，去讀前面連結的那篇。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Go 1.27 即將支援 Generic Method：告別 package-level 函式的 workaround</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/golang_generic_method/</link>
      <pubDate>Wed, 27 May 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/golang_generic_method/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;Go 1.18 帶來泛型之後，我們終於可以寫出型別安全的通用函式，不再需要到處 &lt;code&gt;interface{}&lt;/code&gt; 加型別斷言。但一個眾所周知的限制一直存在到現在，method 沒辦法有自己的 type parameter。&lt;/p&gt;
&lt;p&gt;換句話說，這段程式碼在目前的 Go 版本中是非法的：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;c&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Client&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;doRequest&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;](&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;method&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;path&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;body&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;) (&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 編譯錯誤：cannot use generic method doRequest without instantiation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;為了繞過這個限制，大家只能退而求其次，把 receiver 當作普通參數傳進去，改用 package-level 的 generic function。&lt;/p&gt;
&lt;p&gt;最近 &lt;a href=&#34;https://github.com/golang/go/issues/77273&#34;&gt;Go issue #77273&lt;/a&gt; 的進展讓這件事有了轉機，generic method 的設計已被接受，預計在 Go 1.27 正式釋出。&lt;/p&gt;
&lt;h2 id=&#34;為什麼現在沒有-generic-method&#34;&gt;為什麼現在沒有 Generic Method？&lt;/h2&gt;
&lt;p&gt;這不是 Go team 沒想到，而是刻意的設計決策。&lt;/p&gt;
&lt;p&gt;Go 的 interface 是核心功能，method 必須能夠被 dispatch 到具體的實作上。如果 method 自身帶有 type parameter，編譯器在 dispatch 時需要知道 &lt;code&gt;T&lt;/code&gt; 是什麼，這在動態 dispatch（interface dispatch）的情境下是個難題，runtime 無法事先知道要生成哪個型別的實例化版本。&lt;/p&gt;
&lt;p&gt;所以泛型剛推出時，generic method 被刻意排除在外，等待一個比較完整的設計方案。這個等待從 Go 1.18 一路等到了 1.27（暫定）。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Pragmatic Clean Architecture in Go</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/pragmatic_clean_architecture_in_go/</link>
      <pubDate>Thu, 23 Apr 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/pragmatic_clean_architecture_in_go/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;在大型系統或需要長期維護的產品中，導入 DDD（Domain-Driven Design）或 Clean Architecture 幾乎是業界的標準答案，分層清晰、職責明確、可測試性高，這些優點毋庸置疑。&lt;/p&gt;
&lt;p&gt;但問題是，不是每個專案都是大型系統。&lt;/p&gt;
&lt;p&gt;當你面對的是一個內部工具、side project、或是剛起步的新產品，Clean Architecture 的大全套 Use Case、Port、Adapter、Aggregate、Repository Interface 全放 domain，往往會讓你在還沒寫第一行商業邏輯之前，就先在資料夾結構裡迷路了三個小時，或是讓不熟悉的貢獻者花費大量時間在閱讀架構。&lt;/p&gt;
&lt;p&gt;這篇文章想討論的是：在不犧牲可測試性與可維護性的前提下，哪些抽象可以捨棄、哪些值得保留，以及我自己踩過的一些坑。&lt;/p&gt;
&lt;h2 id=&#34;捨棄什麼&#34;&gt;捨棄什麼&lt;/h2&gt;
&lt;h3 id=&#34;獨立的-portadapter-層&#34;&gt;獨立的 Port/Adapter 層&lt;/h3&gt;
&lt;p&gt;Clean Architecture 中，Use Case 透過明確定義的 input/output port 與外界溝通，搭配 Adapter 負責格式轉換。這在大型系統中確實有其價值，但它帶來的代價是：為了讓每一層都能獨立替換，你需要維護大量的介面與轉換邏輯，而這些轉換邏輯往往只是把 A struct 的欄位複製到 B struct。&lt;/p&gt;
&lt;p&gt;在小專案中，這條轉換鏈可以大幅縮短。HTTP handler 本身就可以負責 DTO 的轉換，不需要再多一層 Adapter。&lt;/p&gt;
&lt;h3 id=&#34;ddd-的-aggregate&#34;&gt;DDD 的 Aggregate&lt;/h3&gt;
&lt;p&gt;Aggregate 是 DDD 中保護業務不變式（invariant）的邊界，透過 Aggregate Root 統一管理相關物件的存取。這個概念本身沒有問題，但在小專案中，如果業務規則還不夠複雜到需要嚴格的不變式保護，過早引入 Aggregate 反而會讓簡單的 CRUD 操作變得冗長。&lt;/p&gt;
&lt;h2 id=&#34;保留什麼&#34;&gt;保留什麼&lt;/h2&gt;
&lt;p&gt;捨棄了部分複雜度之後，剩下的四層架構已經足以應付絕大多數的小專案需求。&lt;/p&gt;
&lt;h3 id=&#34;package-domain&#34;&gt;&lt;code&gt;package domain&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;這裡只放 &lt;strong&gt;Domain Model&lt;/strong&gt; 與 &lt;strong&gt;Value Object&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;值得特別說明 Domain Model 的定義。它不等同於 DDD 裡的 Domain，不是什麼深奧的設計概念，Domain Model 只是在描述「這個應用程式如何跟外部世界互動、邊界在哪」，也就是你的核心資料結構與它們身上的行為。有些「賣課」的人喜歡把 Domain Model 直接等同於 DDD 的 Domain，實際上它是個更基礎、更普遍的概念，可以參考這支&lt;a href=&#34;https://www.youtube.com/watch?v=aSPu0WhVPew&#34;&gt;影片&lt;/a&gt;的解釋。Domain Model 中不需要也不該出現任何技術細節，例如回傳是否是 JSON 等等，這些 Domain Model 甚至要能讓 PM 與非技術人員也「聽的懂」。&lt;/p&gt;</description>
    </item>
    <item>
      <title>放棄 Electron 與 Tauri? 我用 Wails &#43; HTMX 打造 3MB 的跨平台桌面應用</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/golang_desktop_application_with_wails_and_htmx/</link>
      <pubDate>Mon, 09 Mar 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/golang_desktop_application_with_wails_and_htmx/</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Web 仔要有 Web 仔的自覺&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;前陣子收到了 Murphy 的需求，嘗試使用 &lt;a href=&#34;https://www.omegaatt.com/blogs/develop/2025/tauri_first_look&#34;&gt;Tauri 寫了一個桌面 App&lt;/a&gt;。Tauri 結合 Rust 與 React 的體驗相當不錯，打包出來的體積也十分理想。&lt;/p&gt;
&lt;p&gt;但身為一個主要撰寫 Golang 的後端工程師，內心總有一個聲音：「如果能用 Go 來寫桌面應用，那該有多好？」&lt;/p&gt;
&lt;p&gt;眾所周知，Golang 在原生的 Desktop GUI 框架上一直沒有特別強勢的殺手級專案（過去使用 Fyne 的經驗不算太好）。在環顧了一眾基於 Chromium 的 Electron 或是基於系統 WebView 的 Tauri 後，決定走一條稍微不一樣的「老路」：使用 &lt;a href=&#34;https://wails.io/&#34;&gt;Wails&lt;/a&gt; 搭配 &lt;a href=&#34;https://htmx.org/&#34;&gt;HTMX&lt;/a&gt; 與 &lt;a href=&#34;https://templ.guide/&#34;&gt;Templ&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;最終的產物就是 &lt;a href=&#34;https://github.com/omegaatt36/dub&#34;&gt;Dub&lt;/a&gt; 一個跨平台的批次檔案重新命名工具。更甚，在 Linux 上打包出來的可執行檔只有約 3MB，並且完美支援 macOS 與 Linux，且幾乎沒有複雜的前端狀態管理負擔。&lt;/p&gt;
&lt;h2 id=&#34;為什麼是-wails--htmx&#34;&gt;為什麼是 Wails + HTMX？&lt;/h2&gt;
&lt;p&gt;在選擇桌面應用程式的技術方案時，我們通常有幾個考量&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Electron: 開發體驗極佳，生態系最豐富，但代價是動輒上百 MB 的體積與高昂的記憶體佔用。對於一個小工具來說，太過沉重。（這也是我優先選擇 Zed.dev 作為主要編輯器的原因）&lt;/li&gt;
&lt;li&gt;Tauri: 效能極佳，體積小，但後端需要寫 Rust。雖然 Rust 很香，但有時候只想用最熟悉的 Go 快速把邏輯刻出來。&lt;/li&gt;
&lt;li&gt;Wails: 與 Tauri 類似，採用前端網頁技術結合系統原生 WebView，但後端語言是 Golang。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;既然選了 Wails，前端該用什麼？
現代前端框架（React, Vue, Svelte）通常需要建置複雜的 SPA（Single Page Application），並透過 JSON API 與後端溝通。但對於一個狀態並不複雜的工具軟體，不想在前端再維護一份狀態。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Clean Craftsmanship：在 LLM 時代重拾軟體工匠精神</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/clean_craftsmanship/</link>
      <pubDate>Thu, 19 Feb 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/clean_craftsmanship/</guid>
      <description>&lt;h2 id=&#34;為什麼在這個時間點讀這本書&#34;&gt;為什麼在這個時間點讀這本書&lt;/h2&gt;
&lt;p&gt;最近對於如何在 LLM 時代帶領團隊一起提昇生產力感到困惑。當 AI 能在幾秒鐘內生成幾百行程式碼，「寫程式」這件事的門檻似乎降到了前所未有的低點。但門檻低了，品質呢？&lt;/p&gt;
&lt;p&gt;當「每個人」都在養龍蝦，有了生產力焦慮，TypeScript 寫的 &lt;a href=&#34;https://github.com/openclaw/openclaw&#34;&gt;OpenClaw&lt;/a&gt; 才剛出現，Python 的 &lt;a href=&#34;https://github.com/HKUDS/nanobot&#34;&gt;NanoBot&lt;/a&gt; 馬上跟上，接著是 Golang 的 &lt;a href=&#34;https://github.com/sipeed/picoclaw&#34;&gt;picoclaw&lt;/a&gt;，然後 Rust 的 &lt;a href=&#34;https://github.com/zeroclaw-labs/zeroclaw&#34;&gt;ZeroClaw&lt;/a&gt; 也來了。&lt;/p&gt;
&lt;p&gt;根據 ZeroClaw 做的 &lt;a href=&#34;https://github.com/zeroclaw-labs/zeroclaw?tab=readme-ov-file#benchmark-snapshot-zeroclaw-vs-openclaw-reproducible&#34;&gt;benchmark&lt;/a&gt;，這些 Agent 已經降到小於 5MB 的記憶體佔用與 10ms 的啟動時間，「種族」為人類的我們對於產出軟體來說還剩什麼？&lt;/p&gt;
&lt;p&gt;帶著這個問題，我決定先從自身出發，找出在 LLM 時代還能保持軟體工程「工匠精神」的誘因。於是翻開了 Robert C. Martin 的 &lt;a href=&#34;https://www.tenlong.com.tw/products/9786263339941&#34;&gt;&lt;em&gt;Clean Craftsmanship&lt;/em&gt;&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;這本書分為三個部份：&lt;strong&gt;紀律、標準、道德&lt;/strong&gt;。一半以上的篇幅在講述 TDD 這個老生常談的開發方式，但透過 TDD，我們更能知道何謂軟體的「品質」。以下是我特別書籤的幾個段落。&lt;/p&gt;
&lt;h2 id=&#34;紀律&#34;&gt;紀律&lt;/h2&gt;
&lt;h3 id=&#34;童子軍規則&#34;&gt;童子軍規則&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;離開營地時，要比你來時更乾淨。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;這也是在 &lt;em&gt;Clean Code&lt;/em&gt; 一書就提到的概念。每次微小的重構，都能小程度的減少技術債的產生。不需要一次大刀闊斧，只要每次經過一段程式碼時，順手讓它變得更好一點。&lt;/p&gt;
&lt;p&gt;讓我想到 &lt;a href=&#34;https://www.approachwithalacrity.com/claude-ne/&#34;&gt;Claude is not a senior engineer (yet)&lt;/a&gt; 這篇文章中提到的 Sweeks——一位被稱為「園丁」的 distinguished engineer，他不斷地重寫、收緊抽象，讓經過他手的程式碼都變得更乾淨。我們都想成為 Sweeks，對吧？&lt;/p&gt;
&lt;p&gt;在 LLM 時代，AI 擅長的是「組裝」現有解決方案，但它缺乏 Sweeks 那種「看到可以更好的地方就會忍不住動手」的靈魂。童子軍規則提醒我們：這份靈魂不能丟。&lt;/p&gt;
&lt;h3 id=&#34;test-doubles-的正名&#34;&gt;Test Doubles 的正名&lt;/h3&gt;
&lt;p&gt;書中透過實戰的例子講述了所有 test double：&lt;strong&gt;Dummy、Stub、Spy、Mock、Fake&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;坦白說，我曾經在諸多 repo 中看到這些名詞卻沒有實際使用它們。頂多在 DI 時製作了一個「用於模擬 database repository 的 implement」，或是使用了 &lt;code&gt;gomock&lt;/code&gt; 這種套件來產生 mock，然後把所有替身都統稱為 mock。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 1.25 testing/synctest 初體驗：告別在測試中寫 time.Sleep 的日子</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/golang_synctest/</link>
      <pubDate>Sun, 18 Jan 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/golang_synctest/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;在我們目前的應用程式架構中，由於高度與 Kubernetes 耦合，服務啟動與運作期間需要頻繁地去讀取 K8s 中的 ConfigMap。為了達成配置熱更新（Hot Reload），我們引入了 Kubernetes client-go 中的 &lt;a href=&#34;https://pkg.go.dev/k8s.io/client-go/informers&#34;&gt;informers&lt;/a&gt; 機制來監聽 ConfigMap 的 CRUD 事件。&lt;/p&gt;
&lt;p&gt;雖然 K8s 官方提供了 fake client 讓我們能測試 informers 的邏輯，但在 Service Code 的層級，我們往往需要封裝一層更適合業務邏輯的 &lt;code&gt;ConfigWatcher&lt;/code&gt;。Golang 引以為傲的輕量級 Goroutine 與 Channel 搭配非常適合用來處理這種非同步的事件傳遞。&lt;/p&gt;
&lt;p&gt;然而，一旦涉及到 Goroutine 的非同步測試，「時間」往往就成了最大的敵人。&lt;/p&gt;
&lt;h2 id=&#34;遇到的問題不穩定的測試與魔法數字&#34;&gt;遇到的問題：不穩定的測試與魔法數字&lt;/h2&gt;
&lt;p&gt;為了模擬 ConfigMap 的變更通知，我們定義了一個 &lt;code&gt;ConfigMapWatcher&lt;/code&gt; 介面與對應的 Event 結構：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventTypeAdded&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventType&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;iota&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventTypeModified&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventTypeDeleted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Type&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEventType&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Value&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;map&lt;/span&gt;[&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;]&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapWatcher&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Watch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;eventCh&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著，我們很自然地在 testing 中實作了一個 fake 物件來模擬事件發送：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;injectCh&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;watchErr&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;watchOnce&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;sync&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Once&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;newFakeConfigMapWatcher&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;injectCh&lt;/span&gt;: make(&lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;sendEvent&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;event&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;injectCh&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;event&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;fakeConfigMapWatcher&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;Watch&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;eventCh&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ConfigMapUpdateEvent&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;watchErr&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;watchErr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;watchOnce&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Do&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;go&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;select&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Done&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				&lt;span style=&#34;color:#66d9ef&#34;&gt;case&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;injectCh&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;					&lt;span style=&#34;color:#a6e22e&#34;&gt;eventCh&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;				}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;問題來了，當我們在寫單元測試時，呼叫 &lt;code&gt;sendEvent&lt;/code&gt; 將事件送入 channel 後，消費者端（也就是我們的業務邏輯 Goroutine）並不會「立刻」收到並處理完成。為了確保 &lt;code&gt;assert&lt;/code&gt; 斷言執行時，業務邏輯已經跑完了，我們被迫在測試中加入 &lt;code&gt;time.Sleep&lt;/code&gt;：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 1.26 新特性在數量上史無前例的多</title>
      <link>https://www.omegaatt.com/blogs/develop/2026/golang_1_26_huge_features/</link>
      <pubDate>Tue, 13 Jan 2026 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2026/golang_1_26_huge_features/</guid>
      <description>&lt;p&gt;隨著時間來到 2026 年初，Go 語言迎來了 1.26 版本的更新。如果說 Go 1.18 的泛型是語言層面的重大變革，那麼 Go 1.26 則是在「數量」與「廣度」上讓人感到驚艷的一次釋出。從語言特性的語法糖、標準庫的實用擴充，到 Runtime 效能的顯著提升（Green Tea GC），甚至是實驗性的 SIMD 支援，這次的更新內容豐富到讓人目不暇給。&lt;/p&gt;
&lt;p&gt;本文將挑選其中幾個我認為對日常開發最重要、或最有趣的改動來進行介紹。&lt;/p&gt;
&lt;h2 id=&#34;語言層面的改動&#34;&gt;語言層面的改動&lt;/h2&gt;
&lt;h3 id=&#34;newexpr終於不用再寫輔助函數了&#34;&gt;&lt;code&gt;new(expr)&lt;/code&gt;：終於不用再寫輔助函數了&lt;/h3&gt;
&lt;p&gt;在 Go 1.26 之前，如果我們想要取得一個基本型別（如 &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;string&lt;/code&gt;）的 pointer，通常需要宣告一個變數或者寫一個輔助函數。這在定義 struct 的 literal 時特別煩人，尤其是當 struct 欄位是 &lt;code&gt;*bool&lt;/code&gt; 或 &lt;code&gt;*int&lt;/code&gt; 用來區分「零值」與「未設定」的時候。&lt;/p&gt;
&lt;p&gt;回顧過去，我們為了這個小需求付出了不少努力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 Go 1.18 泛型出現之前：我們經常需要定義一堆如 &lt;code&gt;Int64Ptr(v int64) *int64&lt;/code&gt; 或 &lt;code&gt;Float64Ptr(v float64) *float64&lt;/code&gt; 的輔助函數（AWS SDK 的使用者應該對此非常熟悉）。&lt;/li&gt;
&lt;li&gt;匿名函數大法：如果不想要定義全域的輔助函數，有時甚至會看到像 &lt;code&gt;enabled := func(b bool) *bool { return &amp;amp;b }(true)&lt;/code&gt; 這種冗長且難讀的寫法。&lt;/li&gt;
&lt;li&gt;泛型時代：雖然可以用一個通用的 &lt;code&gt;ptr[T]&lt;/code&gt; 解決，但還是需要額外的程式碼。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以前我們可能需要這樣做：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ptr&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;](&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;T&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Config&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Enabled&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;bool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;conf&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Config&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Enabled&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;ptr&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在 Go 1.26 中，內建的 &lt;code&gt;new&lt;/code&gt; 函數得到了增強，現在它不僅接受型別，還可以直接接受表達式（Expression）。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Interface 不是有開就好：從一個 PR 來看抽象化的重要性</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/interface-is-not-just-about-creating-one/</link>
      <pubDate>Sat, 04 Oct 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/interface-is-not-just-about-creating-one/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;最近團隊正在開發一個新產品，其中一個核心功能需要 client 與 server 之間進行即時、雙向的溝通。經過一番技術評估，我們決定採用 WebSocket 來實現這個需求。&lt;/p&gt;
&lt;p&gt;身為一個良好習慣的開發團隊，我們在開發初期就導入了依賴注入（Dependency Injection），希望透過界面（Interface）來解耦商業邏輯與具體的實作，這樣不僅能提高程式碼的可測試性，未來在更換底層實作時也能更加輕鬆。&lt;/p&gt;
&lt;p&gt;一切聽起來都很美好，直到我在一次 Code Review 中，看到了一段熟悉的程式碼。&lt;/p&gt;
&lt;h2 id=&#34;一個-pr-的故事&#34;&gt;一個 PR 的故事&lt;/h2&gt;
&lt;p&gt;在我們的 Domain Layer，也就是處理核心商業邏輯的地方，我看到同事定義了下面這個 interface：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// package/to/domain/service.go&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// WebSocketService defines the interface for websocket communication.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;WebSocketService&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// StartAndLinsten starts the service and listens for incoming messages.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;StartAndLinsten&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// Send sends a message to the client.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Send&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;message&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;any&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// ... other methods&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;第一眼看過去，好像沒什麼大問題。有名稱、有方法、也確實是個 interface。然而，當我細看 &lt;code&gt;WebSocketService&lt;/code&gt; 這個命名時，總覺得哪裡怪怪的。&lt;/p&gt;
&lt;p&gt;於是我在 PR 上留下了這樣的 comment：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;這個界面主要是抽象化 client 與 server 間的互動，不應該侷限於 WebSocket 這個 Protocol。假如我們未來要換成使用 socket.io 或是 gRPC stream，是不是連 domain 層的 interface 也要跟著改動？&lt;/p&gt;</description>
    </item>
    <item>
      <title>監控你的執行檔：初探 watchexec</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/watch_your_exec_watchexec/</link>
      <pubDate>Sat, 06 Sep 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/watch_your_exec_watchexec/</guid>
      <description>前端開發有 liveserver，後端開發有 air，那 TUI 開發呢？本文記錄了我在開發 Bubbletea 應用時，從 air 轉向 watchexec 的心路歷程，以及如何使用這個通用工具來優雅地實現終端機應用的熱重載。</description>
    </item>
    <item>
      <title>用 Golang Bubbletea 打造終端機應用：從 Hello World 到多頁面架構</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/golang_bubbletea_experience/</link>
      <pubDate>Fri, 05 Sep 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/golang_bubbletea_experience/</guid>
      <description>探索如何使用 Golang 的 Bubbletea 函式庫，基於 Elm 架構，從零開始打造一個互動式終端機應用（TUI）。本文將從一個簡單的計數器範例，逐步引導你建構出一個類似 Web 應用的多頁面架構，並分享整個生命週期中的關鍵概念與注意事項。</description>
    </item>
    <item>
      <title>如何利用 Golang AST 助攻 LLM 省 token 又高效</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/golang_ast_llm_coding/</link>
      <pubDate>Mon, 16 Jun 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/golang_ast_llm_coding/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;近來大型語言模型（LLM）的發展可謂一日千里，特別是在程式碼理解、生成與輔助開發方面，展現出了驚人的潛力。許多開發者開始嘗試將 LLM 融入到日常工作中，期望能提昇開發效率，甚至實現所謂的「vibe coding」——讓 LLM 理解程式碼的整體風格與意圖，並在此基礎上進行協作。&lt;/p&gt;
&lt;p&gt;然而，當我們試圖讓 LLM 直接「閱讀」整個大型專案的程式碼庫時，往往會碰到一些現實的挑戰。上下文長度限制、高昂的 token 消耗以及潛在的雜訊干擾，都可能讓 LLM 的表現不盡如人意。這時候，我們就需要更聰明的方法來為 LLM「提煉」程式碼的精華。&lt;/p&gt;
&lt;p&gt;在這篇文章中，我想分享一個在 Golang 專案中可能被忽略的利器：抽象語法樹（Abstract Syntax Tree, AST）。透過 Golang AST，我們可以更精準地提取程式碼的結構資訊，為 LLM 提供一份濃縮且高效的上下文，既能節省寶貴的 token，又能幫助 LLM 更好地把握「Code Vibe」。&lt;/p&gt;
&lt;h3 id=&#34;llm-直接消化大型-code-base-的痛&#34;&gt;LLM 直接消化大型 Code Base 的「痛」&lt;/h3&gt;
&lt;p&gt;想像一下，你正在開發一個頗具規模的 Golang 後端服務，裡面包含了數十個套件、數百個檔案。現在，你想讓 LLM 幫你新增一個功能，或者重構某個模組。如果直接把所有相關的程式碼一股腦地丟給 LLM，可能會遇到以下這些令人頭痛的問題：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;token 消耗「爆表」：LLM 的使用成本與輸入輸出的 token 數量直接相關。將大量原始碼作為輸入，無疑會產生巨額的 token 費用，對於個人開發者或小型團隊來說，這可能難以承受。&lt;/li&gt;
&lt;li&gt;「腦容量」不足的上下文限制：即使是目前頂尖的 LLM，其能夠處理的上下文長度也是有限的。面對龐大的程式碼庫，LLM 可能無法一次「看」全所有必要的資訊，導致理解片面或生成結果不佳。&lt;/li&gt;
&lt;li&gt;資訊過載與雜訊干擾：完整的程式碼中，充斥著各種細節——註解、空行、詳細的錯誤處理邏輯、暫時用不到的私有函式等等。這些資訊對於 LLM 理解程式碼的「vibe」或執行特定高層次任務（例如「模仿現有風格新增一個 API 端點」）來說，有時反而會成為雜訊，影響其判斷。&lt;/li&gt;
&lt;li&gt;龜速的回應：通常情況下，輸入給 LLM 的資訊越多，它處理並生成回應所需的時間就越長。在追求高效開發的今天，漫長的等待顯然不是我們想要的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;面對這些挑戰，我們不禁要問：有沒有一種方法，可以只給 LLM「剛剛好」的資訊，讓它既能理解我們的意圖，又能高效地完成任務呢？Golang AST 或許就是答案的一部分。&lt;/p&gt;
&lt;h3 id=&#34;golang-ast-如何助攻&#34;&gt;Golang AST 如何「助攻」&lt;/h3&gt;
&lt;p&gt;在我們深入探討 AST 如何幫助 LLM 之前，先快速回顧一下什麼是 Golang AST。&lt;/p&gt;</description>
    </item>
    <item>
      <title>一次核心模組的重構經驗</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/experience_of_refine_core_module/</link>
      <pubDate>Sat, 14 Jun 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/experience_of_refine_core_module/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;在軟體開發的漫漫長路中，我們時常會接手一些充滿「歷史印記」的專案。這些專案的核心模組，往往因為業務的快速迭代與時間的無情沖刷，逐漸演化成難以觸碰的「史前巨獸」。近期，我便有幸（或許該說是不幸地）參與了一次這樣核心模組的重構之旅，其核心是我們產品線廣泛使用的 Golang gRPC 認證攔截器 (Interceptor)。這段經歷充滿挑戰，但也收穫良多，希望能藉此分享一些心得。&lt;/p&gt;
&lt;h2 id=&#34;歷史的塵埃核心模組的演進悲歌&#34;&gt;歷史的塵埃：核心模組的演進悲歌&lt;/h2&gt;
&lt;p&gt;我接手的這個核心認證模組，在專案初期或許設計簡潔明瞭，但隨著產品線的不斷擴展和需求的堆疊，其複雜度已然失控。追溯其演進的脈絡，彷彿能看到一部小型技術債的形成史。&lt;/p&gt;
&lt;h3 id=&#34;最初的起點單純的-grpc-interceptor&#34;&gt;最初的起點：單純的 gRPC Interceptor&lt;/h3&gt;
&lt;p&gt;可以想見，專案伊始，對於 gRPC 服務的認證需求相對單純。一個通用的攔截器或許就能滿足所有需求，程式碼結構清晰可見：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SimpleAuthInterceptor&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Performing basic authentication via SimpleAuthInterceptor&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handler&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;req&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SimpleStreamAuthInterceptor&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Performing basic stream authentication via SimpleStreamAuthInterceptor&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;handler&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;srv&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;ss&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;lis&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;net&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Listen&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;tcp&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;:50051&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Fatalf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;failed to listen: %v&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;grpc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;NewServer&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;grpc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;UnaryInterceptor&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;SimpleAuthInterceptor&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;grpc&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;StreamInterceptor&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;SimpleStreamAuthInterceptor&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;gRPC server listening on :50051&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Serve&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;lis&lt;/span&gt;); &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;log&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Fatalf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;failed to serve: %v&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在這個階段，一切看起來是那麼的美好與純粹。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang Iterator 簡介與 samber/lo 比較</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/golang_iterator/</link>
      <pubDate>Sat, 31 May 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/golang_iterator/</guid>
      <description>&lt;p&gt;自從 Golang 1.18 版本引入泛型（Generics）後，Go 語言的生態系統迎來了許多令人興奮的變化。其中，Golang 1.23 版本對 Iterator（迭代器）的標準化，以及 &lt;code&gt;iter&lt;/code&gt; 套件的加入，無疑是近期改動中相當重要的一環。本文將淺談 Golang Iterator 的基本概念，深入探討 Pure Iterator 與 Impure Iterator 之間的區別與設計考量，並與社群中流行的 &lt;code&gt;samber/lo&lt;/code&gt; 工具庫進行比較。&lt;/p&gt;
&lt;h2 id=&#34;什麼是-iterator&#34;&gt;什麼是 Iterator？&lt;/h2&gt;
&lt;p&gt;Iterator Pattern（迭代器模式）是一種常見的設計模式，它提供了一種循序存取集合物件中各個元素的方法，而又無需暴露該物件的內部表示。簡單來說，Iterator 就像一個指針，可以依序指向集合中的下一個元素，直到遍歷完所有元素為止。&lt;/p&gt;
&lt;h2 id=&#34;golang-中的-iterator&#34;&gt;Golang 中的 Iterator&lt;/h2&gt;
&lt;p&gt;在 Golang 1.23 之前，我們通常透過 &lt;code&gt;for-range&lt;/code&gt; 迴圈來迭代 array、slice、string、map、channel 等內建資料結構。然而，對於自訂的資料結構或複雜的序列生成邏輯，缺乏一個統一的迭代標準。&lt;/p&gt;
&lt;p&gt;Golang 1.23 版本正式將 Iterator 標準化，並在標準庫中加入了 &lt;code&gt;iter&lt;/code&gt; 套件。同時，&lt;code&gt;slices&lt;/code&gt; 和 &lt;code&gt;maps&lt;/code&gt; 套件也增加了一些回傳 Iterator 的工廠函數（Iterator Factories）。到了 Golang 1.24，更有如 &lt;code&gt;strings.SplitSeq&lt;/code&gt; 等函數加入，進一步豐富了 Iterator 的應用場景。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// strings.SplitSeq 回傳一個迭代器，用於遍歷由 sep 分隔的 s 子字串。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 此迭代器產生的字串與 Split(s, sep) 回傳的相同，但不會建構整個 slice。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 它回傳一個單次使用的迭代器。&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;SplitSeq&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;s&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;sep&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;iter&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Seq&lt;/span&gt;[&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果對 Golang 1.23+ 中 Iterator 的語法和語義還不熟悉，建議可以閱讀 Ian Lance Taylor 在 Go 官方部落格發表的&lt;a href=&#34;https://go.dev/blog/range-functions&#34;&gt;介紹文章&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>建構多平台的 container image</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/building_multiple_platform_container_image/</link>
      <pubDate>Sun, 30 Mar 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/building_multiple_platform_container_image/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;最近在搞 side project 時，常常需要在不同的 CPU 架構上跑我的應用程式。例如，開發用的 MacBook 是 ARM 架構 (Apple Silicon)，開發用的 Desktop 是 x86 架構（Ryzen 5900X），而部署的伺服器可能是 x86 (AMD64)，有時候甚至想在 Raspberry Pi (ARM) 上跑些小東西。每次都要為不同平台分別建構 image 實在有點麻煩，而且 Registry 上一堆 &lt;code&gt;xxxapp-amd64&lt;/code&gt;, &lt;code&gt;xxxapp-arm64&lt;/code&gt; 的 tag 看了也很礙眼。經過一番研究與嘗試，是時候接觸 Docker Buildx 了。&lt;/p&gt;
&lt;h2 id=&#34;為什麼需要多平台映像檔&#34;&gt;為什麼需要多平台映像檔&lt;/h2&gt;
&lt;p&gt;在 wintel 的商業策略下，以及大家對高性能伺服器的普遍認知，主要用 x86/amd64，但現在 ARM 架構越來越普及，從 Apple Silicon 的 Mac、AWS Graviton 處理器、各種 IoT 設備到你的 Raspberry Pi，ARM 無所不在。如果你的 container image 只支援 amd64，那它就無法在這些 ARM 設備上原生運行 (需要模擬，效能差)。為了&lt;a href=&#34;https://en.wikipedia.org/wiki/Write_once,_run_anywhere&#34;&gt;Build Once, Run Anywhere&lt;/a&gt;，多平台映像檔 (Multi-platform images) 就是 meta。&lt;/p&gt;
&lt;h2 id=&#34;oci-多平台映像檔架構簡述&#34;&gt;OCI 多平台映像檔架構簡述&lt;/h2&gt;
&lt;p&gt;其實不複雜。傳統的單一平台 image，它的 manifest 指向一組設定檔和一堆 layer。而多平台 image 則是透過一個 manifest list (索引) 指向多個特定平台的 manifest。每個特定平台的 manifest 才各自指向該平台的設定檔和 layer。&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用 go work 在本地開發解決同時開發 module 的問題</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/go_module_and_go_work/</link>
      <pubDate>Thu, 20 Feb 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/go_module_and_go_work/</guid>
      <description>&lt;p&gt;在 &lt;a href=&#34;https://go.dev/blog/get-familiar-with-workspaces&#34;&gt;Golang 1.18 中 go workspace 的提案&lt;/a&gt;釋出後，golang 的官方文件或多或少也提到應該要怎麼做 multi module 的開發。相較於過去需要不斷的替換 &lt;code&gt;go.mod&lt;/code&gt; 內的 &lt;code&gt;replace&lt;/code&gt; 指令，&lt;code&gt;go work&lt;/code&gt; 大幅改善了 multi module 的開發體驗。&lt;/p&gt;
&lt;h2 id=&#34;為什麼需要-go-work&#34;&gt;為什麼需要 go work&lt;/h2&gt;
&lt;h3 id=&#34;專案逐漸變大&#34;&gt;專案逐漸變大&lt;/h3&gt;
&lt;p&gt;當你在維護一個小工具 side project 時，單一的 module 就能夠滿足所有需求，但當專案逐漸變大，會需要將專案拆分成多個 module。
可以透過一個例子來理解：
假設我們有一個專案，拆分成以下兩個 module：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;common-lib-golang&lt;/code&gt;: 存放所有專案都會用到的 function，例如 retry, logger, tracer 等等&lt;/li&gt;
&lt;li&gt;&lt;code&gt;backend&lt;/code&gt;: 實際提供 http api 的程式碼&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最開始的檔案架構為：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;├── backend
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── go.mod
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   ├── go.sum
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;│   └── main.go
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;└── common-lib-golang
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── go.mod
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ├── go.sum
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    └── util.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;隨著專案變大，我們在開發 &lt;code&gt;backend&lt;/code&gt; 時，時常會需要修改 &lt;code&gt;common-lib-golang&lt;/code&gt; 內的程式碼，這時候就需要 &lt;code&gt;go work&lt;/code&gt; 來協助我們。&lt;/p&gt;</description>
    </item>
    <item>
      <title>在習慣 go mod 後重新學習 git submodule</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/git_submodule_turorial/</link>
      <pubDate>Fri, 24 Jan 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/git_submodule_turorial/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;使用 Golang 作為主要開發已經有五年的時間。最近因工作需要接觸到 Python 專案，並且該專案使用 git submodule 的方式來引用共同函式庫 &lt;code&gt;common-lib-python&lt;/code&gt;。對於長久使用 Golang 的 &lt;code&gt;go mod&lt;/code&gt; 的我來說，submodule 是一個相對陌生的概念。藉這個機會撰寫一篇文章，整理並紀錄一下 git submodule 的用法，也作為未來的參考。&lt;/p&gt;
&lt;p&gt;這篇文章會假設已經對 git 的基本操作有一定程度的了解，並著重在 submodule 的概念、使用情境以及與 Golang 的 &lt;code&gt;go mod&lt;/code&gt; 的差異比較。文章內容會以下列流程來呈現：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立一個新的 Python 專案 &lt;code&gt;my-python-repo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;建立一個 Python 模組 &lt;code&gt;my-python-module&lt;/code&gt; 作為 submodule&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;my-python-repo&lt;/code&gt; 中使用 &lt;code&gt;my-python-module&lt;/code&gt; 作為 submodule&lt;/li&gt;
&lt;li&gt;模擬需求變更，同時修改 &lt;code&gt;my-python-repo&lt;/code&gt; 與 &lt;code&gt;my-python-module&lt;/code&gt;，並分別發送 PR&lt;/li&gt;
&lt;li&gt;與 Golang 的 &lt;code&gt;go mod&lt;/code&gt; 進行比較&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;建立-python-專案與模組&#34;&gt;建立 Python 專案與模組&lt;/h2&gt;
&lt;p&gt;先建立兩個新的 git repo，分別是 &lt;code&gt;my-python-repo&lt;/code&gt; 與 &lt;code&gt;my-python-module&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 建立 my-python-repo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir my-python-repo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd my-python-repo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git init
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;touch main.py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git commit -m &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Initial commit&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 建立 my-python-module&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd ..
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mkdir my-python-module
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd my-python-module
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git init
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;touch my_module.py
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git commit -m &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Initial commit&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 將兩個專案 push 到 Github/Gitlab 上&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;my-python-repo&lt;/code&gt; 與 &lt;code&gt;my-python-module&lt;/code&gt; 都已經是一個獨立的 git repo。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang Composition over Inheritance</title>
      <link>https://www.omegaatt.com/blogs/develop/2025/golang_composition_over_inheritance/</link>
      <pubDate>Sat, 11 Jan 2025 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2025/golang_composition_over_inheritance/</guid>
      <description>&lt;p&gt;Golang 是一門簡潔有力的程式語言，相較於其他程式語言，更傾向於使用組合（composition）而不是繼承（inheritance），語言設計之初更是沒有提供繼承的關鍵字，這種設計哲學讓 Golang 在現代軟體開發中脫穎而出。&lt;/p&gt;
&lt;p&gt;繼承固然有其優點，但在建構複雜的物件關係時，容易產生過於龐大的繼承層級結構。這使得程式碼難以閱讀和維護，就像是一棵盤根錯節的大樹，牽一髮而動全身。&lt;/p&gt;
&lt;p&gt;過深的繼承層級會導致知名的&lt;a href=&#34;https://github.com/Dobiasd/articles/blob/master/implementation_inheritance_is_bad_-_the_fragile_base_class_problem.md&#34;&gt;「脆弱基類問題」(fragile base class problem)&lt;/a&gt;，使得程式碼難以修改和擴展。&lt;/p&gt;
&lt;p&gt;組合則不同，它鼓勵建立小型、專注的 struct，然後像樂高積木一樣，將這些 struct 組合成更大的結構。這種方式讓程式碼模組化，更容易理解和修改。&lt;/p&gt;
&lt;h2 id=&#34;彈性&#34;&gt;彈性&lt;/h2&gt;
&lt;p&gt;Golang 的 type system 支援我們靈活地組合各種 struct。可以建立一個新的 struct，並在其中「嵌入」其他 struct 作為其欄位。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Car&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;make&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;year&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Driver&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;car&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;Car&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;myCar&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Car&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Toyota&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Camry&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2020&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Driver&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;John&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;myCar&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;name&lt;/span&gt;)         &lt;span style=&#34;color:#75715e&#34;&gt;// 輸出: John&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;driver&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;car&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;make&lt;/span&gt;)    &lt;span style=&#34;color:#75715e&#34;&gt;// 輸出: Toyota&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在這個例子中，&lt;code&gt;Driver&lt;/code&gt; 透過組合 &lt;code&gt;Car&lt;/code&gt; 來建立更豐富的資料結構。&lt;code&gt;Driver&lt;/code&gt; 「has-a」 &lt;code&gt;Car&lt;/code&gt;，而不是 「is-a」 &lt;code&gt;Car&lt;/code&gt;，這提供了更高的彈性，讓 &lt;code&gt;Driver&lt;/code&gt; 可以更專注在自身的邏輯。&lt;/p&gt;
&lt;h2 id=&#34;程式碼複用&#34;&gt;程式碼複用&lt;/h2&gt;
&lt;p&gt;組合促進了程式碼的複用。可以建立許多小型、可複用的 struct，然後將它們組合成各種不同的結構。&lt;/p&gt;
&lt;p&gt;繼承也能實現程式碼複用，但它也可能導致不必要的耦合，使得程式碼難以修改，因為對父類別的修改可能會影響到所有子類別。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Engine&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;power&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;fuelType&lt;/span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Wheels&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;count&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;material&lt;/span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Vehicle&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;engine&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Engine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;wheels&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Wheels&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;brand&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Vehicle&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;getBrand&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;brand&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Vehicle&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;getEnginePower&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;engine&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;power&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;在這個例子中，&lt;code&gt;Vehicle&lt;/code&gt; 透過組合 &lt;code&gt;Engine&lt;/code&gt; 和 &lt;code&gt;Wheels&lt;/code&gt; 來複用這兩個 struct 的欄位和功能。&lt;code&gt;Vehicle&lt;/code&gt; 「has-a」 &lt;code&gt;Engine&lt;/code&gt; and 「has-a」 &lt;code&gt;Wheels&lt;/code&gt;，並可以新增自己的欄位和方法，例如 &lt;code&gt;brand&lt;/code&gt;、&lt;code&gt;getBrand()&lt;/code&gt; 和 &lt;code&gt;getEnginePower()&lt;/code&gt;。這種方式讓 &lt;code&gt;Vehicle&lt;/code&gt; 可以專注於自身的邏輯，同時又能複用 &lt;code&gt;Engine&lt;/code&gt; 和 &lt;code&gt;Wheels&lt;/code&gt; 的功能。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 1.22 中 http routing 的改進</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/golang_http_routing_122/</link>
      <pubDate>Wed, 30 Oct 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/golang_http_routing_122/</guid>
      <description>&lt;p&gt;Golang 作為一個偏向 server 應用的程式語言，一般的 web server 並不會直接使用原生的 package &lt;code&gt;net/http&lt;/code&gt;，而更多的使用 &lt;code&gt;gin-gonic/gin&lt;/code&gt; 或是 &lt;code&gt;gorilla/mux&lt;/code&gt;，後來也有 &lt;code&gt;labstack/echo&lt;/code&gt; 以及 &lt;code&gt;go-chi/chi&lt;/code&gt; 等等選擇，在效能、輕量、好維護、好擴充中，都能找到對應的 third party package，其中的原因不外乎是原生的 package 提供的功能過於簡潔。&lt;/p&gt;
&lt;p&gt;好在 1.22 中，官方改進了 &lt;code&gt;net/http&lt;/code&gt; 中對於多工器、路由，甚至出了一篇&lt;a href=&#34;https://go.dev/blog/routing-enhancements&#34;&gt;部落格&lt;/a&gt;，現在更可以「大膽的」直接使用 standard library。&lt;/p&gt;
&lt;h2 id=&#34;path-parameter&#34;&gt;Path Parameter&lt;/h2&gt;
&lt;p&gt;若要將應用的 Web API 定義成 RESTful，我們會使用 &lt;code&gt;/資源/{資源唯一識別符}/子資源/{子資源唯一識別符}&lt;/code&gt; 來定義路徑。假如要獲取一個使用者的訂單，則會使用 &lt;code&gt;GET /users/1/orders&lt;/code&gt; 來獲取。在 1.22 以前，我們只能定義到 &lt;code&gt;/users&lt;/code&gt;，再自行解析往後的 path：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HandleFunc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/users&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ResponseWriter&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;subPath&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;strings&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;TrimPrefix&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;req&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;URL&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Path&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/users/&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; len(&lt;span style=&#34;color:#a6e22e&#34;&gt;subPath&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;xxx&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;ooo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;而在 1.22 中新增了 &lt;code&gt;net.http&lt;/code&gt; 對 path parameter 的支持，我們可以直接使用 &lt;code&gt;(*http.Request).PathValue(&amp;quot;xxx&amp;quot;)&lt;/code&gt; 來獲取：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HandleFunc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/users/{user_id}&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;w&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;ResponseWriter&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;http&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;userID&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;PathValue&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user_id&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;不過也帶來一些限制&lt;/p&gt;</description>
    </item>
    <item>
      <title>使用 TinyGo 與 Raspberry pi pico 實現溫濕度感測器</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/tinygo_temperature_humidity_sensor/</link>
      <pubDate>Wed, 02 Oct 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/tinygo_temperature_humidity_sensor/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;作為嵌入式系統學習的微小專案，我決定使用 &lt;a href=&#34;https://tinygo.org/&#34;&gt;TinyGo&lt;/a&gt; 來實現一個簡單的溫濕度感測器。過去我大多使用 Arduino 作為微控制器（MCU）開發平台，但這次想嘗試使用 TinyGo 來進行單片機的學習。
TinyGo 是 Go 語言的一個子集，專門針對小型設備和微控制器進行了優化，使得我們可以在資源受限的硬體上運行 Go 程式。&lt;/p&gt;
&lt;p&gt;全部的程式碼都可以到 &lt;a href=&#34;https://github.com/omegaatt36/pico-bme280&#34;&gt;omegaatt36/pico-bme280&lt;/a&gt; 中找到。&lt;/p&gt;
&lt;h2 id=&#34;硬體選擇&#34;&gt;硬體選擇&lt;/h2&gt;
&lt;h3 id=&#34;raspberry-pi-pico&#34;&gt;Raspberry Pi Pico&lt;/h3&gt;
&lt;p&gt;我選擇了 Raspberry Pi Pico 作為本專案的主控板。Pico 是一款基於 RP2040 晶片的微控制器開發板，具有以下特點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;雙核 ARM Cortex-M0+ 處理器，時脈可達 133 MHz&lt;/li&gt;
&lt;li&gt;264KB 的 SRAM 和 2MB 的板載閃存&lt;/li&gt;
&lt;li&gt;支援 USB 1.1 主機和設備功能&lt;/li&gt;
&lt;li&gt;低功耗睡眠和休眠模式&lt;/li&gt;
&lt;li&gt;可編程 I/O（PIO）狀態機&lt;/li&gt;
&lt;li&gt;30 個 GPIO 引腳&lt;/li&gt;
&lt;li&gt;便宜&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;價格僅需要 5 美元，後繼款的 &lt;a href=&#34;https://makerpro.cc/2024/08/the-second-generation-raspberry-pi-pico-is-here/&#34;&gt;Pico 2&lt;/a&gt; 仍然維持 5 美元，還額外增加了 RISC-V 架構的支援，可以一次玩到兩種處理器架構。
Pico 的這些特性使其非常適合用於各種嵌入式專案，包括我們的溫濕度感測器。&lt;/p&gt;
&lt;h3 id=&#34;bme280-感測器&#34;&gt;BME280 感測器&lt;/h3&gt;
&lt;p&gt;最初我購買了 &lt;a href=&#34;https://www.bosch-sensortec.com/products/environmental-sensors/pressure-sensors/bmp280/&#34;&gt;BMP280&lt;/a&gt; 感測器，但後來發現它只能測量溫度和氣壓，無法測量濕度。因此，我轉而選擇了 &lt;a href=&#34;https://www.bosch-sensortec.com/products/environmental-sensors/humidity-sensors-bme280/&#34;&gt;BME280&lt;/a&gt; 感測器，它可以同時測量溫度、濕度和氣壓。&lt;/p&gt;
&lt;p&gt;BME280 是一款精確度高、體積小、待機功耗低的環境感測器。但當連續偵測時，通過的電流會導致 sensor 功耗上升，進而導致測量到過高的溫度。&lt;/p&gt;</description>
    </item>
    <item>
      <title>透過內建的 pprof 工具來分析 Golang 發生的 memory leak</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/golang_memory_leak_detection/</link>
      <pubDate>Sat, 17 Aug 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/golang_memory_leak_detection/</guid>
      <description>&lt;h2 id=&#34;前言&#34;&gt;前言&lt;/h2&gt;
&lt;p&gt;某天下午，公司的 cronjob daemon 無預警的被 GCP OOM Kill 了，且程式碼沒有看出明顯的原因。&lt;/p&gt;
&lt;p&gt;根據過去的經驗，local 開發時會使用 &lt;code&gt;go tool pprof&lt;/code&gt; 來分析 CPU profile 或是 memory 與 trace 的問題，詳細可以參考 &lt;a href=&#34;https://go.dev/blog/pprof&#34;&gt;Go 官方文件&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;由於我們的程式碼是一個基於 gin 的 http service，因此可以使用 &lt;a href=&#34;https://github.com/gin-contrib/pprof&#34;&gt;gin 提供的 pprof&lt;/a&gt; 來快速建立 endpoint。&lt;/p&gt;
&lt;h2 id=&#34;gin-pprof&#34;&gt;gin pprof&lt;/h2&gt;
&lt;p&gt;gin 的 pprof package 提供了數個基於 &lt;code&gt;net/http/pprof&lt;/code&gt; 的 endpoint，可以分別為：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/&lt;/code&gt;: 基本的 pprof 的 static page，可以分析 CPU 與 memory 的問題。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/cmdline&lt;/code&gt;: 分析 command line 的問題。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/profile&lt;/code&gt;: 分析 CPU profile 的問題，可以透過 query string 來指定 CPU profile 的 duration。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/symbol&lt;/code&gt;: 分析 symbol table 的問題。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/trace&lt;/code&gt;: 分析 trace 的問題。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/goroutine&lt;/code&gt;: 分析 goroutine 的問題，這也是本文中重點查看的 endpoint。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/heap&lt;/code&gt;: 分析 heap 的問題。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可以在 http server 中加入以下程式碼來啟用 pprof：&lt;/p&gt;</description>
    </item>
    <item>
      <title>基於 Golang 的 Grafana Dashboard 與 JWT 認證的前後端實作</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/grafana_embed_example/</link>
      <pubDate>Mon, 10 Jun 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/grafana_embed_example/</guid>
      <description>&lt;p&gt;在工作上有一個需求是需要做一些 &lt;a href=&#34;https://aws.amazon.com/tw/what-is/olap/&#34;&gt;OLAP&lt;/a&gt;，原訂計畫是使用 Google Looker(ver. Google Cloud Core)，礙於量小不符合經濟效益，決定用 Grafana 這個較熟悉的開源套件來幫助我們做視覺化的處理。&lt;/p&gt;
&lt;p&gt;這篇文章的範例可以在 &lt;a href=&#34;https://github.com/omegaatt36/grafana-embed-example&#34;&gt;omegaatt36/grafana-embed-example&lt;/a&gt; 中找到所有的 source code&lt;/p&gt;
&lt;p&gt;我的 Use Case 為已經有一組 SHA512 產生的 Key，以下的內容為使用 HS512 進行簽名與認證。&lt;/p&gt;
&lt;h2 id=&#34;流程&#34;&gt;流程&lt;/h2&gt;
&lt;pre class=&#34;mermaid&#34;&gt;sequenceDiagram
    autonumber
    participant U as User
    participant B as Browser
    participant I as iframe
    participant S as Server
    participant G as Grafana

    rect rgb(236,239,244)
    U-&gt;&gt;B: Open Web Page
    B-&gt;&gt;S: Request JWT Token
    S-&gt;&gt;B: Return JWT Token
    B-&gt;&gt;I: Load iframe
    I-&gt;&gt;G: Request Dashboard with JWT Token
    G-&gt;&gt;I: Return Dashboard
    I-&gt;&gt;B: Display Dashboard in iframe
    B-&gt;&gt;U: Show Dashboard
    end
&lt;/pre&gt;

&lt;h2 id=&#34;grafana&#34;&gt;Grafana&lt;/h2&gt;
&lt;h3 id=&#34;配置&#34;&gt;配置&lt;/h3&gt;
&lt;p&gt;主要是針對 &lt;code&gt;grafana.ini&lt;/code&gt; 做修改&lt;/p&gt;</description>
    </item>
    <item>
      <title>用 testcontainers 在本地開發 Go 應用程式</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/testcontainers_with_go/</link>
      <pubDate>Sun, 19 May 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/testcontainers_with_go/</guid>
      <description>&lt;h2 id=&#34;介紹&#34;&gt;介紹&lt;/h2&gt;
&lt;p&gt;使用 &lt;a href=&#34;https://testcontainers.com/&#34;&gt;testcontainers&lt;/a&gt; 是在本地開發 Golang 應用程式的一個高效方式。這可以讓我們在不需要依賴外部環境的情況下，模擬應用程式在實際生產環境中的運行狀況。&lt;/p&gt;
&lt;h3 id=&#34;安裝-testcontainers&#34;&gt;安裝 testcontainers&lt;/h3&gt;
&lt;p&gt;在 Go 專案中，我們可以通過以下指令來導入 testcontainers&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;go get github.com/testcontainers/testcontainers-go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;透過-redis-實踐一個-rate-limiter&#34;&gt;透過 Redis 實踐一個 rate limiter&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;user&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Limiter&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;redis&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Client&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;         &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&lt;/span&gt;   &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Duration&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// 1 hour for limitPeriod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;counterWindow&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Duration&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// 1 minute for example, 1/60 of the period&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;NewLimiter&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;redis&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Client&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;period&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;expiry&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Duration&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Limiter&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Limiter&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;:         &lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&lt;/span&gt;:   &lt;span style=&#34;color:#a6e22e&#34;&gt;period&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;counterWindow&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;expiry&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Limiter&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;AllowRequest&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;incr&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;now&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Now&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;timestamp&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sprint&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;now&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Truncate&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;counterWindow&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;Unix&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;val&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HIncrBy&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;timestamp&lt;/span&gt;, int64(&lt;span style=&#34;color:#a6e22e&#34;&gt;incr&lt;/span&gt;)).&lt;span style=&#34;color:#a6e22e&#34;&gt;Result&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;val&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; int64(&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ErrRateLimitExceeded&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;now&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Add&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Expire&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HGetAll&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;Result&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;!=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;threshold&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sprint&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;now&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Add&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;Unix&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;total&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;k&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;result&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;k&lt;/span&gt; &amp;gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;threshold&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;strconv&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Atoi&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;v&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;total&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        } &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;HDel&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;key&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;k&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;total&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; int(&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ErrRateLimitExceeded&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;now&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Add&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;r&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;limitPeriod&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RateLimitExceeded&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Remaining&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Limit&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Period&lt;/span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Duration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;Reset&lt;/span&gt;     &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ErrRateLimitExceeded&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;remaining&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;period&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Duration&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;reset&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;time&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Time&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RateLimitExceeded&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;Remaining&lt;/span&gt;: &lt;span style=&#34;color:#a6e22e&#34;&gt;remaining&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;Limit&lt;/span&gt;:     &lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;Period&lt;/span&gt;:    &lt;span style=&#34;color:#a6e22e&#34;&gt;period&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;Reset&lt;/span&gt;:     &lt;span style=&#34;color:#a6e22e&#34;&gt;reset&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;RateLimitExceeded&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;Error&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Sprintf&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rate limit of %d per %v has been exceeded and resets at %v&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Limit&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Period&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;e&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Reset&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;創建和啟動-redis-容器&#34;&gt;創建和啟動 Redis 容器&lt;/h3&gt;
&lt;p&gt;正式的產品通常會使用 config 來管理 Redis 位置，這個 demo 中直接使用 &lt;code&gt;localhost:6379&lt;/code&gt; 來展示。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何利用 Open Policy Agent 配合 Golang 建構彈性的 RBAC 模組</title>
      <link>https://www.omegaatt.com/blogs/develop/2024/golang_rbac_with_open_policy_agent/</link>
      <pubDate>Thu, 04 Apr 2024 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2024/golang_rbac_with_open_policy_agent/</guid>
      <description>&lt;h2 id=&#34;rbac-概念簡介&#34;&gt;RBAC 概念簡介&lt;/h2&gt;
&lt;p&gt;在我們探討如何利用 Open Policy Agent (以下簡稱 OPA) 和 Golang 建立一個彈性的 RBAC 模組之前，先讓我們來了解一下 RBAC的基本概念。&lt;/p&gt;
&lt;p&gt;RBAC（Role-Based Access Control，基於角色的訪問控制）是一種廣泛應用的訪問控制策略，在軟體安全性領域尤為重要。其核心思想是將系統訪問權限與用戶的角色（職位、責任或職務）關聯起來，而不是直接與個別用戶關聯。這意味著訪問權限被捆綁到角色上，然後將用戶分配給這些角色。舉個例子，一個「管理員」角色可能有權訪問系統的所有資源，而「員工」角色則只能訪問特定部分的資源。&lt;/p&gt;
&lt;p&gt;RBAC 的主要優勢在於其靈活性和簡化的權限管理。當需要變更權限時，只需修改角色的訪問權限，而不需要為每個用戶單獨設定。這不僅使權限管理更為高效，也減少了錯誤配置的可能性，提高了整體的系統安全性。&lt;/p&gt;
&lt;p&gt;在實踐中，RBAC 允許創建精細且靈活的策略，以滿足複雜的商業和安全需求。無論是大型企業還是小型團隊，RBAC 都提供了一個可靠的框架，來確保正確的用戶擁有適當的訪問權限，從而保護關鍵資源免受未授權訪問。&lt;/p&gt;
&lt;p&gt;可以參考 &lt;a href=&#34;https://www.cloudflare.com/zh-tw/learning/access-management/role-based-access-control-rbac/&#34;&gt;Cloudflare 的文章&lt;/a&gt;，簡單來說就是&lt;strong&gt;什麼「角色」能夠對什麼「資源」做什麼「操作」&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id=&#34;open-policy-agent-opa-介紹&#34;&gt;Open Policy Agent (OPA) 介紹&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://www.openpolicyagent.org/&#34;&gt;OPA&lt;/a&gt; 是一個「Strategy as Code」的開源專案，專門設計用於統一地管理和執行跨不同系統的策略。它不僅提供了一個高級的策略語言——Rego，還支援將策略作為代碼與應用程式的其他部分一同存儲、版本控制和部署。OPA 的這種設計使其能夠輕鬆集成到微服務、Kubernetes、CI/CD 管道、API 網關等多種環境中。顯著特點是其策略的編寫方式。Rego 是一種專門為策略和規則定制的查詢語言，它使開發者能夠以聲明式方式描述策略和規則，從而確保這些策略既容易理解又易於維護。這對於建立複雜的 RBAC 系統尤為重要，因為它允許策略的靈活性和可擴展性，同時又保持了清晰和易於審查的結構。&lt;/p&gt;
&lt;p&gt;我們可以利用 OPA 提供的 API 來評估和執行這些策略。這意味著開發者可以在 Golang 程式碼中直接嵌入策略判斷的邏輯，從而實現動態、細粒度的訪問控制。這種方法的一個優點是，它支援在 runtime 動態更新策略，或是編譯進 binary，從而提供更大的靈活性和即時性。&lt;/p&gt;
&lt;h2 id=&#34;整合-opa-與-golang&#34;&gt;整合 OPA 與 Golang&lt;/h2&gt;
&lt;h3 id=&#34;透過官方案例來了解如何使用&#34;&gt;透過官方案例來了解如何使用&lt;/h3&gt;
&lt;p&gt;參考了 &lt;a href=&#34;https://www.openpolicyagent.org/docs/latest/comparison-to-other-systems/&#34;&gt;OPA 官方的 rbac 章節&lt;/a&gt;，並加以修改。使用最簡單的例子：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;admin can read user
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bob is admin
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;-------------------
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;bob can read user
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;轉化成 RBAC 模型即為：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 隱式記憶體別名 Implicit Memory Aliasing 與其檢測方法</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/golang_implicit_memory_aliasing/</link>
      <pubDate>Sun, 12 Nov 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/golang_implicit_memory_aliasing/</guid>
      <description>&lt;p&gt;在使用 Golang 時，我們可能會遇到一種稱為隱式記憶體別名（Implicit Memory Aliasing）的問題。這篇文章將探討這個問題以及如何使用工具和語言特性來解決它。&lt;/p&gt;
&lt;h2 id=&#34;隱式記憶體別名的問題&#34;&gt;隱式記憶體別名的問題&lt;/h2&gt;
&lt;p&gt;隱式記憶體別名主要發生在 &lt;code&gt;range&lt;/code&gt; 語句中。當使用 &lt;code&gt;range&lt;/code&gt; 對一個切片 slice 或映射 map 進行迭代時，Iterator 在每次迭代中並不是唯一的實例。這可能導致意外的行為，特別是在並發環境或當迭代變量被指針引用時。&lt;/p&gt;
&lt;h3 id=&#34;slice-with-implicit-memory-aliasing&#34;&gt;&lt;a href=&#34;https://go.dev/play/p/E_0aBWZcsmZ&#34;&gt;slice with Implicit Memory Aliasing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;下面示例可能是基本的 golang 面試題，會問你迭代後的 pointers 內的 Name 為何
答案是 Joe Joe Joe&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;fmt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Person&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Gender&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;persons&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; []&lt;span style=&#34;color:#a6e22e&#34;&gt;Person&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;John&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Gender&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;M&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Jane&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Gender&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;F&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		{&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Joe&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;Gender&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;X&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;pointers&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; make([]&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, len(&lt;span style=&#34;color:#a6e22e&#34;&gt;persons&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;person&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;persons&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Printf&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;%p, %p, %p, %v\n&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;person&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;person&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;person&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Gender&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;persons&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;].&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;pointers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;] = &lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;person&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;range&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;pointers&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;fmt&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Println&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;pointers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;], &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;pointers&lt;/span&gt;[&lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;channel-with-implicit-memory-aliasing&#34;&gt;&lt;a href=&#34;https://go.dev/play/p/N21dlpTr_9G&#34;&gt;channel with Implicit Memory Aliasing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;同樣的情況也會發生在 channel 的迭代。&lt;/p&gt;</description>
    </item>
    <item>
      <title>golang 樂觀鎖、悲觀鎖 學習筆記與實驗</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/golang_lock_practice/</link>
      <pubDate>Sun, 14 May 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/golang_lock_practice/</guid>
      <description>&lt;h2 id=&#34;介紹&#34;&gt;介紹&lt;/h2&gt;
&lt;p&gt;改變數值的三個步驟&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;取出&lt;/li&gt;
&lt;li&gt;修改&lt;/li&gt;
&lt;li&gt;保存&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但這三者間的時間差在不同 process、不同 thread、不同 corutine/goroutine 中會造成競爭危害(race condition)。&lt;/p&gt;
&lt;p&gt;可以使用多種發法確保並行(concurrency)處理時保持資料的一致性，這裡介紹的是最常使用的悲觀鎖與樂觀鎖。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;悲觀鎖: 總可能發生問題
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;lock
(1) 取值
(2) 修改
(3) 保存
unlock
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;樂觀鎖: 不會總是發生問題
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;(1) 取值
if *addr == old {
    (2) 修改
    (3) 保存
    return true
}
return false
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;悲觀鎖&#34;&gt;悲觀鎖&lt;/h2&gt;
&lt;p&gt;golang 中主要使用 &lt;code&gt;sync.Mutex&lt;/code&gt; 作為悲觀鎖，看似會阻塞住其他 goroutines，但其實 &lt;code&gt;sync.Mutex&lt;/code&gt; 中也使用到了 CAS。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sync.Mutex&lt;/code&gt; 中有一個 &lt;code&gt;int32&lt;/code&gt; 的 &lt;code&gt;state&lt;/code&gt; 與 &lt;code&gt;uint32&lt;/code&gt; 的 &lt;code&gt;sema&lt;/code&gt;(semaphore)&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;Mutex&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int32&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;sema&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;uint32&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;mutexLocked&lt;/span&gt; = &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;iota&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;// mutex is locked&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;mutexWoken&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;mutexStarving&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;mutexWaiterShift&lt;/span&gt; = &lt;span style=&#34;color:#66d9ef&#34;&gt;iota&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;starvationThresholdNs&lt;/span&gt; = &lt;span style=&#34;color:#ae81ff&#34;&gt;1e6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;Mutex&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;Lock&lt;/span&gt;() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;atomic&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;CompareAndSwapInt32&lt;/span&gt;(&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;state&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;mutexLocked&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;race&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Enabled&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#a6e22e&#34;&gt;race&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Acquire&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;unsafe&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Pointer&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;lockSlow&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;state&lt;/code&gt;:&lt;/p&gt;</description>
    </item>
    <item>
      <title>golang 中使用 Line LIFF 實作 Single Sign-On</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/line_verify/</link>
      <pubDate>Sat, 22 Apr 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/line_verify/</guid>
      <description>&lt;p&gt;文章中的程式碼放在&lt;a href=&#34;https://github.com/omegaatt36/line-verify-id-token&#34;&gt;https://github.com/omegaatt36/line-verify-id-token&lt;/a&gt;中。&lt;/p&gt;
&lt;h3 id=&#34;requirements&#34;&gt;Requirements&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://developers.line.biz/zh-hant/&#34;&gt;Line Login Channel&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;channel id&lt;/li&gt;
&lt;li&gt;channel secret&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/line/line-liff-v2-starter&#34;&gt;Line LIFF APP&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;用 liff.getIDToken() 獲取 &lt;a href=&#34;https://developers.line.biz/en/docs/line-login/verify-id-token/#get-an-id-token&#34;&gt;ID Token&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在現代網站中，單一登錄 (Single Sign-On, SSO) 已經成為了一個普遍存在的功能，它能夠讓使用者在不同的應用程式和服務之間自動地登錄，而不需要再輸入帳號和密碼。這樣可以方便使用者的使用，並且也能夠增加安全性，減少帳號密碼被盜用的風險。&lt;/p&gt;
&lt;p&gt;我們將使用 Golang 語言來實作單一登錄功能，並且使用 Line LIFF 來進行身份驗證。在此之前，我們需要先了解幾個概念。&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://developers.line.biz/en/docs/liff/overview/&#34;&gt;Line LIFF (Line Front-end Framework)&lt;/a&gt; 是一個由 Line 提供的 Web 應用程式框架，開發者可以使用它來建立 Line 的客戶端應用程式。使用 Line LIFF 開發的應用程式可以在 Line 客戶端中被直接執行，而不需要額外安裝或下載。此外，Line LIFF 還提供了一些功能，例如使用者的身份驗證、分享資料等等。&lt;/p&gt;
&lt;p&gt;JWT (JSON Web Token) 則是一種開放標準，用於在不同系統之間安全地傳輸訊息。它通常用於認證和授權，因為它可以確保傳輸的訊息是可信的，而且在傳輸過程中不會被竄改。&lt;/p&gt;
&lt;p&gt;我們可以使用 Line 的 Verify API，同時也可以選擇後端&lt;a href=&#34;https://developers.line.biz/en/docs/line-login/verify-id-token/#write-original-code&#34;&gt;自己驗證&lt;/a&gt;，解出 jwt 中的資訊。&lt;/p&gt;
&lt;p&gt;我們的目的是拿到每個使用者在 Line 的 UserID，做為身分識別。於是根據 &lt;a href=&#34;https://developers.line.biz/en/docs/line-login/verify-id-token/#payload&#34;&gt;Line 提供的 JWT 欄位對應&lt;/a&gt;，定義一個結構用來存放驗證後的資訊。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// DecodedIDToken defines decoded payload by id token.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DecodedIDToken&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;struct&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Amr&lt;/span&gt;       []&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;ChannelID&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Email&lt;/span&gt;     &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;ExpiredAt&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;IssuedAt&lt;/span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;int64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Issuer&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Name&lt;/span&gt;      &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Picture&lt;/span&gt;   &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;UserID&lt;/span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接著撰寫一個驗證 ID Token 的 function 用來驗證&lt;/p&gt;</description>
    </item>
    <item>
      <title>跟風寫了個使用 OpenAI API 的 Telegram Chat Bot</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/openai_telegram_chat_bot/</link>
      <pubDate>Fri, 17 Mar 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/openai_telegram_chat_bot/</guid>
      <description>&lt;h1 id=&#34;前言&#34;&gt;前言&lt;/h1&gt;
&lt;p&gt;放在最前面 &lt;a href=&#34;https://github.com/omegaatt36/chatelegram&#34;&gt;Github repo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;原本打算使用別人寫好的 bot，免費的版本均是透過儲存 cookies 的方式，直接透過 ChatGPT 網頁建立新的 「Chat」。好景不常，OpenAI 馬上把登入頁面到聊天頁面中間安插了類似 reCaptcha，來驗證是否為真人。看到有好心人提供了 go 版本的 OpenAi API，就打算自己寫一寫，順便當作 &lt;a href=&#34;https://www.tenlong.com.tw/products/9789864342945&#34;&gt;《Clean Architecture》&lt;/a&gt; 的讀後作業。&lt;/p&gt;
&lt;h1 id=&#34;依賴注入&#34;&gt;依賴注入&lt;/h1&gt;
&lt;p&gt;&lt;del&gt;架構圖待補&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;我需要對 OpenAI 詢問，並用 stream(逐字)的方式回給我，也需要對 Telegram 逐字回答。&lt;/p&gt;
&lt;p&gt;在&lt;a href=&#34;https://github.com/omegaatt36/chatelegram/tree/8931bfbf9e0e00891c07aaaf0c12aa730e34dd39&#34;&gt;這個 8931bf 版本&lt;/a&gt;中，很明確定義對於 OpenAI 與 Telegram 的 usecase&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;chatgpt_usecase.go&lt;/code&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ChatGPTUseCase defines ChatGPT send question use case.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;ChatGPTUseCase&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;Stream&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;ctx&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;context&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Context&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;question&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) (&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;telegram_usecase.go&lt;/code&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// TelegramUseCase defines telegram send message use case.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TelegramUseCase&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;interface&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;SendAsLiveOutput&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;chatID&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int64&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;feed&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;-&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;chan&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 &lt;code&gt;*_repository.go&lt;/code&gt; 進行實作。並在 &lt;code&gt;main.go&lt;/code&gt; 中依賴反轉，將實作注入近 bot service。&lt;/p&gt;</description>
    </item>
    <item>
      <title>gin 搭配 html/template 包實現動態生成 HTML 文件</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/gin_embed_practice/</link>
      <pubDate>Thu, 26 Jan 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/gin_embed_practice/</guid>
      <description>&lt;h2 id=&#34;起因&#34;&gt;起因&lt;/h2&gt;
&lt;p&gt;在網站註冊流程中，若是用信箱驗證，網站會寄送一封驗證信到指定的電子信箱。信中可能含有 verify token 或是直接是寫好的 verify URL。至於實作方面我們可以透過 Go 1.16 推出的 Embedding Files 搭配 html/template，實現動態生成 HTML 信件，用以寄送至指定信箱。&lt;/p&gt;
&lt;p&gt;本篇內容的詳細程式碼可以到 &lt;a href=&#34;https://github.com/omegaatt36/gin-embed-template&#34;&gt;github&lt;/a&gt; 頁面查看。&lt;/p&gt;
&lt;h2 id=&#34;實作&#34;&gt;實作&lt;/h2&gt;
&lt;p&gt;資料夾結構:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;project/
├─ templates/
│  ├─ template.go
│  ├─ success.tmpl
│  ├─ verify.tmpl
├─ main.go
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;templates&#34;&gt;templates&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;project/templates/template.go&lt;/p&gt;&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在包內宣告私有變數，透過 Embedding Files 讀出目錄內的所有檔案。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;template&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;//go:embed *&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;f&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;embed&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;FS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;宣告需要被轉換成 HTML 模板的檔案名 &lt;code&gt;TemplateName&lt;/code&gt;，可以用 &lt;a href=&#34;https://github.com/abice/go-enum&#34;&gt;go-enum&lt;/a&gt; 來自動生成變數。並將其註冊進陣列 &lt;code&gt;_TemplateNameNames&lt;/code&gt; 內。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;package&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;template&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// ENUM(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// success.tmpl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// verify.tmpl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// )&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;type&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateName&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; (&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateName&lt;/span&gt;) &lt;span style=&#34;color:#a6e22e&#34;&gt;String&lt;/span&gt;() &lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; string(&lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;_TemplateNameNames&lt;/span&gt; = []&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	string(&lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateNameVerifyTmpl&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	string(&lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateNameSuccessTmpl&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// TemplateNameNames returns a list of possible string values of TemplateName.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateNameNames&lt;/span&gt;() []&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;tmp&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; make([]&lt;span style=&#34;color:#66d9ef&#34;&gt;string&lt;/span&gt;, len(&lt;span style=&#34;color:#a6e22e&#34;&gt;_TemplateNameNames&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	copy(&lt;span style=&#34;color:#a6e22e&#34;&gt;tmp&lt;/span&gt;, &lt;span style=&#34;color:#a6e22e&#34;&gt;_TemplateNameNames&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;tmp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateNameVerifyTmpl&lt;/span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateName&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;verify.tmpl&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateNameSuccessTmpl&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TemplateName&lt;/span&gt; = &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;success.tmpl&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最終將 embed files 與檔案名丟給 &lt;code&gt;html/template&lt;/code&gt; 包請他們我們轉換成 template。&lt;/p&gt;</description>
    </item>
    <item>
      <title>後端工程師用 Golang 在 WSL 中寫 GUI 是不是搞錯了甚麼 - 安裝篇</title>
      <link>https://www.omegaatt.com/blogs/develop/2023/golang_gui_fyne_bme280/</link>
      <pubDate>Thu, 26 Jan 2023 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2023/golang_gui_fyne_bme280/</guid>
      <description>&lt;p&gt;由於先前將 raspberry pi 4b 給賣掉了，&lt;a href=&#34;https://github.com/omegaatt36/bmx280/tree/main/raspberrypi&#34;&gt;bmx280&lt;/a&gt; 中基於 bme280 而產生的 exporter 因此失去戰鬥能力，藉此玩玩 &lt;a href=&#34;https://tinygo.org/&#34;&gt;tinygo&lt;/a&gt;，透過 serial port 透過 raspberry pi pico 對 bme280 讀取溫溼度。在開發過程中發現，WSL 中，command line 輸入:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-shell&#34; data-lang=&#34;shell&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GOOS&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;windows go run main.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;這樣會吃 windows 的環境，比如讀 serial port 需要 COM port，這樣就不是去讀 /dev/ttyS4 而是讀 COM4，這 cross compile 也太方便了吧&amp;hellip;。&lt;/p&gt;
&lt;p&gt;後續在工作上接收了需要寫一個 websocket server/client，而 client 需要:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有圖形化最好 (TUI or GUI)&lt;/li&gt;
&lt;li&gt;跑在 windows 桌面上&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;，開發過程中再次感受到 cross compile 的方便，有點慶幸是在這個一個方便的時代學習敲鍵盤(?&lt;/p&gt;
&lt;h2 id=&#34;requirement&#34;&gt;Requirement&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Golang 1.16+ ()&lt;/li&gt;
&lt;li&gt;Docker or Podman&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;在-wsl-中建立-x-window-system-環境&#34;&gt;在 WSL 中建立 X Window System 環境&lt;/h2&gt;
&lt;p&gt;因為我的 Golang 環境是基於 WSL，於是我需要先準備在 WSL 中能跑的 GUI 環境。
在 Unix like 使用的 GUI 被稱之為 &lt;a href=&#34;https://en.wikipedia.org/wiki/X_Window_System&#34;&gt;X Window System 或 X11 或 X&lt;/a&gt;，以所需的情形來說即是需要在 Windows 中啟動 X server，來與 WSL 中的 X client 連線。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 學習筆記 Day 0 - Why Golang &amp; 安裝篇</title>
      <link>https://www.omegaatt.com/blogs/develop/2020/golang_learning_day0/</link>
      <pubDate>Tue, 25 Aug 2020 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2020/golang_learning_day0/</guid>
      <description>&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;免責聲明，此篇為個人學習筆記，非正式教學文，僅供參考感謝指教，有任何內容錯誤歡迎發 issue。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img alt=&#34;image source: https://medium.com/nordcloud-engineering/how-to-build-pluggable-golang-application-and-benefit-from-aws-lambda-layers-154c8117df9b&#34; loading=&#34;lazy&#34; src=&#34;https://miro.medium.com/max/2946/1*TArmNwMaoXjR1MegmucwyQ.png&#34;&gt;&lt;/p&gt;
&lt;h2 id=&#34;why-golang&#34;&gt;Why Golang&lt;/h2&gt;
&lt;p&gt;    在高中時期，除了一年級上學期是用 C 打開程式大門之外，大家都知道的，畫金字塔、 &lt;code&gt;cin&lt;/code&gt; 、 &lt;code&gt;cout&lt;/code&gt; 等等基本的邏輯觀念，一年級下學期開始就是 C#、HTML、PHP，也玩轉在 CPLD、Arduino、焊三角錐、兩根同軸焊接，總之就是全部吃掉就對了，雖然都是皮毛但不至於完全沒印象。&lt;/p&gt;
&lt;p&gt;    然而到了大學，敝人不才沒有考上一般大學，也沒考上工科第一二志願科大，在整個大學過程碰了最多的反而是 python、machine learning。然而也都是碰皮毛&lt;del&gt;因為都在玩社團、學攝影&lt;/del&gt;，事實上在大學時期程式功力已荒廢大半。&lt;/p&gt;
&lt;p&gt;    所幸在實習期間，公司願意提供環境讓我邊學習邊成長後做出貢獻，而使用的後端語言是 PHP，然而就一年以來的使用 PHP 有以下優缺點：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;學習曲線&lt;del&gt;極&lt;/del&gt;低，但坑很多&lt;/li&gt;
&lt;li&gt;解釋型腳本語言部署極方便&lt;/li&gt;
&lt;li&gt;非靜態、非強行別、萬用 array 導致容易出現很多偷懶的 code&lt;/li&gt;
&lt;li&gt;窮人聖經 LAMP、LNMP&lt;/li&gt;
&lt;li&gt;歷史悠久 framework 眾多、工作好找，甚至有許多 C 底層的高校框架可以使用。&lt;/li&gt;
&lt;li&gt;可以很 OO 也可以不 OO，寫 code 彈性極大&lt;/li&gt;
&lt;li&gt;在 php 7.0 改用 zend 引擎後效能改頭換面，「原來 PHP 也可以寫遊戲」。搭配一些 C 底層框架，甚至可以跟 nodeJS 一較高下，而期望中 8.0 效能將再更高。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img loading=&#34;lazy&#34; src=&#34;https://i.imgur.com/CkKvVrE.jpg&#34;&gt;&lt;/p&gt;
&lt;p&gt;    但是，再快也只是 AE86，腳本語言終究與「高效」存在矛盾，同時，與其在 PHP 中配合 IDE 使用 type hinting 自我約束，何不直接寫強行別語言呢?? 然而在看過許多主流 web service 語言後，最後決定來學學 Golang，而關於 golang 的優勢，可以點擊下方圖片看更多詳細。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Golang 學習筆記 Day 1 - Getting started</title>
      <link>https://www.omegaatt.com/blogs/develop/2020/golang_learning_day1/</link>
      <pubDate>Tue, 25 Aug 2020 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2020/golang_learning_day1/</guid>
      <description>&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;免責聲明，此篇為個人學習筆記，非正式教學文，僅供參考感謝指教，有任何內容錯誤歡迎發 issue。
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在前一天介紹了為何要使用 golang、安裝過程以及基本的 hello world，這天將重點放在最基本的 golang 程式碼特徵。&lt;/p&gt;
&lt;h2 id=&#34;packages&#34;&gt;packages&lt;/h2&gt;
&lt;p&gt;正如昨天關於 hello world 的解析中提到的，每個程式都是由一到多個 packages 組成的，也就是 &lt;code&gt;.go&lt;/code&gt; 中必須宣告該檔案是哪個 package，且同一個資料夾中僅接受 &lt;code&gt;xxx&lt;/code&gt; 以及 &lt;code&gt;xxx_test&lt;/code&gt; 兩種 package名稱，也就是該 package 的名稱以及測試檔的「測試包」。&lt;/p&gt;
&lt;p&gt;至於 package 要怎麼命名呢，首先要提到檔案該如何命名，雖然 &lt;code&gt;go format&lt;/code&gt; 是對於程式碼風格十分嚴格的，但是對於檔案名稱似乎就沒那麼嚴謹，但整個社群中普遍會用 &lt;code&gt;snake_case&lt;/code&gt; 或是 &lt;code&gt;dash-case&lt;/code&gt; 作為檔案 (甚至是 package) 的命名。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;aaabbbccc.go&lt;/li&gt;
&lt;li&gt;time-machine.go&lt;/li&gt;
&lt;li&gt;battery_charger.go&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是需要注意的是 go build 會忽略所有以 &lt;code&gt;_&lt;/code&gt; 或是以 &lt;code&gt;.&lt;/code&gt; 作為結尾的後綴， &lt;a href=&#34;https://dave.cheney.net/2013/10/12/how-to-use-conditional-compilation-with-the-go-build-tool&#34;&gt; &lt;code&gt;underline&lt;/code&gt; 可能會導致在建構時被忽略&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;再來提到 package 到底要如何命名呢，&lt;a href=&#34;https://blog.golang.org/package-names&#34;&gt;官方文檔&lt;/a&gt;中明確的提到不要用 &lt;code&gt;camelCase&lt;/code&gt; 以及 &lt;code&gt;snake_case&lt;/code&gt; 。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The style of names typical of another language might not be idiomatic in a Go program. Here are two examples of names that might be good style in other languages but do not fit well in Go:&lt;/p&gt;</description>
    </item>
    <item>
      <title>RESTful server based on golang</title>
      <link>https://www.omegaatt.com/blogs/develop/2020/restful-api-with-go/</link>
      <pubDate>Thu, 19 Mar 2020 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2020/restful-api-with-go/</guid>
      <description>&lt;p&gt;很高興有個機會製作面試前的測試作業，也就趁這個機會做個筆記以及學習紀錄。&lt;/p&gt;
&lt;h2 id=&#34;前置作業&#34;&gt;前置作業&lt;/h2&gt;
&lt;p&gt;先點列一下項目需求：&lt;/p&gt;
&lt;h3 id=&#34;基本需求&#34;&gt;基本需求&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;based on golnag&lt;/li&gt;
&lt;li&gt;The API interface you provide can be any of the following：RESTful、json rpc、gRPC&lt;/li&gt;
&lt;li&gt;At least two sources&lt;/li&gt;
&lt;li&gt;When a source is unavailable, the result of its last successful ask is returned&lt;/li&gt;
&lt;li&gt;Use git to manage source code&lt;/li&gt;
&lt;li&gt;Write readme.md and describe what features, features, and TODO which have been implemented&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;額外可選&#34;&gt;額外可選&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Traffic limits, including the number of times your server queries the source, and the number of times the user queries you&lt;/li&gt;
&lt;li&gt;Good testing, annotations, and git commit&lt;/li&gt;
&lt;li&gt;An additional websocket interface is provided to automatically send the latest information whenever the market changes&lt;/li&gt;
&lt;li&gt;Users can choose to use an automatic source determination, the latest data source, or manually specify the source of the data&lt;/li&gt;
&lt;li&gt;Package it as a Dockerfile, docker-compose file, or kubernetes yaml&lt;/li&gt;
&lt;li&gt;There is a simple front-end or cli program that displays the results&lt;/li&gt;
&lt;li&gt;The API you provide has a corresponding file, such as a swagger, or simply a markdown file&lt;/li&gt;
&lt;li&gt;Other features not listed but that you thought would be cool to implement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;這邊就必須要先打預防針了，&lt;strong&gt;這篇僅為學習筆記&lt;/strong&gt;，並非正式教學。且，我只是 Golang 初學者，所以這篇內也不會出現什麼高等級的解法，甚至也是第一次實作介接 API 呢 (笑)。而 DB 以及 redis 都是直接以方便使用去寫 dbi 而已並無用抽象曾去封裝，也進而導致 unit test 十分困難先擱置了，有空才會再針對性重構，而此次就先將目標專注在 API 介接。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Codewars Find X II in golang</title>
      <link>https://www.omegaatt.com/blogs/develop/2020/codewars_find_x_ii/</link>
      <pubDate>Tue, 03 Mar 2020 00:00:00 +0000</pubDate><author>raiven.kao@gmail.com (Raiven Kao)</author>
      <guid>https://www.omegaatt.com/blogs/develop/2020/codewars_find_x_ii/</guid>
      <description>&lt;p&gt;想不到一眨眼半個月過去了，自從面試趨勢之後整個人彷彿被電到失去信心，在離職與等待面試消息期間，也只好振作補足不足的地方繼續向前。&lt;/p&gt;
&lt;p&gt;而 docker 學習心得何時會產出呢？在學習過程中發現除了一般操作(day1 內容)外，底層或多 container 溝通都是大量的網路觀念與知識，需要更多知識量才能整理成筆記，不急不急。&lt;/p&gt;
&lt;p&gt;由於學習過程發現而這段時間在 coding 能力上主要還是靠 codewars 上的題目，畢竟解完後能看別人的 code 來檢視自己的 code 能不能更好。其中不免遇到一些覺得很有趣的題目，好比這題 &lt;a href=&#34;https://www.codewars.com/kata/5d339b01496f8d001054887f&#34;&gt;Find X II&lt;/a&gt;，他並不是什麼過分難的題目，邏輯也不是很難通，畢竟只是一個 6 kyu 的題目，但為何快一年了只有 44 個人解完呢，這就牽扯到下面要說的。&lt;/p&gt;
&lt;h1 id=&#34;題目概要&#34;&gt;題目概要&lt;/h1&gt;
&lt;p&gt;有下一段程式碼:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FindX&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FindX&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;x&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但 x 範圍為 1 &amp;lt;= n &amp;lt;= &lt;code&gt;10**6(1e6)&lt;/code&gt; ，請試著重構。且當 n 愈來試大時，可能會超過 int64 表達，故需要將結果對 &lt;code&gt;10**9 + 7&lt;/code&gt; 取模&lt;/p&gt;
&lt;p&gt;標記 &lt;code&gt;FUNDAMENTALS&lt;/code&gt; &lt;code&gt;OPTIMIZATION&lt;/code&gt;&lt;/p&gt;
&lt;h1 id=&#34;解題邏輯&#34;&gt;解題邏輯&lt;/h1&gt;
&lt;p&gt;若沒有取模限制的話這題可以化簡為 O(1)，但很明顯不行。於是先將原題目化簡為 O(n):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-go&#34; data-lang=&#34;go&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;func&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;FindX&lt;/span&gt;(&lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt;) &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#a6e22e&#34;&gt;mod&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; int(&lt;span style=&#34;color:#a6e22e&#34;&gt;math&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Pow10&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt;)) &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;7&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;:=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;n&lt;/span&gt;; &lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;++&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt; = (&lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;&lt;span style=&#34;color:#a6e22e&#34;&gt;i&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;%&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;mod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;m&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;好測試過，就算是輸入 1e6 進去也很快就算出來了，於是提交答案&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
