<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by TengYao Chi on Medium]]></title>
        <description><![CDATA[Stories by TengYao Chi on Medium]]></description>
        <link>https://medium.com/@frankvicky?source=rss-abe1cb5099ad------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*F0STeOUwCwHR-N8cGDtxeQ.jpeg</url>
            <title>Stories by TengYao Chi on Medium</title>
            <link>https://medium.com/@frankvicky?source=rss-abe1cb5099ad------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Mon, 08 Jun 2026 00:19:28 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@frankvicky/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[[2025] 英國工作簽證申請記錄]]></title>
            <link>https://medium.com/@frankvicky/2025-%E8%8B%B1%E5%9C%8B%E5%B7%A5%E4%BD%9C%E7%B0%BD%E8%AD%89%E7%94%B3%E8%AB%8B%E8%A8%98%E9%8C%84-d40903be56d5?source=rss-abe1cb5099ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/d40903be56d5</guid>
            <category><![CDATA[skilled-worker-visa]]></category>
            <category><![CDATA[united-kingdom]]></category>
            <category><![CDATA[visa]]></category>
            <category><![CDATA[uk-visa]]></category>
            <dc:creator><![CDATA[TengYao Chi]]></dc:creator>
            <pubDate>Sat, 11 Oct 2025 15:53:13 GMT</pubDate>
            <atom:updated>2025-10-11T16:07:29.587Z</atom:updated>
            <content:encoded><![CDATA[<p>本篇記錄 Skilled Worker Visa 申請經驗，也分享自己的心得。</p><p>2025 年因緣際會可以去海外工作，這期間除了面試等等事情外，也深刻感受到簽證帶給人的焦慮，因此決定寫下這篇記錄，讓也要準備簽證申請的人可以對簽證申請過程中會發生的事有更全面的預期。</p><p><strong>本篇文章只是經驗分享，不代表任何法律建議，簽證申請是法律專業，有法律相關疑問請諮詢律師或專業代辦。</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TxMGSOinn8AZ382cOc0Cqg.jpeg" /><figcaption>發個無關的 [ALEXANDROS] 鎮樓</figcaption></figure><h4>申請背景概述</h4><p>簽證類型：Skilled Worker Visa<br>職業：Software Engineer<br>雜項：這個簽證必須要有雇主才可以申請，且雇主必須要有 Sponsorship。我的公司本身就有合法的 Sponsorship，因此不需要再從頭申請 Sponsorship。另外公司有加購 Priority Service，因此簽證核發較快。<br>時間軸：<br>08/21 公司委託移民律師開始進行我的簽證申請，並於之後同步申請 DCoS<br>09/04 移民律師透過電子郵件聯絡我，並依據我的資料整理出簽證申請概要<br>09/12 DCoS 核發<br>09/24 律師提交簽證申請表給 UKVI<br>09/30 去<a href="https://maps.app.goo.gl/5cEkra2EiHQJuZ257">台北 VFS</a> 進行生物辨識並提交護照，同日下午取回護照<br>10/01 簽證下簽，同日進行 eVISA 申請<br>10/02 eVISA 申請通過，流程結束</p><p>目前英國已經全面採用 eVISA，所以不會再有護照貼紙囉。</p><p>接下來會說明<strong>書面申請</strong>、<strong>生物辨識</strong>還有<strong>心得分享</strong>。</p><h4>書面申請所需資料和流程</h4><ol><li>護照：不需要特別說明的必備物品，填寫線上申請表時需要上傳<strong>含有個人資訊頁面</strong>的掃描副本。</li><li>身分證：正反面影本。</li><li>存款證明：可以由公司擔保從而免除該文件的提交，我的狀況就是由公司擔保。否則需要檢附財力證明，證明你可以至少在第一個月來英國時有足以維生的資金。（我申請時是需要證明戶頭有 £1,270 的餘額至少 28 天，但法規隨時調整，請以 UKVI 網站資料為主）</li><li>學位證明或畢業證書：必須要是英文版本，我是學士畢業，因此只需要學位證明即可，碩士以上有其他要求。</li><li>成績單：必須要是英文版本。</li><li>CoS：這是公司要準備的文件，申請者只需要 CoS Number 用提書面申請時填表。</li><li>IELTS 成績單：<strong>兩年內</strong>雅思成績單，請注意必須報考 UKVI 版本，否則不予採用。（British Council 和 IDP 都有提供該類型考試）</li><li>履歷：個人求職履歷即可</li><li>職位相關證明：聘書、JD、任何證明你可以勝任該職位的佐證資料</li></ol><p>有了以上資料就可以進行申請了，線上申請表問題繁多，請依照實際狀況誠實作答，填完線上申請表會被導覽至 VFS 頁面，選時間預約現場生物辨識。關於詳細填表流程，可以去 Youtube 用關鍵字查詢，很多影片都有詳細過程。</p><h4>可以提早準備的文件</h4><ol><li>英文學位證明和成績單：學校行政效率通常比較難以預期，所以可以提早準備。</li><li>十年內出入境紀錄：線上申請表會有要求你填寫，但如果沒有記錄旅行的習慣會很頭大，而且現在護照出入境不一定會蓋章了，所以如果有需求可以去移民署網站查閱，會有詳細出入境日期。</li><li>交通違規記錄：監理所網站可以查，我就忘了自己很久以前有一張罰單。交通罰單這種記錄並不會影響簽證申請，建議誠實申報。</li></ol><p>特別注意，上面這些文件都可以在提交線上書面申請表時，透過網站一併上傳電子檔。否則必須在生物辨識階段，在 VFS 現場掃描提交，必須負擔額外費用。</p><h4>生物辨識資料和流程</h4><p>需要攜帶的物品：</p><ol><li>護照：必備，會需要存放在 VFS 一段時間。</li><li>Document Checklist：該文件上面會有你在生物辨識當日需要攜帶的物品，因人而異。（我自己是只需要攜帶護照）</li><li>生物辨識預約通知單：上面的 QR Code 裡是你的預約資訊，建議整張通知單彩印，當日攜帶至 VFS。</li><li>護照個人資訊頁影本：VFS 的警衛會跟你要，我原本就有準備，沒帶應該可以當場影印，但建議自己準備避免節外生枝。</li><li>第二證件：身分證、健保卡等等都可，<strong>這不是生物辨識的所需物品，但是 VFS 所在的大樓必須要換證才能進入。</strong></li></ol><p>Document Checklist 上面會說需要「Colour copies of all pages of the passport or travel document.」，也就是整本護照的彩色副本。這裡我自己的經驗是，你只需要攜帶護照正本前去參加生物辨識就可以滿足條件，因為 VFS 會把你的護照留下來進行整本掃描。</p><p>生物辨識當天，<strong>提早十五分鐘到 VFS</strong>，VFS 地址可以透過 Google Map 輕易找到，或是官網也有提供，一樓有一蘭拉麵作為地標：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*TzBB8oxHHCvie6kX.jpeg" /><figcaption>image credit: <a href="https://www.dcard.tw/f/studyabroad/p/256027855">https://www.dcard.tw/f/studyabroad/p/256027855</a></figcaption></figure><p>搭電梯至七樓就可以到 VFS</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O2P7-4IzQ2zfmDeCF6e3Ug.jpeg" /></figure><p>抵達之後，理論上在外面等就好，時間到警衛會出來叫你，但是很擔心也可以進去跟警衛知會，只是會被請出去在外面等。</p><p><strong>VFS 裡面不能拍照，且手機必須靜音</strong>。進去之後警衛會跟你要預約資料還有護照資料頁影本，接下來會做安全檢查。沒問題之後就會給你號碼單，去等待區等待叫號。</p><p>被叫號之後就會進去小房間，負責的工作人員會跟你抬槓，問你一些你辦簽證的目的，然後壓指紋、拍大頭貼、看監視器，有問題都可以問他。</p><p>結束之後，會到外面櫃檯處理剩餘事項，櫃檯人員會交代什麼時候可以領護照，依照時間來取護照即可，最後還會送你一張 LEBRA 網卡。整個生物辨識之旅大概半小時搞定，接著就可以回家等簽證結果的 EMAIL。護照如果可以取件了也是用 EMAIL 通知。</p><h4>心得和一些流程之外的事情</h4><p>現在回想這個簽證申請流程感覺好像一切順利，但在當下其實很焦慮，我並不是一個喜歡等待的人，簽證申請又涉及多方合作，所以有時中間就會有很多等待期，滋味並不好受。<br>申請提交時儘管自己資料都沒問題，也沒有不良紀錄，但又會擔心會不會被拒簽，但好在英國簽證真的跟其他人經驗分想一樣，記錄和資料沒有問題就不會有意外。</p><p>另外還有一個插曲，在填表時，會有一個問題詢問預計抵達英國的時間，如果填太早是會被額外收費的，我就是因為想要提早過去找房子，所以預計日期填了比我工作開始日早 28 天，所以需要多繳半年的 IHS，£517，約莫 20000 NTD。（這些額外費用在填表時會有提示。）但我自己覺得工作開始之前可以安頓下來很重要，所以還是含淚繳錢。</p><p>總之焦慮的簽證之旅總算告一個段落，耗時一個半月，比面試還煎熬。如果有問題歡迎在下面留言，如果不是法律相關我都會回覆，祝大家英簽順利。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d40903be56d5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[「源」亦是「緣」]]></title>
            <link>https://medium.com/@frankvicky/%E6%BA%90-%E4%BA%A6%E6%98%AF-%E7%B7%A3-90295e0f4947?source=rss-abe1cb5099ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/90295e0f4947</guid>
            <category><![CDATA[jobs]]></category>
            <category><![CDATA[open-source]]></category>
            <dc:creator><![CDATA[TengYao Chi]]></dc:creator>
            <pubDate>Thu, 14 Aug 2025 08:23:36 GMT</pubDate>
            <atom:updated>2025-08-15T12:52:12.568Z</atom:updated>
            <content:encoded><![CDATA[<h4>在開源貢獻的過程中建立人脈和影響力，這些都是會在未來回饋給你的。</h4><h4>前言</h4><p>繼今年四月獲得 Kafka 社群提名成為 Committer 之後，我就開始漫長的找工作之旅，最後在各種幫助之下，成功獲得加入某外商的機會。<br>如果你對於開源貢獻有興趣，可以參考我這篇<a href="https://medium.com/@frankvicky/from-history-to-apache-892f00a65474">心得</a>。這篇主題會圍繞在開源對於找工作到底有沒有幫助。</p><h4>楔子</h4><p>不知道大家對於取得開源頭銜之後的想像是什麼呢？<br>我想大部分人應該還是會想要透過這份經歷去槓桿未來的工作，畢竟犧牲下班和休息時間，在開源中勤勤懇懇，即使是熱愛也一定希望這份熱愛可以被看見且有回報。如果你正是這麼想的，那這篇文章可以給你一些參考。</p><h4>重點先說</h4><p>開源到底可不可以在職涯上幫到你？絕對可以。<br>但不是說搞開源 == 職涯起飛，這兩者並不是直接的因果關係，做開源並取得職缺在台灣不是一條尋常路，累積和機緣缺一不可。如果你真的想要快速取得工作，我建議現在就馬上把 LeetCode 打開、系統設計開練，因為開源並不是找工作的必須項，而是一種變量。</p><p>接下來我會跟你分享我的過程。</p><h4>我是如何取得外商工作的</h4><p>我是透過內推取得某外商的工作機會。至於為什麼人家願意幫我內推？這是因為在 2024 年 4月開始貢獻 Kafka 之後，我在過程中和多位他們的工程師逐漸熟稔，並且也在他們的群體中建立了不錯的聲望。但是這也不是說人家內推，我就拿到 offer，中間也是經歷了不少波折。</p><p>在今年 4 月我取得頭銜後，中間陸續在修履歷、找幾間公司面試暖身，到了 6 月就嘗試透過他們的一位工程師幫我內推一個 focus OSS 的職缺，這位工程師跟我非常熟稔，且我們多次在 PR 上合作，推送了很多重要的修復。他馬上就答應我且願意幫我大力推薦，並且後來也確認我想投的職缺剛好就是他的 team，且 Hiring Manager 也是他的 Team Manager，到這裡聽起來都很美好對吧？然而現實是失敗了，原因不是因為履歷或是技術，而是我沒有當地的工作簽證，換言之，我沒有身份。</p><p>其實這樣的結果並不意外，疫情帶來了大遠端時帶，卻也加速了全球化時代的終結，大部分科技巨頭都會叫你回去辦公室上班，相比之下只要到境內遠端已經很寬鬆了。</p><p>到這邊為止，其實我當下已經覺得沒有希望去 某外商 工作了，所以我轉而專注在投遞亞洲的職缺，不過事情又有了轉機。<br>7 月時，我又在 LinkedIn 上看到另一位我認識的工程師在轉貼他們 team 的職缺資訊，雖然看起來跟我之前投遞失敗的職缺一樣，但我本著問問又不吃虧的心態，厚著臉皮問人家可不可以內推，對方也是很爽快地同意了，並且說願意幫我在公司內尋找各種可能性來贊助我簽證，我非常感謝他願意幫忙。等待的期間其實也不短，但中間對方都會定期跟我同步情況。最終在一段時間的等待之後，我收到了 HR 的來信，請我提供國籍等等資訊來評估各種可能的加入途徑。後來的事情就開始順利了，在找到可行方案之後，我們就開始面試，Behavior、Coding、System Design 等等一樣沒少。對，你沒看錯。</p><p><strong>要面試且會考 LeetCode</strong></p><p><strong>要面試且會考 LeetCode</strong></p><p><strong>要面試且會考 LeetCode</strong></p><p>很重要所以說三次。</p><p>我還是需要面試，不要覺得做開源就可以不用準備 LeetCode 和系統設計，該來的就是會來，<strong>如果那刻真的來臨，請做好萬全準備然後抓住那個該死的機會</strong>。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*v6o_8XAwmxbc2rIY.jpg" /><figcaption>ShowMaker 這句此生僅有的機會一直在我心中</figcaption></figure><p>你可能想問有人不用面試的嗎？我只認識一個光頭是這樣，而且還拒絕人家。但說到底，還是要說回影響力，如果能做出巨大的影響力，不用面試當然是可以的，但我還沒到那個 level。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*XAIOL5cqtjwzAykA.png" /><figcaption>頭銜（影響力）和人脈缺一不可</figcaption></figure><h4>在台灣或亞洲求職</h4><p>有些人可能覺得要出國實在太遙遠，想知道在台灣的求職情況，這邊統一跟大家分享。</p><p>我自己體感，以 Kafka 這種熱門專案的頭銜來說，面試機會變得相當容易取得，請注意是面試機會不是直接叫你去上班。你還是需要通過 leetcode、面談等等關卡。開源經歷的確會加分，但效果有限，畢竟台灣幾乎沒有把特定開源專案當成產品在經營的公司，大部分還是以 Product 為主，如果想要最大化開源經歷的優勢，建議可以瞄準 Infra 相關的職缺，但這類職缺相對較少。</p><p>所以結論，即使有開源頭銜，面試還是要好好準備。我也曾經透過友人幫助投遞一些日本的大型公司，但最終也是因為面試表現沒有達到預期所以沒有被錄取。</p><h4>總結 — 到底該用什麼心態去做開源？</h4><ul><li>以影響力為導向，而不是結果導向</li><li>在過程中盡量的去認識其他工程師、拓展人脈，緣分很重要。</li><li>業力會引爆、功德會迴向，所以在過程中盡力而為。</li></ul><p>有了這三點在心中，就可以更平和、更耐心看待這段旅程。</p><p>你或許會說，感覺開源貢獻沒什麼用啊，還是要面試、還是要考演算法，你好像在繞遠路到終點。但其實於我來說，開源更像是一個路徑明確的遠路，在這條路上的收穫都讓我更靠近目標。如果不是貢獻 Kafka，我不會認識那些願意幫助我的工程師，如果不是在合作過程中逐漸有默契，他們也不會願意幫我內推，更不會願意在我遇到困難時願意花額外心力幫我找解決辦法。會有這些人幫助我，是因為我在貢獻的過程中打開了這些可能性，每次交付、每次談話都是建立信任和連結的過程，所以說開源其實就是開緣，至於緣是好是壞，都取決於我們如何耕耘。</p><p>最後，祝各位還在開源上面努力的同伴都能如願以償。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=90295e0f4947" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[From History to Apache]]></title>
            <link>https://medium.com/@frankvicky/from-history-to-apache-892f00a65474?source=rss-abe1cb5099ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/892f00a65474</guid>
            <category><![CDATA[taiwan]]></category>
            <category><![CDATA[bootcamp-graduate]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[kafka]]></category>
            <dc:creator><![CDATA[TengYao Chi]]></dc:creator>
            <pubDate>Sat, 05 Apr 2025 01:48:09 GMT</pubDate>
            <atom:updated>2025-04-11T00:18:01.264Z</atom:updated>
            <content:encoded><![CDATA[<h4>從歷史系到阿帕契﹣轉職仔的開源之路</h4><h4>回歸</h4><p>好久沒寫技術部落格了，沒想到上一篇已經是一年前了，這似乎是大多數技術部落格的宿命。技術日新月異令人振奮，但作為軟體工程師，感受到的更多是焦慮和迷失，於是要學的基礎忘記了，承諾的部落格堆滿灰塵，這個職業走在科技的前沿，卻也更容易在刺激中迷失。為了壓抑焦慮，我們不斷在網路裡浮沉，試圖尋找憂慮的解答。</p><p>我很幸運，在焦慮的海浪裡看到了岸，於是我沒有在軟體工程裡不斷革新的浪潮裡被淹沒，也找到了努力的方向，如今也達成了一個小小的里程碑。所以接下來我會寫一些關於這一年沒寫部落格之後我在做什麼，我是如何透過開源社團「<a href="https://www.facebook.com/opensource4you">源來適你</a>」投入到開源貢獻，進而取得開源技術頭銜。</p><p>如果你也正因為職涯的不確定性而感到無比焦慮，希望這篇文章可以給你一點啟發，發展職涯的方式不是只有刷題，軟體工程師還有其他條路可以增進自己並讓其他人看見。</p><p>文章主要分為 「<strong>開源貢獻</strong>」和「<strong>心路歷程</strong>」兩部份，前者主要專注在參與開源貢獻的好處，後者主要是開始參與貢獻後一路以來的感受。</p><h3>開源貢獻</h3><p>這個章節說明我這一年透過「<a href="https://www.facebook.com/opensource4you">源來適你</a>」參與 <a href="https://github.com/apache/kafka">Apache Kafka</a> 的貢獻所得到的改變和益處，還有給想參加開源貢獻的人一點建議。</p><h4>關於作者</h4><p>我是紀登耀（<a href="https://github.com/frankvicky">GitHub</a> / <a href="https://www.linkedin.com/in/tingi%C4%81u-k%C3%AC/">LinkedIn</a>），畢業於臺北大學歷史學系，是俗稱的轉職仔，在兵役之後就去了資策會開辦的 Java 培訓班，之後就開始當一個 Web 仔騙吃騙喝。</p><p>在職場載浮載沉幾年後，2024 / 04 / 20 發出了在 Apache Kafka 的<a href="https://github.com/apache/kafka/pull/15766">第一支 PR</a>，從此開啟了自己的開源貢獻之旅，在社群的這一路上從懵懂到熟悉，從道歉到下跪，我能清晰地感受到自己一點點的在進步。而經過這一年的貢獻，我在 2025 / 04 / 04 受邀成為 Apache Kafka Committer 。我在這段期間總共提交了 <strong>171</strong> 個 commits，這其中也包含了一些大型的貢獻，比如 <a href="https://github.com/apache/kafka/pull/17373">Log4j2 的遷移</a>等等，而這一切都不是一年前的我可以想像的。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*SHwXkFoz8914GsNA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xod8mdpUW66z2ixhxcK5TA.png" /><figcaption>Apache Committer List</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/682/1*1qWqDISKHrEUhPzW6LmGsQ.png" /><figcaption>Apache Kafka GitHub Contributions Statistic</figcaption></figure><h4>為什麼開始貢獻開源</h4><p>其實在一開始從培訓班結業時並找到工作後，我並不覺得寫程式是一件很困難的事情，甚至覺得工作算是很輕鬆，完全處於達克效應的愚昧之峰，但是因為一些契機開始參與技術社群之後，才認知道軟體工程是一門高深的技藝和學問，且這些年經歷了 COVID-19、區塊鏈爆發和 AI 的興起，這些大事件的為軟體產業帶來了一波一波的革新浪潮，無一不讓我感到很焦慮，我曾經嘗試隨著海浪起舞，不斷追逐新技術，但我發現海浪終究會平息，而且下一波海浪到來並不一定是往同一個方向，為此我開始找尋可以真正讓我在這些海浪中存活下來的方法。</p><p>也就是在這時，我接觸到了「<a href="https://www.facebook.com/opensource4you">源來適你</a>」這個社團，並且發現相較於其他開源社團著重推廣，這個社團是以實作、貢獻為主，而且還有對應專案的專家作為 Mentor 指導社團成員，最重要的是這些貢獻和知識都是帶得走的，所有的努力也都會作為 commit history 留下，於是在參與過幾次線上會議後，我下定決心要開始投入。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*d3N1h8uy5FmCUxa5" /><figcaption><a href="https://www.facebook.com/share/p/1YSYgED8yQ/">我第一次參與線上會議的主題是關於 offsetOfMaxTimestamp 的 bug</a></figcaption></figure><h4>為什麼選擇 Apache Kafka</h4><p>時也運也，Kafka 本身就是 Event Streaming 裡非常熱門且廣泛印用的領導專案，彼時的我正在以 JD 為導向進行學習，而 Kafka 是一個出鏡率非常高的單字，所以我也在 Udemy 上修習 Kafka 的課程。</p><p>恰逢 <a href="https://www.linkedin.com/in/chia7712">蔡嘉平</a> 和 <a href="https://www.linkedin.com/in/showuon/">Luke Chen</a> 兩位 Kafka PMC 作為 Mentor 在社團的<a href="https://www.facebook.com/share/p/1C7Z6tPBuh/">香菜饅頭營</a>裡開辦了 Kafka 小隊，這對於我來說這是千載難逢的機會，除了我比較熟悉 Java 和 Kafka 本身很熱門之外，我也想要從書本以外的地方更加深入地了解分散式系統，所以就順勢投入了 Kafka 的貢獻。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7fk7rswB04oaArB-GpU6Qw.png" /><figcaption>Kafka 有接近 30 K 的星星，也長年霸榜在 Apache Popular Repositories</figcaption></figure><h4>參與開源帶來的好處</h4><p>這裡會說明參與開源貢獻給我帶來什麼好處。</p><ul><li><strong>提昇自己對於閱讀程式碼的能力</strong></li></ul><p>Kafka 有著 140 萬行程式碼，單論 Java 就有 120 萬行，在這麼大的 Code Base 裡 trace code 並不容易。在初期還是菜鳥的時候，光是找到關鍵程式碼就必須花費大量時間，這也迫使我花費大量時間去閱讀程式碼，為了更有效率的 trace code，也特別再去研究 IDE 有什麼更進階的功能可以幫助我，而這些訓練都不只是侷限在這個專案裡，Kafka 給了我更大、更有挑戰性的環境讓我鍛鍊自己的技巧。</p><ul><li><strong>了解大型系統的設計哲學</strong></li></ul><p>以 Backward Compatibility 為例，Kafka 是一個超過十年的大型系統，積累了龐大的使用者群體，在不斷加入新功能的同時，也必須考慮舊版本的使用者，因此每個 feature 都必須在極大程度上保證兼容性。</p><p>在 Kafka 4.0 版本以前，Kafka 可以跟任意舊版本的溝通而不會出現故障，直到 4.0 才因為架構大規模改變才捨棄 2.1 以前的版本。因此，任何一個改動最前提的要求就是考量到兼容性，這樣對於兼容性的要求是在日常開發經驗中很難遇到的。</p><ul><li><strong>練習技術提案</strong></li></ul><p>Apache 專案通常都有 Improvement Proposal 的制度，以 Kafka 來說，因為對於兼容性的高度要求，只要涉及到公共 API 的改變，都必須撰寫 KIP (Kafka Improvement Proposal) ，這些提案根據專案不同都有自己要遵守的格式，撰寫完成之後，還必須把 KIP 丟到 <a href="https://lists.apache.org/list.html?dev@kafka.apache.org">Dev Mail Channel</a> 進行公開討論，而作為提案者，會受到來自各方的詢問甚至挑戰，為了讓提案通過必須一一解決這些問題，而後再發起投票來爭取支持，這些都通過之後才能把提案付諸實現。這是個非常寶貴的經驗，除了可以練習用撰寫技術提案，我也必須替自己的提案辯護，而這些都是使用英文進行，除了鍛鍊思維也對英文寫作能力有很大的幫助。</p><ul><li><strong>走出舒適圈，接觸自己不熟悉的技術</strong></li></ul><p>以往在工作中，不知道大家怎麼使用 build tool？以我來說，Maven 和 Gradle 都有使用經驗，但僅僅只限於非常簡單的定義依賴，但是在 Kafka 這樣發展很久的大型專案來說，基礎的使用是沒辦法滿足需求的。</p><p>Kafka 是使用 Gradle 作為 build tool，為了解決對於建構順序和專案需求，Kafka 使用了很多 Gradle 的進階功能，有些用法甚至可以稱之為 hack，如果不是參與了開源，我這輩子大概很少有機會用到 implmentation 以外的關鍵字。</p><p>再比如 Python，在參與 Kafka 之前，我完全沒有使用過 Python，但是在 Kafka 之中，System Test 相關的程式碼是用 Python 編寫的，我在貢獻的過程中，也「被迫」要去了解 Python 如何運作，這樣才能解決相關的 issue。雖然我依然不算熟悉 Python，但透過一個現實中的大型專案作為學習入門是非常有趣的，這是很多線上課程都沒有的真實用例。</p><ul><li><strong>免費向大公司的資深工程師學習</strong></li></ul><p>這是一個顯而易見的好處，在這種大型專案的 Committer 或 PMC，通常都已經是在各自公司裡的 Staff Engineer，更有甚者有些人甚至本身就是多個專案的 PMC，堪稱開源神獸，而在開源的世界裡，只要提交自己的 PR，就有機會被這些大神 Review，而他們也能夠針對我們 PR 的不足之處提出洞見，這樣的機會是非常難得的，尤其是他們對於專案都非常熟悉，對於程式碼的品質也有很高的要求，因此在 Review 過程中常常會有出乎意料的收穫，而這些已經在各自領域裡十分傑出的工程師，他們看待問題的思維模式非常值得我們好好學習的。</p><ul><li><strong>自己上門的工作機會</strong></li></ul><p>即使還沒有取得頭銜，在不斷貢獻的過程中就會有一些意想不到的機會自己找上門。因為開源的貢獻是完全公開的，所有人都可以透過 GitHub 或是其他公開管道知道你的貢獻，也因此很多公司的招募人員其實都會透過這些這些公開資訊去尋找潛在的候選人。以我自己的經驗來說，在成為 Committer 之前就有陸續收到 5 ~ 10 個左右的面試邀約，而這些機會是在台灣求職網站基本上是沒辦法接觸到的。</p><ul><li><strong>自我實現</strong></li></ul><p>並不是所有軟體工程師都是在開發大眾會使用的產品，且很可能無法決定技術走向，只能遵循公司既定技術路線，這些因素都很容易讓工程師無法連結自己本身的價值而產生異化感。但是開源不會，不管是選擇大型熱門或是新興的的開源專案貢獻，都可以透過各種方式自由地針對專案裡的技術走向發揮自己的影響力，這樣不僅可以證明自己的實力，也可以在可以明顯地把產出和價值連結，一舉數得。</p><h4>會遇到的困難 ＆為什麼該加入社團</h4><p>上一個章節說了很多參與開源的好處，但過程中並不是只有好處，也會遇到種種困難，這裡說明加入「<a href="https://www.facebook.com/opensource4you">源來適你</a>」可以給你在開源路上帶來什麼幫助，讓你的開源之路更加平坦順暢。</p><ul><li><strong>初期學習曲線較陡</strong></li></ul><p>作為初入開源的小白，第一個要面對的困難就是龐大的 Code Base 但不知道要從哪裡開始，除非是新興的專案，否則這裡是最容易放棄的地方，但是如果能堅持過去，接下來就會順暢很多。如果是社團中有在 Own 的專案，就會有 Mentor 餵食ㄧ些 Good First Issue，可以很好地在前期幫助新手了解開發流程、建立信心。</p><ul><li><strong>沒人 Review</strong></li></ul><p>新手另外一個容易放棄的點就是興致沖沖提交了第一個 PR 但是放三個月了都沒人來看。關於這點可能的原因很多，可能是大家單純很忙、你的 ID 太陌生或 PR 格式不對，人家連看都懶。在社團中，Mentor 通常是有專案寫入權限的人，因此可以保底有人幫你 Review，持續地回饋才能維持住較高的動力。</p><ul><li><strong>修 Comments 修到中離</strong></li></ul><p>我看過很多新手，在社群裡提交了 PR，但是可能是因為和專案不熟，導致程式碼品質低落，因此 Reviewer 每次 Review 都會留下一狗票的 Comments，很多新手來回幾次之後就會放棄。</p><p>這裡更加體現 Mentor 的作用，社團裡每週都有週會，在 Issue 上遇到問題都可以詢問，也可以通過 Slack 頻道進行非同步溝通，其他夥伴也會回應他們知道的知識，幫助你可以更容易解決 Issue 並提交高品質的 PR。</p><ul><li><strong>找不到 Issue 解</strong></li></ul><p>這通常是有一定經驗的貢獻者會遇到的問題，一般的 issue 已經沒什麼問題，但是孤狼一隻，常常會拔劍四顧心茫然，空有滿腔熱血但沒有 issue 可以做，久了很容易熱情消失。</p><p>社團中除了 Mentor 會不定時丟出 Good First Issue，偶爾也會有其他同儕分享較大型、可拆分的題目，因此不必擔心沒有事做，只怕自己手速太慢。</p><h4>開始貢獻之後應有的預期</h4><p>如果你看到這裡已經很心動，相信你多少對於開源貢獻有點躍躍欲試了，可以找找看<a href="https://github.com/opensource4you/readme?tab=readme-ov-file#%E7%9B%AE%E5%89%8D%E6%9C%89-mentor-%E5%B8%B6%E7%9A%84%E5%B0%88%E6%A1%88">社團專案列表</a>有沒有心儀的專案。這邊以個人經驗來說說該以什麼心態、預期來投入。</p><ul><li><strong>開源貢獻是需要長期且耐心地投入</strong></li></ul><p>不管是來練功還是以頭銜為目標，都要認知到開源貢獻至少必須以半年為單位的持續投入才可能建立個人形象，有了好的形象才可以形成正循環。</p><p>我覺得開源的世界就像是一個真實的 MMORPG，每個人的行為最中都會為自己建立一個品牌，而這個品牌的好壞會很大影響你在一個專案的推進進度的速度，因此為自己建立好名聲是非常重要的。名聲的建立可以從各方面著手，比如不要只是開 PR，而是好好撰寫 PR 描述，表達思路，或是提前在 PR 裡面標注需要討論的地方，提前留下問題，這樣都可以為讓其他人對你留下好印象。</p><ul><li><strong>初期投入時不要太挑 Issue</strong></li></ul><p>來開源貢獻其實就是希望能夠在技術上有所突破，所以在初期不要太挑 Issue 做，多方涉略，一個大專案中會有許多模組，初期多方嘗試，會對各個模組比較有基礎的認識，等變成熟手之後再考慮要專攻哪個部分。</p><ul><li><strong>做，做就對了</strong></li></ul><p>如果你還在因為各種因素或未知而遲遲沒有行動，我的建議是不要在躊躇，因為很多顧慮是會在過程中迎刃而解的，就算最後發現開源貢獻不適合你，也不會有任何損失，反而是在猶豫中浪費的時間是最可惜的。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/600/0*FegQUTkoIxfxOOxP.JPG" /><figcaption>所有的問題都會在頭洗下去之後得到答案</figcaption></figure><h4>總結</h4><p>最初的我其實只是抱持著學習的心態來參與社團，但隨著投入的越深越能感覺到自己的成長，而獲得頭銜對我來說更像是社群對我貢獻的肯定。雖然現在有了頭銜，但我完全不覺得自己是一個 Kafka 專家，反而更覺得由於多了頭銜，自己必須要更努力去了解這個系統才能不辜負這個榮譽。</p><p>非常感謝「<a href="https://www.facebook.com/opensource4you">源來適你</a>」提供了一個這麼好的環境讓我可以用最低的阻力參與世界級專案的開發，五年前我從資策會離開時連 Camal case 和 Snake Case 都還分不太清楚，更想像不到的會有今天。</p><p>我也誠摯地邀請所有渴望證明自己或是因為焦慮而迷失的工程師來參與到開源之中，這條路雖然在台灣不是顯學，但絕對值得嘗試。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/584/1*cuDyUTd2Ss3NinY3QzalfA.png" /><figcaption>資策會畢業專題一隅，snake case 和 camal case 混用的吐血程式碼</figcaption></figure><p>社團目前除了寶寶照顧級的開源貢獻指導之外，也舉辦各種線上、線下活動，除了是讀書會、科技開講等固定節目之外，還有實體工作坊等等，即使你不參與開源貢獻也可以從中學到很多。另外，社團目前也透過 OCF（開放文化基金會） 開設了<a href="https://ocf.neticrm.tw/civicrm/contribute/transact?reset=1&amp;id=84">捐款帳戶</a>，如果你覺得社團很讚，行有餘力也請不吝支持。</p><p>另外社團已經培育了多位 Committer，較為近期的就有： <a href="https://www.facebook.com/share/p/1PRyrj8x7Q/">哲佑</a>、<a href="https://www.facebook.com/share/p/1AUcQLtXtj/">Nary</a> 等社團的明日之星，而且社團也由於驚人的能量而被國內和國外的媒體採訪：</p><ul><li><a href="https://slat.org.tw/node/205?fbclid=IwY2xjawIsXO1leHRuA2FlbQIxMAABHT4jKg02mk6DzzeeobSQPUzG7a31n26YjXL_i6RmkR_EB_Yqbo70FbGo5w_aem_Vw-2uHZxyiOQ65BG8moIxw">戰鬥力最強的取暖小圈圈：專訪源來適你社群蔡嘉平</a> — 軟體自由電子報</li><li><a href="https://bigdata.2minutestreaming.com/p/kafka-community-spotlight-taiwan?fbclid=IwY2xjawIsXM1leHRuA2FlbQIxMAABHY8a13JwK9wfnvk9XG_1SesFCROHFIWIY4mYdY20ES6OaTycjsHpjQDDZQ_aem_M2E-I46m1JuvkaChjtzhwA">kafka community spotlight: TAIWAN</a> — 2minutestreaming</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0lI_fj62O1KYZjU9yc0_Cg.png" /><figcaption>社團吉祥物，源冥寶寶，勾追，歡迎認養</figcaption></figure><h3>心路歷程</h3><p>這篇會更像是這一段旅程的一個總結，所以沒興趣可以跳過。</p><h4>楔子</h4><p>2024 年初，彼時我還在和自己私人讀書會群組裡的夥伴每天刷題，討論更面試常被提及的演算法，除此之外，我還在密集準備 AWS SAA 的考試，企圖在這些大家習以為常的賽道裡實現彎道超車。每天的任務除了刷題以外，還會過濾 JD，根據 JD 內容來找到自己不足之處，並且抽空閱讀分散式系統的書籍，這是我和夥伴每天的日常。雖然日子充實，但接連幾次面試的失利卻開始讓我懷疑自己，LeetCode 難免會碰到自己不會的問題、熟讀知識卻被說沒有實務經驗、證照加分有限，雖然有拿到幾個 Offer 卻始終不是心儀的機會，這讓我開始思考，到底有其他條路來拓展職涯，可以把書上的知識跟實際的產出做結合來加強自己？</p><p>此時，在網路衝浪時看到了一則<a href="https://www.facebook.com/share/p/1A3MkT5PvB/">活動訊息</a>，成為我開始投入開源的起點。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*1DSxYRxzAaG_gY8D" /><figcaption>當時我正在 Udemy 上學習一門 Kafka 課程，所以馬上就被吸引到啦</figcaption></figure><h4>當頭棒喝</h4><p>我有想過之後可能會遇到挫折，但沒想到這麼快。我的麻煩在<a href="https://github.com/apache/kafka/pull/15766">第二支 PR</a> 就來了，這個 issue 還是我在「微醺饅頭」的活動現場領到的。當時我以為是簡單的 Scala to Java 改寫，沒想到由於缺乏關於專案上下文的知識，我拖了整整 17 天才合併，期間喜提 124 的 comments。</p><p>當時除了社團 mentor ＠<a href="https://www.linkedin.com/in/chia7712">蔡嘉平</a> 以外還有一個國外大公司的 Staff Engineer 在幫忙審閱，每次提交一次 commit 就會收到 10 個左右的 comments，真的讓我很焦慮也很灰心，直接開始懷疑人生。</p><p>最終合併之後，嘉平請我思考要不要繼續貢獻。當時是我最接近放棄的一次，我也確實覺得很丟臉，但是我想了一想，我要因為自己可悲的自尊心放棄這種機會嗎？丟臉是丟不死人的，但抱大腿的機會是真的很少，所以我決定繼續厚臉皮的留下來參與貢獻。</p><h4>擺正心態</h4><p>有了前面的教訓之後，我真正的認識到這是一個長遠的學習過程，我開始投入更多的時間在專案上，除了貢獻程式碼之外，更多地去了解專案本身，每次社團裡的週會也都積極的參加，這很像是大學時期的 office hour，我都盡量把問題整理好，一次提問。</p><p>沒有 Issue 做的時候我就會閱讀相關書籍，補充知識，比如 《Apache Kafka: The Definitive Guide》和 《Designing Data-Intensive Application》都是在貢獻初期讀完的。</p><p>順帶一提，「<a href="https://www.facebook.com/opensource4you">源來適你</a>」除了各大開源專案的社團之外還有其他活動，比如讀書會，那時透過讀書會的指定書本也是 DDIA，透過參與讀書會不僅加快了學習進度，也更了解分散式系統，更是在貢獻 Kafka 的過程中跟書中的知識互相印證，形成了正向循環。</p><h4>步入正軌</h4><p>開始貢獻兩三個月之後，跟相比初期，我處理一個 issue 的時間有顯著的下降，能夠更快地 trace code，找到問題的核心。也就是在這個時候，我開始覺得「取得頭銜」不再是一個不切實際的目標。考量到如果佛系參與的話，會花費很久的時間才能達成目標，為了加速這個過程，我開始給自己訂 KPI，也就是每個月至少要被 merge 10 支 PR。</p><p>現在回顧，我很慶幸自己有給自己設定這樣的小目標，以當時來看，這個 KPI 是有點超出我的能力範圍的，我必須更專心的投入才能勉強達成，這逼迫我不能只做社團裡丟出來的題目，如果 JIRA 上有沒人接手的題目我也會去嘗試，更甚者會去注意程式碼有沒有還沒發現的問題。這種壓力幫我養成了上面這些習慣，我開始習慣下班到家就開始做貢獻，假日也可以維持較長時間的專注，根據不精準計算，平日可以至少投入 4 小時，假日則是 8 小時，這樣的投入大大加速了我了解 Kafka 的速度。</p><h4>開花結果</h4><p>從開始貢獻到現在，時間過得飛快，我也見證了很多社團和 Kafka 的大事，比如 SITCON 擺攤、嘉平一拳打出本不在規劃內的 3.9 版本、竣陽的明星三缺一、<a href="https://bigdata.2minutestreaming.com/p/kafka-community-spotlight-taiwan?r=4ra82d&amp;utm_campaign=post&amp;utm_medium=web&amp;showWelcomeOnShare=false">Stan 對於社團的採訪</a>、不斷拖延的 Kafka 4.0 和 黃金流沙饅頭營⋯⋯</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*XvZv3VhNTv0-NMzv" /><figcaption>2024 SITCON 夏令營社群攤位 @陽明交通大學</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*3tzQ9UCUHXov2fct.png" /><figcaption>為了 Stan 的採訪所以去餐廳拍的合照，這篇採訪也讓社團紅到國外</figcaption></figure><p>即使是在寫文章的當下還是覺得晃如隔世，微醺饅頭營彷彿只是昨天的事情，對我來說，社團的意義已經不只是大家聚在一起貢獻開源、討論技術而已，我真的透過開源結交了來自各個產業、天賦異稟的人，源亦是緣。</p><p>到現在成為 Committer 這件事對我來說還是有點不真實。雖然我已經達成了當初設定的目標，但我知道自己還差得很遠，接下來也會努力往 PMC 努力邁進，也很期待未來繼續跟大家、社群一起共事，希望今年大家都可以心想事成，成為 Committer，讓國外知道台灣也有很多強大的開源技術人才。</p><h4>鳴謝</h4><p>第一個一定要感謝 <a href="https://www.linkedin.com/in/chia7712">蔡嘉平</a> ，除了技術的指導之外，更常常要幫我闖的禍道歉下跪，如果不是嘉平我早就死在開源路邊了，真的除了感謝還是感謝，另外作為社團的發起人不僅要運營社團還要兼任 Kafka 組的饅頭，還是 Kafka 上最活躍的 Reviewer，這種執行力和活力真的是我從來沒見過的，真心 Respect。</p><p>另外也我也要很感謝同時期一起貢獻的夥伴，特別是<a href="https://github.com/m1a2st">竣陽</a>、<a href="https://github.com/FrankYang0529">博安</a>、<a href="https://github.com/brandboat">冠博</a>，我們四個一直是社團裡最活躍的四個貢獻者，這一年有舊人離開也有新人加入，但我們四個一直都在這裡，這其實也給了我強烈的競爭心和堅持下去的動力，沒有你們我一定沒辦法這麼有動力。</p><p>特別感謝<a href="https://github.com/m1a2st">竣陽</a>，從私人讀書會時期就是一直互相鼓勵的夥伴，同是轉職仔我們都堅持到了現在而不是甘於平庸，你真的是我見過最持久最大膽的人（各方面）。</p><p>額外感謝<a href="https://github.com/gongxuanzhang">生生</a>，在我早期進入社團時就是非常友善健談的人，也提供了很多學習資源給很菜的我。（前職業選手轉來寫 Code 真的太酷了）希望可以早日見面！也祝福你可以在職涯上可以武運昌隆！</p><p>最後特別感謝 <a href="https://www.linkedin.com/in/showuon/">Luke</a> 大大，雖然因為工作繁忙的原因，比較少在社團出沒，但三不五時就會丟一些 Issue 到頻道裡，根本是頻道裡的聖誕老人。這次能夠過五關斬六將也是多虧 Luke 提供了一個非常大的舞台，希望您生活順遂平安！</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=892f00a65474" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[隨筆] 在 Spring Boot 使用 MicroMeter 整合 CloudWatch 進行系統指標監測]]></title>
            <link>https://medium.com/@frankvicky/%E9%9A%A8%E7%AD%86-%E5%9C%A8-spring-boot-%E4%BD%BF%E7%94%A8-micrometer-%E6%95%B4%E5%90%88-cloudwatch-%E9%80%B2%E8%A1%8C%E7%B3%BB%E7%B5%B1%E6%8C%87%E6%A8%99%E7%9B%A3%E6%B8%AC-fe4be8b99799?source=rss-abe1cb5099ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/fe4be8b99799</guid>
            <category><![CDATA[micrometer]]></category>
            <category><![CDATA[observability]]></category>
            <category><![CDATA[spring-boot]]></category>
            <category><![CDATA[java]]></category>
            <category><![CDATA[aws-cloudwatch]]></category>
            <dc:creator><![CDATA[TengYao Chi]]></dc:creator>
            <pubDate>Tue, 05 Mar 2024 05:00:00 GMT</pubDate>
            <atom:updated>2024-03-05T05:02:22.756Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*em69IQZSXGkD1Bbn.png" /></figure><p>最近在公司收到了一個需求，需要去對產品系統進行監測、搜集資料，以此來確定未來開發方向，比如某些已經掛著 @Deprecated 的 API 到底還有沒有人用、有沒有哪些 API 是熱點等等。</p><p>由於近期把公司系統升級到 Spring Boot 3 之後，都在爬文看有哪些新特性，正好有看到 MicroMeter 和他新的 Observation API ，那就趁這次機會把他從頭搭建起來吧！</p><p>這篇文章主要會記錄該怎麼使用 MicroMeter 並和 CloudWatch 做串接，順便整理腦中的知識。</p><p>CloudWatch 是 AWS 一個非常好用的服務，除了 log 的紀錄、查閱和檢索之外，他還有另一個功能就是指標監測，我們可以在應用程式之中蒐集我們想要觀測的指標，再定期傳送到 CloudWatch ， CloudWatch 除了幫我們儲存這些指標外，更可以幫我們進行視覺化，讓我們可以快速觀察判斷應用程式的情況，從而做出緊急情況的應對或是後續開發的參考。</p><p>MicroMeter 是一種用於收集應用程式測量資料的 Java Library。它提供了一個統一的 API，可用於收集來自各種來源的測量資料，甚至包括 JVM。它也提供了各種常用的測量工具，例如： Counter、 Timer、 Gauge 等等。</p><p>Spring Boot 3 之後，對於 MicroMeter 的整合已經非常完善，接下來會介紹如和使用 MicroMeter 監測應用程式，並將指標傳送到 CloudWatch。</p><p>但是開始之前，要先簡單介紹一下 CloudWatch Metrics 的概念：</p><p><strong>Metric</strong></p><p>Metric 是 CloudWatch 中的核心概念，它表示應用程式在任何時間點的特定測量值。常見的指標是數值 ，例如：CPU 使用率、特定 API 的呼叫次數等等。</p><p><strong>Namespace</strong></p><p>Namespace 是 CloudWatch Metrics的容器。上傳到 CloudWatch 的每個 Data Point 都指定一個Namespace。Namespace 通常用於分類 Metric。</p><p><strong>Dimension</strong></p><p>Dimension 是構成 Metric 的一部分，它是一個 key-value pair。Dimension 可以讓我們更細粒度地監測應用程式，Dimension 更像是透過不同角度去查看一個 Metric，如果一個 Metric 可能擁有多種潛在意義，我們可以替 Metric 加上多個 Dimension，這樣可以方便我們在 CloudWatch 時透過不同的 Dimension 來監測應用程式。</p><p><strong>Data Point</strong></p><p>Data Point 是包含 Metric 和 Timestamp 的單一資料。Data Point是 CloudWatch 中的基本單元。</p><p>MicroMeter 本身並沒有一些複雜的概念，只有幾個概念要特別說明：</p><p><strong>Meter</strong></p><p>Meter 是所有 MicroMeter 測量工具的抽象，所有的測量工具類別都實作它。</p><p><strong>MeterRegistry</strong></p><p>Registry 是 Meter 的註冊中心，由 Registry 創建 Meter 並統一管理 Meter，如果去細看原始碼應該可以發現 Registry 使用 Map 去管理 Meter 。每個監測系統都有對應的 MeterRegistry 實現 ，對於 CloudWatch 而言，實作類別是 CloudWatchMeterRegistry。</p><p><strong>Tag</strong></p><p>一個 key-value pair，MicroMeter 的 Tag 對應 CloudWatch 的 Dimension，最多可以設置十個 Tag 在一個 Meter 上。</p><p>實際在 AWS 會是這樣呈現：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JjcDGuOSnEWlOjKTdZ5YHg.png" /></figure><p>接下來我們實際來使用 MicroMeter 然後把 metric 傳送到 CloudWatch 吧</p><p>首先先引入 Dependency，除了 spring-boot-starter，之外還需要下面的 Dependency：</p><pre>&lt;dependency&gt;<br>    &lt;groupId&gt;io.micrometer&lt;/groupId&gt;<br>    &lt;artifactId&gt;micrometer-core&lt;/artifactId&gt;<br>&lt;/dependency&gt;<br>&lt;dependency&gt;<br>    &lt;groupId&gt;io.micrometer&lt;/groupId&gt;<br>    &lt;artifactId&gt;micrometer-registry-cloudwatch2&lt;/artifactId&gt;<br>&lt;/dependency&gt;</pre><p>然後撰寫一個測試用的 HelloController</p><pre>@RestController<br>@RequestMapping(&quot;dummy&quot;)<br>public class HelloController {<br>    @GetMapping(&quot;hello&quot;)<br>    public String hello() {<br>        return &quot;Hello&quot;;<br>    }<br>}</pre><p>接著我們來設定 CloudWatch，請注意要把資料上傳到 CloudWatch 必須要在應用程式設定好 AccessKey和 SecretAccessKey，所以如果還沒有這對密鑰，請先去 AWS Console 申請然後設置好 AWS CLI。</p><pre>@Configuration<br>public class MetricConfiguration {<br><br>    // 建立 MeterRegistry 的 Bean 來幫助我們管理 Meter<br>    @Bean<br>    public MeterRegistry getMeterRegistry() {<br>        return new CloudWatchMeterRegistry(<br>                setupCloudWatchConfig(),<br>                Clock.SYSTEM,<br>                cloudWatchAsyncClient()<br>        );<br>    }<br><br>    // 建立 CloudWatchAsyncClient，他定時幫我們把 Metrics 往 CloudWatch 送<br>    // 注意 region 請填入實際在 AWS 上使用 CloudWatch 的 Region<br>    // Credential 的部分請看 retrieveCredentialFromProfile()<br>    @Bean<br>    public CloudWatchAsyncClient cloudWatchAsyncClient() {<br>        return CloudWatchAsyncClient.builder()<br>                .region(Region.AP_NORTHEAST_1)<br>                .credentialsProvider(retrieveCredentialFromProfile())<br>                .build();<br>    }<br><br>    // 設定 CloudWatch <br>    // 定義 namespace，然後每 1 分鐘推送一次<br>    private CloudWatchConfig setupCloudWatchConfig() {<br>        return new CloudWatchConfig() {<br>            private final Map&lt;String, String&gt; configuration = Map.of(<br>                    &quot;cloudwatch.namespace&quot;, &quot;MyMetrics&quot;,<br>                    &quot;cloudwatch.step&quot;, Duration.ofMinutes(1).toString()<br>            );<br><br>            @Override<br>            public String get(String key) {<br>                return configuration.get(key);<br>            }<br>        };<br>    }<br><br>    // 如果你有多個 AWS Credential，可以透過 ProfileCredentialsProvider 去指定 profileName<br>    // 懶人一點可以使用 DefaultCredentialsProvider，他會主動去 Java 啟動變數和環境變數等地方去看有沒有 AccessKey 和 SecretAccessKey<br>    private ProfileCredentialsProvider retrieveCredentialFromProfile() {<br>        return ProfileCredentialsProvider.builder()<br>                .profileName(&quot;your-profile-name&quot;)<br>                .build();<br>    }<br>}</pre><p>最後，我們來撰寫 Aspect ，透過 AOP 來幫我們不污染主業務流程的情況下去收集指標，為什麼要使用這種方式呢？</p><p>如果不使用 AOP 的方式，我們勢必得在每個想要監控的方法中加入 MicroMeter 相關的程式碼，這樣業務流程中混雜了不相干的程式碼，也使得維護性和可讀性降低，這就違反了 SoC。</p><pre>@Aspect<br>@Component<br>public class ControllerMetricsAspect {<br><br>    // 透過 pointcut expression 決定哪裡要織入 Aspect<br>    private static final String pointcutExpression = &quot;&quot;&quot;<br>                execution(public * *(..))<br>            &quot;&quot;&quot;;<br><br>            <br>    private final MeterRegistry meterRegistry;<br><br>    public ControllerMetricsAspect(MeterRegistry meterRegistry) {<br>        this.meterRegistry = meterRegistry;<br>    }<br><br>    @AfterReturning(pointcutExpression)<br>    public void countApiCall(JoinPoint joinPoint) {<br>        Signature signature = joinPoint.getSignature();<br>        String className = signature.getDeclaringType().getSimpleName();<br>        String methodName = signature.getName();<br>        ImmutableTag tag = new ImmutableTag(&quot;api.calls.counter&quot;, className);<br>        // 建立 counter<br>        Counter counter = meterRegistry.counter(methodName, singletonList(tag));<br>        counter.increment();<br>    }<br><br>    @Around(pointcutExpression)<br>    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {<br>        Signature signature = joinPoint.getSignature();<br>        String className = signature.getDeclaringType().getSimpleName();<br>        String methodName = signature.getName();<br>        ImmutableTag tag = new ImmutableTag(&quot;api.execution.timer&quot;, className);<br>        // 建立 timer<br>        Timer timer = meterRegistry.timer(methodName, singletonList(tag));<br><br>        long startTime = System.currentTimeMillis();        <br>        final Object proceed = joinPoint.proceed();<br>        timer.record(System.currentTimeMillis() - startTime, TimeUnit.MILLISECONDS);<br><br>        return proceed;<br>    }<br>}</pre><p>以上就大功告成，現在你可以呼叫 API 然後去 CloudWatch 上觀察各種數據囉。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*XTRKRWPLLMo0_WEjXYRb1w.png" /><figcaption>我們上傳的各式 metrics</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*G82XSkjFuFJYu0BmEKLAbg.png" /><figcaption>視覺化後的metrics</figcaption></figure><p>其實除了運用上述的方法觀測系統之外，MicroMeter 還提供了更簡單易用的 annotation 方式，就是透過了 @Observed 註解，不過這種開箱即用的方式，靈活性就沒有自己客製來的好用囉，大家也可以自己嘗試看看 @Observed 註解，這邊提供一些簡單的參數指引。</p><pre>@Observed(<br>  // 比如這邊是 Greet，統計了 count<br>  // 以後在 CloudWatch 就會有一個 Greet.count 的 metrics<br>  name = &quot;自定義 meterName prefix&quot;, <br>  // 這邊就跟 tag 一樣，也就是 CloudWatch 的 Dimension<br>  lowCardinalityKeyValues = {&quot;test-anno-key-class&quot;, &quot;test-anno-value-class&quot;}<br>)</pre><p>這次的需求非常有趣，爬文學習的同時也讓我更了解 AWS 的服務如何跟應用程式串接，也不得不再次感嘆 AWS 服務的包羅萬象和 Spring 生態系的無所不能啊 （謎之聲：學不動啦）</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fe4be8b99799" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[筆記] Kafka Consumer]]></title>
            <link>https://medium.com/@frankvicky/%E7%AD%86%E8%A8%98-kafka-consumer-ca2c0728a62f?source=rss-abe1cb5099ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/ca2c0728a62f</guid>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[message-queue]]></category>
            <dc:creator><![CDATA[TengYao Chi]]></dc:creator>
            <pubDate>Mon, 04 Mar 2024 13:27:09 GMT</pubDate>
            <atom:updated>2024-03-04T13:27:09.483Z</atom:updated>
            <content:encoded><![CDATA[<p>接續前篇 <a href="https://medium.com/@frankvicky/%E7%AD%86%E8%A8%98-kafka-producer-cc618400e352">Kafka Producer</a>，今天輪到 Kafka Consumer 的筆記。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/980/0*FjzP3PkfzMry4gTr.jpeg" /></figure><p>Consumer 從 Topic （透過 name 來辨認哪個 Topic）讀取資料，其背後是<strong>Pull Model的設計，</strong><strong>Consumer 主動向 </strong><strong>Kafka Broker 發送請求</strong>。</p><ul><li>Consumer 可以從同時從多個 Partition 讀取資料且 Consumer 會自動知道該從哪個 Broker 讀取資料。</li><li>即使 Broker 故障，Consumer 也知道如何自動恢復。</li><li>資料讀取的順序是根據 Offset，從低到到高，每個 Partition 獨立。</li></ul><h3>Consumer Deserializer</h3><p>Deserializer 會把從 Kafka 取得的 bytes 資料反序列化成物件或資料，主要是針對 key 和 value 進行反序列化，因此他要知道 key 和 value 的型態才能使用正確的 Deserializer。</p><p><strong>常見的 </strong><strong>Deserializer</strong></p><ul><li>String</li><li>Integer、Float</li><li>Avro</li><li>Protobuf</li></ul><p>由於序列化和反序列化涉及 Producer 和 Consumer，因此在 Topic 的生命週期都不該改變儲存的資料型態，除非創建新的 Topic 。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=ca2c0728a62f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[筆記] Kafka Producer]]></title>
            <link>https://medium.com/@frankvicky/%E7%AD%86%E8%A8%98-kafka-producer-cc618400e352?source=rss-abe1cb5099ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/cc618400e352</guid>
            <category><![CDATA[kafka]]></category>
            <category><![CDATA[message-queue]]></category>
            <dc:creator><![CDATA[TengYao Chi]]></dc:creator>
            <pubDate>Sat, 02 Mar 2024 03:19:00 GMT</pubDate>
            <atom:updated>2024-03-02T03:20:34.850Z</atom:updated>
            <content:encoded><![CDATA[<p>接續前篇 <a href="https://medium.com/@frankvicky/%E7%AD%86%E8%A8%98-kafka-topics-partitions-and-offsets-75e1c4bc4ee7">Kafka Topics, Partitions and Offsets</a>，今天輪到 Kafka Producer 的筆記。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/980/0*nCLSy1updY_SSXtt.jpeg" /></figure><h3>Producer</h3><ul><li>Producers 產生 message並負責將消息寫入 Topics</li><li>Topics由多個 Partitions 組成，因此，Producers 需要提前知道要把消息寫入哪個 Partition，以及該 Partition 所在的 Broker。</li><li>為了避免 Broker 故障導致message丢失，Producers 需要具備自動故障恢復能力。當 Broker 故障時，Producers 可以自動將message轉送到其他可用的 Broker。</li></ul><p>Producers 受益於 Kafka 的負載平衡機制。Kafka 可以根據message的 key 將其傳送到不同的 Partitions，從而將負載分散到多個 Broker 上。這也正是 Kafka 擴充性高的原因之一：<strong>每個 </strong><strong>Partition 都可以同時接收來自多個 </strong><strong>Producers 的</strong><strong>message。</strong></p><h3>Message Keys</h3><ul><li>Producers 可以選擇在發送 message 時夾帶 key ，key 可以是任何形式。</li><li>如果資料不攜帶 key （就是 key 為 null），那 message 會被以 Round Robin 的方式循環發送</li><li>如果攜帶 key ，擁有相同 key 值的 message 會被送到同一個 Partition。概念類似 HashMap，Kafka 針對 key 進行了 hash，以此確定 message 該往哪個 Partition 送。</li><li>這個機制是 Kafka 重要特色，這可以保證 message 的順序。<strong>當我們幫</strong> message <strong>夾帶 </strong><strong>key ，通常是我們需要利用資料的特定屬性進行排序。</strong></li></ul><h4>Round Robin</h4><ul><li>這是一種負載平衡機制，舉例來說 message先是發送到 Partition 0 然後是 Partition 1 然後 Partition 2 ……</li></ul><h3>Kafka Message 的結構</h3><p>message由以下幾個部分組成：</p><ul><li><strong>Key</strong>：message的鍵，可以是任何形式的 String。</li><li><strong>Value</strong>：message的內容，可以是任何形式的資料。</li><li><strong>Headers</strong>：可以存放一些 meta data，例如來源。</li><li><strong>Partition</strong>：message所在的 Partition。</li><li><strong>Offset</strong>：message在 Partition 中的偏移量。</li><li>Timestamp：時間戳章。</li></ul><h3>Kafka Message Serializer</h3><p>Kafka 只從 Producer 接收 bytes 資料，輸出給 Consumer 也是，這看似違反前面說的：Kafka 接收所有形式的資料，但其實還有一個 Serializer 幫我們做 message 轉換成 bytes 的工作。</p><ul><li>序列化只作用於 key 和 value</li><li>Kafka Producer 針對常見型別提供了一些常見的 Serializer 幫我們進行轉換，例如：StringSerializer、IntegerSerializer、JSONSerializer</li></ul><h3>Kafka Message Key Hashing</h3><p>Kafka Partitioner 是實際上計算 message 該往哪個 Partition 送的組件。</p><ul><li>其中這個計算是透過 key hashing 的結果來決定。</li><li>根據 hash 算法的<strong>結果唯一性</strong>，我們可以得知，message 最終去向是由 Producers 夾帶的 key 決定，所以<strong>是 </strong><strong>Producer 決定了 </strong><strong>message 會被送到哪個 </strong><strong>Partition 。</strong></li><li>預設的 hash 演算法是 murmur2</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cc618400e352" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[筆記] Kafka Topics, Partitions and Offsets]]></title>
            <link>https://medium.com/@frankvicky/%E7%AD%86%E8%A8%98-kafka-topics-partitions-and-offsets-75e1c4bc4ee7?source=rss-abe1cb5099ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/75e1c4bc4ee7</guid>
            <category><![CDATA[message-queue]]></category>
            <category><![CDATA[kafka]]></category>
            <dc:creator><![CDATA[TengYao Chi]]></dc:creator>
            <pubDate>Fri, 01 Mar 2024 04:31:09 GMT</pubDate>
            <atom:updated>2024-03-01T04:31:09.753Z</atom:updated>
            <content:encoded><![CDATA[<p>最近開始學習 Apache Kafka 的相關知識，接下來應該會有一系列 Kafka 的學習筆記，第一篇就從 Kafka 的基礎概念 Topics、Partitions、Offsets 開始。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*pn4F9S-ZXhZFnUfbajE6Dw.jpeg" /></figure><h3>Topics</h3><ul><li>在 Kafka Cluster 中的一個特定資料流（stream of data）就是一個 Topic</li><li>logs、purchases、tweets 、 GPS 等等都可以是一個 Topic</li><li>以 RDBMS 來比喻，Topic 就像是資料表（但是沒有任何 constraints）</li><li>Topics 沒有數量上限</li><li>在 Kafka Cluster 中 ，區分不同 Topic 是透過 name ，所以 name 就是 identify ，必須保持唯一性。</li><li><strong>Topic 支援任何訊息格式，</strong>json、text、binary 等等通通都可以</li><li>一個 Topic 中的訊息序列（the sequence of data）就是所謂的 data stream</li><li>雖然前面說 Topic 類似 RDBMS 的資料表，但我們不能對 Topic 進行查詢（Query），取而代之的是，我們可以透過 Kafka Producer 傳送資料給 Topic，並使用 Kafka Consumer 來從 Topic 讀取資料。</li></ul><h3>Partitions and Offsets</h3><ul><li>我們可以把 Topic 切成<strong>任意個</strong> Partitions ，沒有數量上限，比如把一個 Topic 切成 100 個 Partitions，<strong>注意是 </strong><strong>Partitions 是 </strong><strong>0-index 。</strong></li><li>發送給 Topic 的所有資料都會在這些 Partitions 中，並且在 Partitions 中，<strong>這些資料會被排序</strong>。</li><li>假如陸續有資料被送進 Partition 0，則這些資料會被依須從 0 開始被編上 id，每個 Partition 都有自己獨立的 id（<strong>沒有上限</strong>），這個 id 又叫做 Kafka Partition Offset</li><li>每個 Partition 都有自己的 Offset</li><li>Topic 是<strong>不可變的（immutable）</strong>，一旦資料被寫入 Partition，就不可以被修改、刪除了，只能不斷向 Partition 新增資料。</li></ul><h3>一些細節</h3><ul><li>資料在 Kafka 中是有保存期限的，<strong>預設是一個禮拜</strong>，這是一個可以透過configuration 改變的值；<strong>超出時限之後，資料就會消失</strong>。</li><li>Offset 只對特定 Partition 有意義，就如同前述，一個 Topic 有多個 Partition，每個 Partition 都有自己的 Offset，彼此獨立。因此，Partition 0 的 Offset 對於 Partition 2 沒有意義。</li><li>Offset 不會被重複使用，假如 Offset 1 的資料被刪除了，Offset 1 也不會被重複使用，Offset 只會一直成長下去。這也意謂著，<strong>資料的先後順序只有在同一個 </strong><strong>Partition 會被保證，不同的 </strong><strong>Partition 不能拿來比較資料的順序。</strong></li><li><strong>資料會被送到哪個 </strong><strong>Partition 是隨機的，除非有指定 </strong><strong>key</strong>。</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=75e1c4bc4ee7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[隨筆] 甚麼是 VPC，可以吃嗎？]]></title>
            <link>https://medium.com/@frankvicky/%E9%9A%A8%E7%AD%86-%E7%94%9A%E9%BA%BC%E6%98%AF-vpc-%E5%8F%AF%E4%BB%A5%E5%90%83%E5%97%8E-6f7ef2dabc70?source=rss-abe1cb5099ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/6f7ef2dabc70</guid>
            <category><![CDATA[internet]]></category>
            <category><![CDATA[vpc]]></category>
            <category><![CDATA[aws]]></category>
            <dc:creator><![CDATA[TengYao Chi]]></dc:creator>
            <pubDate>Wed, 28 Feb 2024 13:16:45 GMT</pubDate>
            <atom:updated>2024-02-28T15:21:48.240Z</atom:updated>
            <content:encoded><![CDATA[<p>最近在整理公司在 AWS 上使用的服務，於是有了這篇超短文章，主要是簡短記錄自己了解的 VPC 概念和內部組成。</p><p>首先我們先了解到底甚麼是 VPC，VPC 全名叫做 Virtual Private Cloud ，概念跟區域網路（LAN）很像：</p><ul><li><strong>都使用 Private IP 地址</strong></li><li><strong>都使用路由器或防火牆來控制網路流量</strong></li><li><strong>都允許在網路內部進行通訊</strong></li></ul><p>只是差別在於一個是實體上的一個是虛擬的。</p><p>一般來說，當我們佈建好整組系統，包含各種微服務、資料庫等等，我們不會希望今天隨便一個阿貓阿狗都可以隨意訪問系統的任一元件，這不僅會造成系統負擔，也有被惡意人是輕易攻擊。於是我們將系統與網際網路隔離，放置於 VPC 內，只有透過特定入口（例如 Internet Gateway）才可以從外部訪問 VPC 內的系統。</p><p>這個概念很像是出租套房，這些在同一個大樓內的小套房就是系統裡的元件，他們沒有一個公開直接對外的地址，外面的人想要寄信給這些租套房的人，就必須寄信給管理室，讓管理室在分發給套房租客，這裡你可以想像管理室就是 Internet Gateway。</p><p>至於 VPC 內部怎麼溝通，就如一般的 LAN ，VPC 也有子網路的概念，可以透過設定子網路區段來隔離不同環境，每個服務都會有 Private IP ，可以透過 AWS VPN 或 AWS Direct Connect 來進行溝通，這種內部溝通的方式會搭配 VPC 的 Route Table 來轉導流量。<br>那如果是全球層級的服務呢（例如 S3）？如果要連 S3 要先去網際網路再連回來也太沒效率，對於這種情況還有 VPC Endpoint 可以使用，這個服務讓我們可以經由 AWS 的內部網路去呼叫其他服務。</p><p>那如果今天，VPC 內的某個服務需要對外連線（例如軟體更新），卻沒有 Public IP 該怎麼辦呢？AWS 也提供了 NAT 的服務，可以把 Private IP 轉換成 Public IP 來向網際網路溝通，而且是單向溝通，外部網路無法以相同的路徑連接回來。</p><p>防火牆的部分，AWS 提供了 Security Group ，可以指定允許哪些 inbound 來源、目的地、port 、通訊協定等等，這是一種允許名單，也就是登記在案的才可以通過。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=6f7ef2dabc70" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[隨筆] 如何在 Spring 中替 request 加入自定義的 header ?]]></title>
            <link>https://medium.com/@frankvicky/%E9%9A%A8%E7%AD%86-%E5%A6%82%E4%BD%95%E5%9C%A8-spring-%E4%B8%AD%E6%9B%BF-request-%E5%8A%A0%E5%85%A5%E8%87%AA%E5%AE%9A%E7%BE%A9%E7%9A%84-header-be158a28dc31?source=rss-abe1cb5099ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/be158a28dc31</guid>
            <category><![CDATA[spring]]></category>
            <category><![CDATA[kotlin]]></category>
            <category><![CDATA[http-request]]></category>
            <category><![CDATA[java]]></category>
            <dc:creator><![CDATA[TengYao Chi]]></dc:creator>
            <pubDate>Sun, 25 Feb 2024 07:07:40 GMT</pubDate>
            <atom:updated>2024-02-25T07:07:40.600Z</atom:updated>
            <content:encoded><![CDATA[<h3>[隨筆] 如何在 Spring 中替 request 加入自定義的 header ?</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/307/1*1nQXtS1DG--vrKd4j4DbQw.png" /><figcaption>就是 HTTP</figcaption></figure><p>最近在學習關於 Interceptor 和 filter 的知識時，突然突發奇想，這兩者雖然一個是屬於 Spring 一個是 Servlet API，但本質都是對於request進行預處理 ，那我有沒有可能在 request 到達 controller 之前，對這 request 進行加工呢？</p><p>大家都知道 <a href="https://www.rfc-editor.org/rfc/rfc9110.html">RFC 9110</a> 中明確定義了 HTTP Request 應該是 read-only ，而 Servlet 也遵守了這個規範，因此實際在 Interceptor 和 filter 中拿到 request物件時，其可以呼叫的方法也都是 get ，那我們又該如何對 request進行修改呢？</p><p>這時候就要請今天主角 HttpServletRequestWrapper 登場囉，HttpServletRequestWrapper 本身設計的目的就是為了在不破壞 ServletRequest 封裝的情況下，讓開發者可以針對 ServletRequest 進行擴充，因此我們只需要繼承 HttpServletRequestWrapper 並複寫部分方法，然後將 request 放入自定義的 Wrapper 中就可以囉，下面來看看怎麼操作吧。</p><p>首先我們一樣先去 Spring Initializr 產生一個新的 Spring 專案，唯一要注意的是 Dependency 我們只需要 Spring Web 就好，畢竟我們只是在對 request 小打小鬧，其他東西看倌們隨意，這邊會用 Kotlin 做示範。</p><p>首先我們先建立一個 Controller：</p><pre>@RestController<br>@RequestMapping(&quot;foobar&quot;)<br>class FooBarController {<br><br>    @GetMapping(&quot;/hello&quot;)<br>    fun hello(request: HttpServletRequest): Map&lt;String, String&gt; {<br>        val response = hashMapOf&lt;String, String&gt;()<br>        for (headerName in request.headerNames) {<br>            response[headerName] = request.getHeader(headerName)<br>        }<br>        return response<br>    }<br>}</pre><p>到這邊就先啟動專案，透過 postman 或 curl 去打打看 endpoint ，看會拿到甚麼 response 囉。</p><pre>{<br>  &quot;host&quot;: &quot;localhost:8080&quot;,<br>  &quot;user-agent&quot;: &quot;curl/8.4.0&quot;,<br>  &quot;accept&quot;: &quot;*/*&quot;<br>}</pre><p>好，確定原始的 response 之後，我們就要去嘗試加入自定義的 header 啦。</p><p>首先我們先建立一個類別去繼承 HttpServletRequestWrapper ：</p><pre>class CustomRequestWrapper(request: HttpServletRequest) : HttpServletRequestWrapper(request) {<br>    // 用來存放自定義的 header attribute<br>    private val customHeader: MutableMap&lt;String, String&gt; = HashMap()<br><br>    fun putHeader(name: String, value: String) {<br>        customHeader[name] = value<br>    }<br><br>    // 覆寫原本的 getHeader 方法，讓 customHeader 可以被存取<br>    override fun getHeader(name: String): String? =<br>        customHeader.getOrElse(name) { super.getHeader(name) }<br><br>    // 覆寫原本的 getHeaderNames 方法，將自定義的 header key 也加入 Enumeration<br>    override fun getHeaderNames(): Enumeration&lt;String&gt; {<br>        val headerNames = HashSet(customHeader.keys)<br>        for (headerName in (request as HttpServletRequest).headerNames) {<br>            headerNames.add(headerName);<br>        }<br>        return Collections.enumeration(headerNames)<br>    }<br>}</pre><p>最後，我們去實作一個簡單的 Filter ：</p><pre>// 實際開發時應該使用 @WebFilter，這邊偷懶一下<br>@Component<br>class CustomFilter : Filter {<br>    override fun doFilter(request: ServletRequest?, response: ServletResponse?, chain: FilterChain?) {<br>        // 簡單測試當我們發送請求時有沒有經過 filter<br>        System.err.println(&quot;This is ${this.javaClass.simpleName}&quot;)<br><br>        CustomRequestWrapper(request as HttpServletRequest)<br>            .also { it.putHeader(&quot;customHeader&quot;, &quot;hello&quot;) }<br>            .let { chain?.doFilter(it, response) }<br>    }<br>}</pre><p>到這裡應該就大功告成啦，接這我們去實際發送請求看看回應：</p><pre>{<br>  &quot;customHeader&quot;: &quot;hello&quot;,<br>  &quot;host&quot;: &quot;localhost:8080&quot;,<br>  &quot;user-agent&quot;: &quot;curl/8.4.0&quot;,<br>  &quot;accept&quot;: &quot;*/*&quot;<br>}</pre><p>Console 上也有我們印出的訊息：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*slhTto4M9oVxmFULKGdjsA.png" /></figure><p>這次只是在念書時無聊突發奇想，原本是想整理Interceptor 和 filter 的知識的，結果不小心就歪掉了，而且實際上要對 request 加入自定義 header 的應該算是少見（吧），所以這又是一篇廢文囉，大家下次再見。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=be158a28dc31" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[[隨筆]SSL 證書申請和應用流程]]></title>
            <link>https://medium.com/@frankvicky/ssl-%E8%AD%89%E6%9B%B8%E7%94%B3%E8%AB%8B%E5%92%8C%E6%87%89%E7%94%A8%E6%B5%81%E7%A8%8B-50e4fed38ff8?source=rss-abe1cb5099ad------2</link>
            <guid isPermaLink="false">https://medium.com/p/50e4fed38ff8</guid>
            <dc:creator><![CDATA[TengYao Chi]]></dc:creator>
            <pubDate>Sat, 17 Feb 2024 06:25:40 GMT</pubDate>
            <atom:updated>2024-02-25T05:53:11.705Z</atom:updated>
            <content:encoded><![CDATA[<p>近日在工作中被同事問到 SSL 憑證相關的處理問題，竟有些陌生了，於是抽空寫了這篇，重新整理自己的知識。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_76uusBJ1Bedz15n8HFEIg.png" /></figure><h4>申請證書</h4><p>當伺服器要申請 SSL 證書時，伺服器要先產生公鑰與私鑰，並且準備 Domain、申請者、公鑰等等資料，用以生成 CSR 檔案提交給 CA 機構申請SSL 證書。<strong>請注意私鑰不需要提交並且應該妥善保存。</strong></p><h4>簽發證書</h4><p>通過審核之後，CA 機構會簽發 SSL 證書。</p><p>證書內容如下：</p><p><strong>簽名</strong></p><p>CA 機構會先對證書的明文進行第一次 Hash，產生 Message Digest</p><p>CA 機構接下來會對 Message Digest用自己的密鑰進行加密，這個結果就是 SSL 證書的簽名</p><p><strong>明文</strong></p><ul><li>簽發 SSL 證書的機構</li><li>證書有效時間</li><li>Domain</li><li>申請證書的機構</li><li>申請機構提供的公鑰（Server 產生的公鑰）</li></ul><h4>發送證書</h4><p>證書通常會被安裝在 Server 上，而不是直接發送給 Client。在 SSL/TLS 握手時，Server 會首先提供證書給 Client（也就是使用者的瀏覽器），也可以透過 Email 或下載的方式發送給使用者。</p><h4>驗證證書</h4><p>Client 收到證書後，首先先對簽名進行解密，解密需要使用 CA 機構的公鑰，而這個公鑰是公開的。</p><p>解密之後拿到 Message Digest ，Client 再對證書明文進行 Hash 並將結果與 Message Digest 進行比對，因為 Hash 演算法都具有雪崩性和唯一性，所以若結果相等則代表明文內容合法且沒有被篡改，至此，Client 取得包含 Server 公鑰在內的所有明文。</p><h4>密鑰協商</h4><p>在 TLS 交握期間，Client 和 Server 會使用公鑰（Server 放在證書中的公鑰）和私鑰來交換隨機產生的資料，而這個隨機資料會用來建立新的加密金鑰，稱為工作階段金鑰，工作階段金鑰是對稱式密鑰，取得之後，Server 與 Client 的溝通皆使用此密鑰加密與解密。</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=50e4fed38ff8" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>