<?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 Michael Hsu on Medium]]></title>
        <description><![CDATA[Stories by Michael Hsu on Medium]]></description>
        <link>https://medium.com/@evenchange4?source=rss-aa88256220c4------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*DJGW_YxAUzKrLFv333L0EQ.jpeg</url>
            <title>Stories by Michael Hsu on Medium</title>
            <link>https://medium.com/@evenchange4?source=rss-aa88256220c4------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 24 May 2026 02:29:02 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@evenchange4/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[Front-End Practice 2019]]></title>
            <link>https://medium.com/@evenchange4/front-end-practice-2019-251a5876a6c8?source=rss-aa88256220c4------2</link>
            <guid isPermaLink="false">https://medium.com/p/251a5876a6c8</guid>
            <category><![CDATA[front-end-development]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Michael Hsu]]></dc:creator>
            <pubDate>Wed, 20 Nov 2019 04:25:56 GMT</pubDate>
            <atom:updated>2019-11-21T02:15:46.908Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*32B99LtZuUsHjL8K18vXvw.jpeg" /><figcaption><a href="https://unsplash.com/photos/i4W8OINLI_I">His name is Tigger</a>. Photo by <a href="https://unsplash.com/@catherineheath?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Catherine Heath</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><h4>Roadmaps for Front-End Developers</h4><p>邁入 2020 是公司前端團隊第六週年，剛好來整理 2019 年度 Front-End 的實踐與技術盤點，對於前端團隊而言，什麼才是該關注的面向，此篇快速梳理了現況開發模式、過去回顧及未來的關注，期望也能給予往後成員一些 Guideline 方向可以遵循。</p><p>可以瀏覽由 <a href="https://roadmap.sh/frontend">Kamran Ahmed 所整理的 Developer Roadmap</a>，能快速檢視自我能力所缺乏之處，也明確知道前端的職責範圍，下圖為其中幾個重點項目，接下來會針對這些面向進行探討：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kJ_GjLq9MFDaCg6Fkb6eOg.png" /><figcaption>Front-End Practice Mind map</figcaption></figure><h4>Client-Side Frameworks</h4><p>公司內使用了 Angular 一年與 React 五年，經常被問到有沒有考慮用 Vue 或其他關鍵字技術，既然 React Ecosystem 已經能夠在數個專案的磨合下，大幅度的滿足團隊需求，又有什麼特別的原因需要打破舒適圈呢？或是我們在這問題之上應該要釐清的是， Why React.js Community。伴隨新版 Facebook UI 重構的需求，React 時常改版與提出新的設計理念，有大公司的背書外，最主要影響一個技術迭代是生態圈及社群的活絡，除了看彼此語法之間的差異，應全局地探索技術的<strong>效率效能、穩定性、擴展性與前瞻性</strong>。</p><p>在採用一個技術時，不只是比較 API 的封裝，我們挑選能改變開發思維，並在想法上有所突破者，恰巧 React 就是這方面的領頭羊。除了 Declarative and Reactive Programing、Data Flow、Immutability 的設計理念，到近年 Functional Programing，也讓 UI Component 模組化成為顯學。種種目的除了降低專案維護上的困難、增加開發可預期性，並使測試更為方便，以提升專案健康度。</p><blockquote>選擇一門技術，是否對於思維上有所改變</blockquote><h4>Zero Configuration Setup</h4><p>前端領域最為困難的是開發工具，理解現代工具 Webpack 的發展是有必要的，但每當開始新專案又得再次堆起積木，前置作業花費瑣碎的時間，因此有了生成專案的工具，不大需要額外的設定，並保有客製化的擴充彈性。這些工具不只是快速讓架構生成，大幅<strong>減小專案啟動的最大靜摩擦力</strong>，也一定比率的讓維運成本轉嫁，以專注於產能提升。</p><p>面對不同性質的網站需求，且當專案規模不明確時，在以下三個成熟 Zero Configuration Frameworks 工具的比較下，Next.js 相較能夠滿足各類專案，是最為彈性的解決方案：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*InTwDDEwOkBL-stSywtcAQ.png" /></figure><blockquote>維運成本轉嫁，專注於產能提升</blockquote><h4>UI Modularize</h4><p>採用 React 使得 UI 元件模組化成為顯學，就一個專案的開發週期來說，先定義元件、各個頁面挑選元件進行組裝，複雜的元件可以將數枚元件 Compose 搭配在一起使用，甚至是<strong>跨專案能夠複用</strong>。</p><p>過去 <a href="https://github.com/Mediatek-Cloud/mcs-ui">MCS-UI</a> 是以一個團隊標準化作為目標，搭建符合 Branding 的 Guideline，小團隊在維護上還是相當有難度，時常因人力與專案時程壓力而妥協，以至於採用 Open source UI Framework 的團隊仍佔多數。面對需求的變動，Material-UI 是 Google 提出規劃完整的設計框架，其元件種類豐富、<strong>文檔齊全</strong>、API Spec 的擴充性、用以提升開發體驗的 Styled System 設計，都是採用前考量的重點，甚至在使用後也能從中學習到一些技巧。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FSJluk9BpqnfeQvNVwAfwg.png" /><figcaption>MCS-UI [<a href="https://github.com/Mediatek-Cloud/mcs-ui">Link</a>]</figcaption></figure><p>談論模組化，前端除了 CSS 外，邏輯上的封裝也是一種設計美學，React 經歷了三次的變革，從 Higher-Order Components (HOC) 到 Render props，以及近期的 Hooks，不是追求語法上的標新立異，確實解決了模組化的痛點，讓測試更專注，模組化複用思維的改變引領各大社群的相繼參照與討論，這也是 React 的魅力所在。</p><blockquote>UI 模組化成為了顯學</blockquote><blockquote>延伸閱讀：</blockquote><blockquote><a href="https://medium.com/@evenchange4/build-a-web-app-in-mediatek-61b0a26215a0">[Apr, 2017] Build A Web App in MediaTek</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/refactoring-react-component-in-reactive-way-52a64fb6d70b">[Apr, 2017] Refactoring React Component in Reactive Way</a></blockquote><h4>API Communication and Data Layer</h4><p>提到前後端分離，各團隊能在分工下達到最大效益，除專注於各自領域的開發外，最重要的是雙邊的溝通，GraphQL 成為最佳的橋樑。GraphQL 除了已知整體網站效能的提升外，文件化是大多數團隊忽視的重點，肯定沒有工程師會想花費過多的心思在撰寫與維護 API Documentation 上，而 GraphQL Introspection 有著開發完即完成文檔的特性。因此當開發一套系統時，前後端可以先協商 GraphQL Schema Definition Language (SDL)，一旦定下了 Spec 將大幅減去溝通成本。</p><p>Side Effects 與狀態的管理是前端的課題之一，幾年前的 RESTful 與 Redux 是大神們提出的主流解決方案，而後各種完善的工具如雨後春筍般出現。後起之秀 Apollo Client 搭著 GraphQL 順風車出現，從 <a href="https://blog.apollographql.com/previewing-the-apollo-client-3-cache-565fadd6a01e">Cache 設計上</a>讓很多瑣碎的事情自動化、聲明式的數據取用，大幅降低前端專案 Codebase 的複雜度，使狀態的管理更為直覺。</p><p>同樣的，此難題由 Facebook 提出從根本設計上去改善，因而 <a href="https://reactjs.org/docs/concurrent-mode-intro.html">React Concurrent Mode</a> 的出現，將提升 Side Effect 的微控能力，勢必會再次帶動使用者體驗上的優化，是未來可以密切關注的項目。</p><blockquote>GraphQL 的採用成為專案的首選</blockquote><blockquote>延伸閱讀：</blockquote><blockquote><a href="https://speakerdeck.com/evenchange4/graphql-javascript-fullstack-solution">[Dec, 2017] GraphQL — JavaScript Fullstack Solution</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/graphql-microservice-with-apollo-df4ae25a6d43">[Aug, 2017] GraphQL Microservice with Apollo</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/centralized-error-handle-in-react-with-rxjs-fac9e9e37e44">[Jul, 2017] Centralized Error Handle in React with RxJS</a></blockquote><h4>Code Quality and Performance</h4><p>工具標準化是一個 Codebase 健康度的表現，如今已有 Prettier 讓 Coding Style 排版不再煩惱，搭配 ESLint 規範的 Rules 使開發語法基礎、語意細節不遺漏，除此之外 Static Type Check 也是多人合作不可或缺的一部分，採用 TypeScript 補足了 JavaScript 型別語法上的不足。另外，也可以採用諸如 <a href="https://graphql-code-generator.com/">GraphQL Code Generator</a> 的工具，自動化產生 Type 語法，盡量減少撰寫瑣碎的程式碼。</p><p>前面段落提到模組化，其目的之一讓測試更為專注。在過往以 Jest 撰寫測試的經驗中，應從 User Story 在意的操作行為進行測試，也因此 React Testing Library 的理念取代了 Enzyme，不要過度理解實作細節。一般來說並不需要達到 100% Test Coverage rate，尤其時在 Scrum 快速迭代下，過多的測試反成累贅，目前經驗大概介於 70% 左右。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uV0ur86jlfGm3ZUBNXf16w.png" /><figcaption>React Testing Library with Jest</figcaption></figure><p>最後評估網站，因應瀏覽器的更新，效能指標需與時俱進，最好的做法是查看社群統整的大抄：<a href="https://github.com/joshbuchea/HEAD">HEAD</a>、<a href="https://github.com/thedaviddias/Front-End-Checklist">Front-End Checklist</a>、<a href="https://github.com/thedaviddias/Front-End-Performance-Checklist">Front-End Performance Checklist</a> 都是很棒的整理，為產品交付前可以參考的清單。</p><blockquote>從 User Story 行為進行測試，不過度理解實作細節</blockquote><blockquote>延伸閱讀：</blockquote><blockquote><a href="https://medium.com/@evenchange4/react-stack-%E9%96%8B%E7%99%BC%E9%AB%94%E9%A9%97%E8%88%87%E5%84%AA%E5%8C%96%E7%AD%96%E7%95%A5-b056da2fa0aa">[Jul, 2017] React Stack 開發體驗與優化策略</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/introducing-prettier-with-eslint-13f567ae0184">[Apr, 2017] Introducing Prettier with Eslint</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/component-based-code-splitting-70aeebbb2dc7">[May, 2017] Component-based Code Splitting</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/%E9%80%99%E5%80%8B%E9%9B%B7%E8%B8%A9%E4%BA%86%E5%9B%9B%E6%AC%A1-%E7%82%BA%E4%BB%80%E9%BA%BC%E4%BD%A0%E5%8F%AF%E8%83%BD%E9%9C%80%E8%A6%81-eslint-9330c1245ec1">[May, 2017] 這個雷踩了四次，為什麼你可能需要 Eslint</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/reproducing-medium-style-progressive-image-loading-for-react-2e83bba0c608">[May, 2017] Reproducing Medium Style Progressive Image Loading for React</a></blockquote><h4>CICD Workflow and Production</h4><p>工作流程可以從工程面與管理面不同角度切入，<strong>實質上是相互關聯，</strong>如下圖，針對每一個流程，Scrum 的管理工具 Jira 在看板的設計也可以有對應關係。從工程面來看，採用最簡單直覺的 GitHub Flow 開始，完成開發後發起 Pull Rrequest 讓團隊進入 Code Review 的階段，CI 自動化跑測試，同意合併進入 Master 分支，觸發打包部署到測試環境。經測試反饋後，最後下 Git Tag，觸發部署到正式環境。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*j7v9ksshn17vjffijF_dZA.png" /><figcaption>Workflow</figcaption></figure><p>常見有 Travis、Circle CI、Jenkins，而我們使用 Drone 作為 CI Server，以 Docker-based 為基礎，乾淨的執行環境不必再煩惱干擾的問題。有別於以往在 Bamboo 使用 UI 來設定流程，採取 Workflow as Code 的方式來管理專案，也就是流程跟著程式碼走，以便於版本控制，權限下放<strong>工程師能完整設定可預期的工作流程</strong>。</p><p>現在有諸如 <a href="https://github.com/zeit/pkg">Pkg</a> 的工具，可以打包為執行檔成 Binary，提供跨平台支援，而容器化已是產品交付的標準，搭配 Docker Compose 或 Kubernetes，讓管理複雜的容器成為了如今不可或區的利器，因此有了 DevOps Roadmap 更寬廣的路要走，這不是簡單的事情，也是目前所欠缺的。在建置機器環境時，以往都是直接進入環境開始安裝相依環境，之後應採用類似 Infrastructure as Code (IaC) 的概念，讓機器管理與維運更具可預期性。</p><blockquote>延伸閱讀：</blockquote><blockquote><a href="https://medium.com/@evenchange4/deploy-a-commercial-next-js-application-with-pkg-and-docker-5c73d4af2ee">[May, 2018] Deploy a commercial Next.js application with pkg and docker</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/make-your-react-service-portable-b58680688f6a">[May, 2018] Make Your React Service Portable</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/%E4%BA%94%E5%88%86%E9%90%98-kubernetes-%E6%9C%89%E6%84%9F-e51f093cb10b">[Sep, 2017] 五分鐘 Kubernetes 有感</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/microservice-%E7%94%A2%E5%93%81%E4%BA%A4%E4%BB%98-9f2954c7167d">[Aug, 2017] Microservice 產品交付</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/%E4%BD%BF%E7%94%A8-circleci-2-0-workflows-%E6%8C%91%E6%88%B0%E4%B8%89%E5%80%8D%E9%80%9F-9691e54b0ef0">[Jul, 2017] 使用 CircleCI 2.0 Workflows 挑戰三倍速</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/i18n-workflow-for-react-project-9f9ff8fe9aef">[Apr, 2017] I18n Workflow for React Project</a></blockquote><h4>Beyond Front-End</h4><p>專案可能碰到要洽接數個現有系統的案例，GraphQL 很適合扮演所謂 Backend-For-Frontend (BFF) 的角色，其實就是一個 Proxy Sever 的實作，匯流統整數據，最終只揭露單一 Endpoint 給前端頁面使用，對於全站整體效能有所提升。另外，前後端使用 JavaScript 開發，使得部分邏輯 Client Side 與 Server Side 可以共用，因此前端也具備開發後端的能力。</p><p>在 TensorFlow.js 的發展下，能使用瀏覽器資源進行深度學習，非常適合在終端裝置做識別的應用，能夠在不透過網路傳輸下，進行即時的推論與預測，是一個非常有趣的領域。因此前端也適合從此踏入 AI，當一個跨領域的斜槓前端。</p><figure><a href="https://medium.com/@evenchange4/2018-graphql-%E6%BC%B8%E9%80%B2%E5%BC%8F%E5%B0%8E%E5%85%A5%E7%9A%84%E6%9E%B6%E6%A7%8B-aeb2603f2223"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3i9H8w9Awsdr8OOJ-74GIQ.png" /></a></figure><figure><a href="https://medium.com/@evenchange4/inference-on-the-browser-with-tensorflow-js-c7b4de863a2a"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7U5EO4gdIzgpLXriDYd2qw@2x.png" /></a><figcaption>Left: Backend-For-Frontend &amp; Right: TensorFlow.js</figcaption></figure><blockquote>斜槓前端，具備統整能力</blockquote><blockquote>延伸閱讀：</blockquote><blockquote><a href="https://medium.com/@evenchange4/inference-on-the-browser-with-tensorflow-js-c7b4de863a2a">[Sep, 2019] Inference on the Browser with Tensorflow JS</a></blockquote><blockquote><a href="https://medium.com/@evenchange4/2018-graphql-%E6%BC%B8%E9%80%B2%E5%BC%8F%E5%B0%8E%E5%85%A5%E7%9A%84%E6%9E%B6%E6%A7%8B-aeb2603f2223">[Dec, 2017] GraphQL 漸進式導入的架構</a></blockquote><h4>Conclusion</h4><p>雖然在前端的世界目前都是寫 JavaScript 為主，那團隊開發模式為什麼會發生迭代，進而產生 Legacy Code 與技術債呢？其一，因硬體與瀏覽器的進步與安全性的加強，驅動了技術的變化，原本十行程式完成的事情變成一行就搞定，而現在已有 Webpack、Babel 等完善的開發工具，已經盡量彌平過渡期所造成的差異，並使程式語言更具擴充性，提升開發體驗舒適度。</p><p>最主要應該還是人的因素，團隊成員的流動與組成，常常是決定一個團隊技術棧的關鍵。在人力充沛時，可以考慮團隊自主開發細節的 UI Library，但人力緊縮時，就只能做出一些取捨採用現成的工具。技術的選擇，也因專案規模與需求而異，並不會有一招半式能打天下的作法，但不變的只有過程中所累積的經驗與開發思維。適應追逐新技術的環境，是前端領域不可或缺的能力。</p><iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fspeakerdeck.com%2Fplayer%2Fba3bfa55baf44b3e8c5f8bd5872d9ce8&amp;url=https%3A%2F%2Fspeakerdeck.com%2Fevenchange4%2Ffront-end-practice-2019&amp;image=https%3A%2F%2Fspeakerd.s3.amazonaws.com%2Fpresentations%2Fba3bfa55baf44b3e8c5f8bd5872d9ce8%2Fslide_0.jpg%3F575363&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=speakerdeck" width="710" height="532" frameborder="0" scrolling="no"><a href="https://medium.com/media/94c9f60f182fe8ae77631b5ea8aebeb8/href">https://medium.com/media/94c9f60f182fe8ae77631b5ea8aebeb8/href</a></iframe><h4>References</h4><ul><li><a href="https://github.com/kamranahmedse/developer-roadmap">Roadmap to becoming a web developer in 2019</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=251a5876a6c8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Inference on the Browser with Tensorflow JS]]></title>
            <link>https://medium.com/@evenchange4/inference-on-the-browser-with-tensorflow-js-c7b4de863a2a?source=rss-aa88256220c4------2</link>
            <guid isPermaLink="false">https://medium.com/p/c7b4de863a2a</guid>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[vgg16]]></category>
            <category><![CDATA[tensorflowjs]]></category>
            <category><![CDATA[transfer-learning]]></category>
            <dc:creator><![CDATA[Michael Hsu]]></dc:creator>
            <pubDate>Tue, 10 Sep 2019 08:38:55 GMT</pubDate>
            <atom:updated>2019-09-12T07:59:52.231Z</atom:updated>
            <content:encoded><![CDATA[<h4>以 CNN 貓狗分類器為例，讓 AI 以不同角度切入前端戰場</h4><p>全民人工智慧時代來臨，近期正進行公司的深度學習課程中，所以挑了一組常見的 <a href="https://www.kaggle.com/c/dogs-vs-cats">Kaggle Dogs vs. Cats Dataset</a> 來練習。網路上的學習資源已經相當豐富，這篇文章主要會紀錄從訓練端到終端上的過程，以 Python Karas 做訓練，輸出轉換成 Tensorflow.js 的格式，在瀏覽器端進行推論，完整地走過一遍。</p><blockquote>Links: <a href="https://github.com/evenchange4/nextjs-tfjs-cnn">GitHub</a> | <a href="https://github.com/evenchange4/nextjs-tfjs-cnn/blob/master/python/vgg16.ipynb">Jupyter Notebook</a> | <a href="https://nextjs-tfjs-cnn.now.sh/">Demo</a></blockquote><figure><img alt="Architecture diagram" src="https://cdn-images-1.medium.com/max/1024/1*HgKl3jn6-wI96TQr0pVydg@2x.png" /><figcaption>Architecture diagram</figcaption></figure><h3>Convolutional Neural Network</h3><p>針對影像問題上主要會拿 Convolutional Neural Network 來分析，CNN 使用 Convolutional 概念擷取特徵、Pooling 降維加速計算，並在訓練中自動學習最佳參數。而其中如何去設計中間層是非常需要經驗的，雖然可以從頭搭建出自己的模型，但也有更有效率的做法，也就是 <a href="https://www.tensorflow.org/js/tutorials/transfer/what_is_transfer_learning">Transfer Learning</a>：直接採用 Pre-trained 的模型與參數。本篇文章挑選了由 Keras 實作 ImageNet 資料集所訓練出來的 VGG16 模型，直接複用中間層來萃取圖像特徵，最後再客製化輸出層來作為最後分類器使用。</p><h3>Training</h3><h4>Running Jupyter Notebook on Google Colab</h4><p>Jupyter Notebook 是很常見來撰寫程式的工具，所見即所得的特性，相當適合拿來對資料結構做前處理。Notebook 也便於結果的分享，因此 Kaggle 會有許多熱心的人上傳各自的實驗過程，不管是要 Reproduce 或在學習上都很有幫助，筆者也是直接參考推薦數最多的 <a href="https://www.kaggle.com/uysimty/keras-cnn-dog-or-cat-classification">Uysim’s Notebook</a> 來整理資料。以下是最後整份 Notebook 的紀錄，可以直接跑在 Google Colab 並且選擇 GPU 來加速運算，接下來的段落會把每個步驟稍微解釋：</p><blockquote>Jupyter Notebook: <a href="https://github.com/evenchange4/nextjs-tfjs-cnn/blob/master/python/vgg16.ipynb">https://github.com/evenchange4/nextjs-tfjs-cnn/blob/master/python/vgg16.ipynb</a></blockquote><h4>Pre-requirements</h4><p>在 Notebook 中可以直接下 kaggle CLI 指令，將資料集下載到 Google Colab 的空間，但每次重新連接可能會被分配新的環境，過程中資料不會被保留，所以也可以透過 Mount Google drive 把最後要輸出的結果放到個人的空間上：</p><pre>!kaggle competitions download -c dogs-vs-cats<br>drive.mount(&#39;/content/drive&#39;)</pre><h4>Build Model</h4><p>Transfer Learning Model 實作上直接拿 Keras 提供的 VGG16，因為設定 include_top=False 忽略 Output Layers，只拿中間層來當作 Feature Extractor，並且設定 trainable = False Freeze Layers，直接使用訓練好的 weights=’imagenet’ 參數。後面加上 Classifier Layers，這幾層的參數則是需要在過程中被從頭訓練的，其中使用 BatchNormalization 讓數值不會過大過小，加速收斂。接著數層全連接層 Dense，以及避免 Overfitting 使用 Dropout：一定機率讓神經元被忽略。最後我們要分類貓或狗，設定輸出兩個維度，並挑選 Softmax 激活函數使得機率總和為 1：</p><pre>pre_trained_model = VGG16(include_top=False, weights=&#39;imagenet&#39;)<br>for layer in pre_trained_model.layers:<br>    layer.<strong>trainable = False</strong></pre><pre>x = <strong>BatchNormalization</strong>()(pre_trained_model.output)<br>x = Flatten()(x)<br>x = Dense(units=2048, activation=&quot;relu&quot;)(x)<br>x = Dense(units=256, activation=&quot;relu&quot;)(x)<br>x = <strong>Dropout</strong>(0.5)(x)<br>x = Dense(units=2, activation=&quot;softmax&quot;)(x)</pre><pre>model = Model(inputs=pre_trained_model.input, outputs=x)<br>model.compile(loss=&#39;categorical_crossentropy&#39;,<br>              optimizer=&#39;adam&#39;,<br>              metrics=[&#39;accuracy&#39;])</pre><h4>Prepare data</h4><p>在真正開始跑訓練前，需要把資料做好準備，首先將下載的資料集切分為訓練用的 Training data，與小部分判斷訓練結果的 Validation data。若資料量不足，為了避免 Overfitting 的狀況，可以使用由 Keras 提供的 Data augmentation <a href="https://keras.io/preprocessing/image/#imagedatagenerator-class">ImageDataGenerator</a> 功能，在每一輪 Epoch 自動生成相仿的圖片供訓練。見下圖範例，將原始圖像旋轉、縮放、裁減、翻轉，轉化後的假資料，以提升模型的泛化能力：</p><figure><img alt="Data augmentation ImageDataGenerator" src="https://cdn-images-1.medium.com/max/715/1*DTnUCzoI4JhxqHcnKcalbw.png" /><figcaption>Data augmentation ImageDataGenerator</figcaption></figure><h4>Fit Model</h4><p>跑訓練與調校模型參數是過程中最耗時的，可以採用 GPU 來加速運算，其中設定 batch_size 讓 Mini-batch Gradient Descent 小批小批更新學習參數；透過 epochs 設定資料集要拿來訓練幾輪。除此之外，可以預先設定訓練結束的 callbacks 邏輯，例如若連續幾輪都沒獲得優化，EarlyStopping 就直接中斷訓練；又或是下左圖觀測 Validation Accuracy 連續兩輪都沒有提升，讓 ReduceLROnPlateau 調降 Learning Rate，來減小訓練步伐。下右圖為視覺化最後訓練出來 Accuracy 的變化：</p><pre>EarlyStopping(patience=5)<br>ReduceLROnPlateau(monitor=&#39;val_acc&#39;, <br>                  patience=2, <br>                  verbose=1, <br>                  factor=0.5, <br>                  min_lr=0.00001)</pre><figure><img alt="Fit model with callbacks" src="https://cdn-images-1.medium.com/max/1024/1*aAKQ9QCab9PF8eUPhNefaQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/856/1*aMbR3CpBE5YQ2C56h_y5bw.png" /><figcaption>Fit model with callbacks &amp; Virtualize training accuracy</figcaption></figure><h4>Predict Result</h4><p>最後我們來看沒有提供答案的 Testing data，將預測得到的結果整理好 CSV 格式，上傳到 <a href="https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/overview">Kaggle Late Submission</a> 進行 Log Loss 評分，如果滿意可以 model.save 將模型及訓練參數輸出，最後會得到一份 HDF5 格式的檔案 model.h5。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/852/1*yHxNPEAW0WSm1ueKfy8Pag.png" /><figcaption>Testing data result</figcaption></figure><h3>Client side</h3><p>緊接著進入前端 JavaScript 的世界，使用 Next.js 快速搭建環境，並使用 React Material UI 做了基本 Layout。載入模型前，首先將上個段落的 model.h5 透過 tensorflowjs_converter 轉成 Tensorflow.js 的格式，其輸出包含 model.json 與 Binary weight files，這邊拿準備好的 Docker image 環境來執行 CLI，最後下圖利用 <a href="https://github.com/lutzroeder/netron">Netron</a> 來視覺化模型，驗證轉換後的結果是否正確：</p><pre>$ docker run -it --rm \<br>  -v &quot;$PWD/python:/python&quot; \<br>  evenchange4/docker-tfjs-converter \<br>  tensorflowjs_converter --input_format=keras model.h5 model-tfjs</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CxhcHUjz6HDk7tHJ021Kjw.png" /><figcaption>Visualize NN Model with Netron</figcaption></figure><p>在跑預測前，需要把輸入圖檔調整為模型的 Input Tensor，並注意也需要用當初在 Keras VGG16 的 Image Preprocess 步驟，將圖片 Tensor 做一些前處理運算，因為 Tensorflow.js 的支援，讓這些步驟簡單很多：</p><pre><a href="https://github.com/evenchange4/nextjs-tfjs-cnn/blob/master/utils/preprocessImage.ts"><em>// preprocessImage.ts</em></a></pre><pre>const imageTensor = tf.browser<br>  .fromPixels(image)<br>  .resizeNearestNeighbor([224, 224])<br>  .toFloat()<br>  .sub(meanImageNetRGB)<br>  .reverse(2)<br>  .expandDims();</pre><h4>Web Worker</h4><p>最後實驗嘗試將 Predict 函式丟到 Web Worker，使用 WebGL 運算，目前 Chrome 支援度會比較好，但在 CNN 的支援上<a href="https://github.com/tensorflow/tfjs/issues/102">不是很到位</a>，UI Main thread 還是會有卡頓感，這也是之後前端可以優化的目標。下圖是操作流程，先透過 Web Worker 將模型與參數下載，接著就可以上傳圖片進行瀏覽器端的預測了：</p><pre><a href="https://github.com/evenchange4/nextjs-tfjs-cnn/blob/master/utils/inference.worker.js"><em>// inference.worker</em></a></pre><pre>const model = await tf.loadLayersModel(&#39;/static/model.json&#39;);<br>model.<strong>predict</strong>(imageTensor);</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EC5dKVsp8mIVcKHNWIOsrg.png" /><figcaption>Demo [<a href="https://nextjs-tfjs-cnn.now.sh/">https://nextjs-tfjs-cnn.now.sh/</a>]</figcaption></figure><h3>Conclusion</h3><p>在這篇文章我們走過了 Transfer Learning，一開始複用 VGG16 來搭建一個分類器，使用 Kaggle Dataset 在 Google colab 上用 GPU 來快速訓練，最後把輸出的模行轉換成 Tensorflow.js 的格式，在瀏覽器做本地端預測。雖然目前結果不甚好，之後需要花費大量時間調校，但在深度學習的研究成果下，類神經網路已經非常便於使用了。另外可以研究的方向是，預測準確度與模型大小的取捨，目前 VGG16 輸出檔案太大，造成瀏覽器端需要花費很多時間下載。期待接下來也許可以透過這樣的架構，從生活中或公司的資料實際找尋更有趣的應用案例，如果你喜歡這類的文章，可以 👏 讓我知道，也歡迎訂閱 Medium 喔！</p><h4>References</h4><ul><li><a href="https://www.kaggle.com/uysimty/keras-cnn-dog-or-cat-classification">Keras CNN Dog or Cat Classification</a></li><li><a href="https://github.com/himanshu987/VGG16-with-TensorflowJs">VGG16-with-TensorflowJs</a></li><li><a href="https://towardsdatascience.com/tensorflow-js-using-javascript-web-worker-to-run-ml-predict-function-c280e966bcab">TensorFlow.JS — Using JavaScript Web Worker to Run ML Predict Function</a></li><li><a href="https://demo.leemeng.tw/">AI 如何找出你的喵</a></li><li><a href="https://github.com/tensorflow/tfjs-examples/blob/master/mnist-transfer-cnn/README.md">TensorFlow.js Example: MNIST CNN Transfer Learning Demo</a></li><li><a href="https://deeplizard.com/learn/video/hRKEQhiqIU4">TensorFlow.js — Explore tensor operations through VGG16 preprocessing</a></li><li><a href="https://gogul09.github.io/software/mobile-net-tensorflow-js">Classifying images using Keras MobileNet and TensorFlow.js in Google Chrome</a></li><li><a href="https://towardsdatascience.com/building-a-blood-cell-classification-model-using-keras-and-tfjs-5f7601ace931">Building a blood cell classification model using Keras and tfjs</a></li><li><a href="https://blog.csdn.net/msmw2/article/details/80454751">Keras 框架中的 epoch、bacth、batch size、iteration</a></li><li><a href="https://github.com/Elwing-Chou/TibameDL">https://github.com/Elwing-Chou/TibameDL</a></li><li><a href="https://github.com/leemengtaiwan/cat-recognition-train">https://github.com/leemengtaiwan/cat-recognition-train</a></li><li><a href="https://chtseng.wordpress.com/2017/11/11/data-augmentation-%E8%B3%87%E6%96%99%E5%A2%9E%E5%BC%B7/">Data Augmentation 資料增強</a></li><li><a href="https://medium.com/%E9%9B%9E%E9%9B%9E%E8%88%87%E5%85%94%E5%85%94%E7%9A%84%E5%B7%A5%E7%A8%8B%E4%B8%96%E7%95%8C/%E6%A9%9F%E5%99%A8%E5%AD%B8%E7%BF%92-ml-note-cnn%E6%BC%94%E5%8C%96%E5%8F%B2-alexnet-vgg-inception-resnet-keras-coding-668f74879306">[機器學習 ML NOTE] CNN 演化史(AlexNet、VGG、Inception、ResNet)+Keras Coding</a></li></ul><p><em>Special thanks to </em><a href="https://github.com/mnicnc404"><em>mnicnc404</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c7b4de863a2a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Deploy a commercial Next.js application with pkg and docker]]></title>
            <link>https://medium.com/@evenchange4/deploy-a-commercial-next-js-application-with-pkg-and-docker-5c73d4af2ee?source=rss-aa88256220c4------2</link>
            <guid isPermaLink="false">https://medium.com/p/5c73d4af2ee</guid>
            <category><![CDATA[react]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[nodejs]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[zeit]]></category>
            <dc:creator><![CDATA[Michael Hsu]]></dc:creator>
            <pubDate>Mon, 14 May 2018 15:12:28 GMT</pubDate>
            <atom:updated>2018-05-14T15:17:26.343Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*r6B9MhWbn4r-_-YKkE4e6A.png" /><figcaption>GitHub Repository: <a href="https://github.com/evenchange4/nextjs-pkg-docker-alpine">https://github.com/evenchange4/nextjs-pkg-docker-alpine</a></figcaption></figure><p>As I mentioned in the “<a href="https://medium.com/@evenchange4/make-your-react-service-portable-b58680688f6a">Make Your React Service Portal</a>”, there is a scenario that <a href="https://medium.com/@evenchange4/make-your-react-service-portable-b58680688f6a#007b">we deliver commercial applications with docker</a>. Letting customers operate their own applications without source code is the key point in this deploy flow.</p><p>Nowadays, Golang may be the best solution of back-end application and end up with the smallest binary file. Node.js is a common choice in front-end/React ecosystems. However, there is no built-in function to package Node.js project. Fortunately, the <a href="https://github.com/zeit/pkg">pkg CLI</a> made by <a href="https://zeit.co/">zeit</a> does a great job for Node.js platform.</p><p>In this artical, we will introduce pkg to package the Next.js application into an single executable file and use Docker to wrap it with Alpine Linux environment.</p><h4>PKG</h4><blockquote>The pkg CLI enables you to package your Node.js project into an executable that can be run even on devices without Node.js installed. —<a href="https://github.com/zeit/pkg"> zeit/pkg</a></blockquote><p>First, we need to describe pkg configuration of a Node.js project in package.json:</p><ol><li>bin: The entrypoint of Node.js project. You will need a <a href="https://github.com/zeit/next.js#custom-server-and-routing">custom server</a> for the Next.js project.</li><li>pkg.assets: Raw contents of the project. You might want to specify several public static files built by Next.js here.</li><li>pkg.scripts: The JavaScript files need to be compiled into binary without sources.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ip6ODfhue_ofEdmepBE4LA.png" /><figcaption>pkg configuration in package.json [<a href="https://github.com/evenchange4/nextjs-pkg-docker-alpine/blob/master/package.json">source</a>]</figcaption></figure><h4>Next.js file path problem</h4><p>Second, the pkg puts packaged files in their own <a href="https://github.com/zeit/pkg#snapshot-filesystem">snapshot filesystem</a> prefixed with /snapshot/ during the process. You may encounter some path related problems at runtime, we need to customize the next server of Next.js:</p><pre><em>// </em><a href="https://github.com/evenchange4/nextjs-pkg-docker-alpine/blob/master/server.js"><em>server.js</em></a></pre><pre>const next = require(&#39;next&#39;);<br>const nextConfig = require(&#39;./next.config&#39;);</pre><pre>const app = next({ dev, dir: <strong>__dirname</strong>, conf: <strong>nextConfig</strong> });</pre><p>Then, run the command to package the entire application into an executable. You can also specify the target machine environments with --targets option and output path via --out-path as well.</p><pre>$ pkg . <strong>--targets</strong> node9-alpine-x64 <strong>--out-path</strong> pkg</pre><pre># output to pkg/binary</pre><h4>Trick of reducing binary size for Next.js project</h4><p>The original binary file is 85.4MB. The main bottleneck is that <em>pkg</em> package unnecessary files.</p><p>One is document-like files. We can introduce <a href="https://yarnpkg.com/en/docs/cli/autoclean">.yarnclean</a> to remove them from <em>node_modules </em>(-0.2MB). The other is that Next.js treat devDependencies as dependencies. However, we don’t really need them for production usage (e.g. webpack):</p><pre>$ yarn autoclean --force<br>$ rm -rf node_modules/webpack node_modules/webpack-dev-middleware node_modules/webpack-hot-middleware</pre><p>The final size of binary file is about 66MB.</p><h4>Docker multi-stage builds</h4><p>Docker could be a popular solution to deliver the application with machine environments. Customers can directly use docker-compose or Kubernetes to manage container services.</p><p>To make the docker image even smaller, we use Docker multi-stage builds (requiring Docker 17.05 or higher) to reduce layers and remove useless files. Here, we copy the single binary artifact made by pkg from first stage to the second stage.</p><p>In the first stage named as “builder” stage, it cotains a regular npm flow. We use mhart/alpine-node as base image and copy all source codes into the container. Do the processes of installing <em>node_modules</em>, building Next.js and packaging into binary:</p><pre><em>// Dockerfile - stage 1</em></pre><pre>FROM mhart/alpine-node AS <strong>builder</strong><br>WORKDIR /app<br>COPY . .<br>RUN yarn install<br>RUN yarn run build <br>RUN yarn run pkg</pre><p>In the second stage, we copy one single executable file from the first <em>builder</em> stage. We adopt the Alpine image alpine which is the smallest Linux image. Just to remind, you might need to install some missing <em>apk</em> libraries (e.g. libstdc++):</p><pre><em>// Dockerfile - stage 2</em></pre><pre>FROM alpine<br>RUN apk update &amp;&amp; \<br>  apk add --no-cache libstdc++ libgcc ca-certificates &amp;&amp; \<br>  rm -rf /var/cache/apk/*</pre><pre>WORKDIR /app<br>COPY --from=builder /app/pkg .<br>CMD ./next-app</pre><p>Finally, build the docker image and run with predefined environment variables:</p><pre>$ docker build -t app .<br>$ docker run --rm -it -p 3003:3003 -e &quot;PORT=3003&quot; app</pre><h4>Deploy to Now.sh</h4><p>We now deliver the docker image to customers without source code. It is also possible to verify it by deploying the docker service to any PaaS. Now.sh supports Docker multi-stage builds in OSS plan. All we need is one command:</p><pre>$ now --docker</pre><p>That’s it. Awesome zeit! <a href="https://nextjs-pkg-docker-alpine.now.sh">https://nextjs-pkg-docker-alpine.now.sh</a></p><h3>Conclusion</h3><p>In this article we take a look at an example of delivering a commercial Next.js application with pkg and docker. It is quite convenient to deploy it to <em>Now.sh </em>cloud service. Technically, this approach should work with every Node.js project.</p><p>The full source code is available on GitHub <a href="https://github.com/evenchange4/nextjs-pkg-docker-alpine">evenchange4/nextjs-pkg-docker-alpine</a> repository.</p><p><a href="https://github.com/evenchange4/nextjs-pkg-docker-alpine">evenchange4/nextjs-pkg-docker-alpine</a></p><h4>Further Readings</h4><ol><li><a href="https://medium.com/@evenchange4/make-your-react-service-portable-b58680688f6a">Make Your React Service Portable</a></li><li><a href="https://medium.com/@evenchange4/microservice-產品交付-9f2954c7167d">Microservice 產品交付</a></li><li><a href="https://medium.com/@evenchange4/2018-graphql-漸進式導入的架構-aeb2603f2223">2018 GraphQL 漸進式導入的架構</a></li><li><a href="https://medium.com/@evenchange4/五分鐘-kubernetes-有感-e51f093cb10b">五分鐘 Kubernetes 有感</a></li></ol><h4>References</h4><ol><li><a href="https://github.com/zeit/next.js/blob/canary/examples/with-pkg/README.md">https://github.com/zeit/next.js/blob/canary/examples/with-pkg/README.md</a></li><li><a href="https://github.com/zeit/next.js/blob/canary/examples/with-docker/README.md">https://github.com/zeit/next.js/blob/canary/examples/with-docker/README.md</a></li></ol><p><em>Thanks to </em><a href="https://medium.com/u/a8a021c40b3"><em>Lynn Lin</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5c73d4af2ee" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Make Your React Service Portable]]></title>
            <link>https://medium.com/@evenchange4/make-your-react-service-portable-b58680688f6a?source=rss-aa88256220c4------2</link>
            <guid isPermaLink="false">https://medium.com/p/b58680688f6a</guid>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[docker]]></category>
            <dc:creator><![CDATA[Michael Hsu]]></dc:creator>
            <pubDate>Sat, 05 May 2018 16:18:24 GMT</pubDate>
            <atom:updated>2018-05-05T16:20:43.410Z</atom:updated>
            <content:encoded><![CDATA[<h4>Dockerize Next.js 打造一包通吃的 Microservice</h4><blockquote>TL;DR 直接看 Next.js <a href="https://github.com/zeit/next.js/blob/canary/examples/with-docker/README.md">with-docker</a> 範例，簡單地搭配 Docker 來設定環境參數。</blockquote><p>在服務開發與營運的過程中，時常碰到要將程式部署到不同的機器環境上。簡化的流程會是從本機端開發後，部署到 Staging 環境經由 End-to-End 測試，確認沒問題才正式上到 Production 機器，而其中<strong>環境參數的配置與管理</strong>是整個流程中不可忽略的要點。</p><p>若純為伺服器端的服務，不外乎讀取環境變數或 .env 來決定這個服務所需的參數。然而，在網頁端的做法稍有不同，尤其是 React App 多了一道手續，經由 Building Tools 打包，才輸出優化後 JavaScript/Static Bundle 檔案。這篇主要探討『前端專案設定環境變數』之做法，大致上有三種：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*sIZrmHDHTtq7InMJX0OBGw.png" /><figcaption>三種設定環境變數之做法比較表</figcaption></figure><h3>a. Client side runtime</h3><p>可以在程式中針對各個環境寫一串邏輯，來決定當下應採用的配置。因此當瀏覽器載入頁面時，JavaScript Runtime 會拿 Domain Name 作為判斷依據：</p><pre><em>// config.js<br></em>if (/staging\.com/.test(<strong>window.location.host</strong>)) {<br>  OAUTH_DOMAIN = &#39;oauth.staging.com&#39;;<br>  S3_URL = &#39;staging.s3.amazonaws.com&#39;;<br>  PUBLIC_TOKEN = &#39;PUBLIC_TOKEN&#39;;<br>} else if (/production\.com/.test(window.location.host)) {<br>  OAUTH_DOMAIN = &#39;oauth.production.com&#39;;<br>  S3_URL = &#39;production.s3.amazonaws.com&#39;;<br>  PUBLIC_TOKEN = &#39;PUBLIC_TOKEN&#39;;<br>}</pre><p>這樣的做法很簡單，但缺點可想而知，所有機器的參數值都會被揭露給使用者，而且在 Runtime 使用 IF/ELSE 會<strong>影響效能</strong>，所以是三種做法中最不推薦的。</p><h3>b. Build time — Webpack</h3><p>在前端程式打包 Bundle 的流程中，會使用諸如 Babel Plugin 或 Webpack DefinePlugin 來處理環境變數設定的問題，最後還會跑過 Dead-code Elimination 的機制，所以不會揭露多餘的參數。Building Tools 的設定與程式邏輯大概為：</p><pre><em>// webpack.js</em><br>new webpack.DefinePlugin({<br>  &#39;process.env.APP_ENV&#39;: <strong>process.env.APP_ENV</strong>,<br>}),</pre><pre><em>// config.js<br>// Dead code will be removed</em><br>if (process.env.APP_ENV === &#39;staging&#39;) {<br>  OAUTH_DOMAIN = &#39;oauth.staging.com&#39;;<br>  S3_URL = &#39;staging.s3.amazonaws.com&#39;;<br>  PUBLIC_TOKEN = &#39;PUBLIC_TOKEN&#39;;<br>}<br>if (process.env.APP_ENV === &#39;production&#39;) {<br>  OAUTH_DOMAIN = &#39;oauth.production.com&#39;;<br>  S3_URL = &#39;production.s3.amazonaws.com&#39;;<br>  PUBLIC_TOKEN = &#39;PUBLIC_TOKEN&#39;;<br>}</pre><p>接者在 Build 時，透過 APP_ENV 指定你要的環境：</p><pre>$ <strong>APP_ENV=staging</strong> npm run build<br>$ APP_ENV=production npm run build</pre><p>這樣的做法還蠻常見的，只要在 CI 上給與 APP_ENV 值，即可套用正確的配置，也不會有方法 a 提到的效能問題。</p><p>缺點是如果你有五台不同的環境，就要 Build 出五包 Bundles，若 CI 沒有平行執行 Jobs 的能力，Build Time 會是倍數成長，也就是利用<strong>時間換取效能</strong>的做法。</p><h3>c. Docker container — runtime inject</h3><p>有一種商業模式是將軟體授權客戶，讓他們自行去營運，我們不是把整包 Source Code 給客戶，而是只交付打包後的前端 Bundles。而客戶在 Runtime 時才會決定他們的環境參數，因此我們的目標是讓同一份 React App <strong>在任何環境都能執行</strong>，上述 a、b 兩種方法就不夠彈性。</p><p>這個解法需要有一台 Web Server，在 HTML File Response 時<strong>注入到 Global Variable</strong>，通常就是放在 window 底下，使得瀏覽器端也可以取用參數：</p><pre><em>// template.html</em><br>&lt;script&gt;<br>  window.OAUTH_DOMAIN = &#39;oauth.staging.com&#39;;<br>  window.S3_URL = &#39;staging.s3.amazonaws.com&#39;;<br>  window.PUBLIC_TOKEN = &#39;PUBLIC_TOKEN&#39;;<br>&lt;/script&gt;</pre><p>方法 c 的缺點是一般的 Static HTML 網頁架構就不適用了。</p><h4>Next.js 的解法</h4><p>筆者最近的專案使用 Next.js 做為 SSR 架構，這邊舉例他們提出的解法，其實也是把參數<a href="https://github.com/zeit/next.js/blob/canary/lib/runtime-config.js#L1">存在 Global</a>，然後在 Next.js 啟動時注入，而官方開出的 API 使用起來大概會是這樣：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mvrSmx7lGi0432hCLhK3nw.png" /><figcaption>Next.js <a href="https://github.com/zeit/next.js#exposing-configuration-to-the-server--client-side">Exposing configuration to the server / client side</a></figcaption></figure><p>另外，現今的較為流行的封裝方式是 Docker，連同執行環境一並打包：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y52bLQD0GF2Hy7KRVcBtvQ.png" /><figcaption>Next.js Dockerfile</figcaption></figure><p>我們交付前預先跑 docker build，在客戶取得建置後的 Docker Image 後，即可 Runtime 設定 Docker Container 的參數：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*91JXjNfzHzkGG3UMgAjCgA.png" /><figcaption>Docker 建置以及啟動指令</figcaption></figure><blockquote>有興趣的話可以參考我最近貢獻的 <a href="https://github.com/zeit/next.js/blob/canary/examples/with-docker/README.md">with-docker</a> 範例。</blockquote><h4>比較參數儲存位置</h4><p>方法 a 將配置的參數存放在程式邏輯中；方法 b 雖然可以在 Building Tools 中設定，但 a、b 兩種其實都是存在專案的 Repository；而方法 c <strong>不再將環境變數放在專案內</strong>，反而應該使用 Docker-compose 或是 K8S 去統一管理。</p><h3>Conclusion</h3><p>這篇文章整理三種常見的前端專案參數設定方式，在經由比較後，方法 a 會暴露不該揭露的資訊也會影響效能；方法 b 是很常見的做法，但是建置時間過長且不 Portable；在考慮商業模式後，方法 c 可能是最為優雅並符合 Microservice 的概念了。</p><p>這些提到的做法都有在筆者的專案中使用，其實也不是什麼特別嚴重的問題。如果你喜歡這類的文章，可以點個拍手 👏 讓我知道，也歡迎訂閱 Medium 喔！</p><h4>Further Readings</h4><ol><li><a href="https://medium.com/@evenchange4/microservice-產品交付-9f2954c7167d">Microservice 產品交付</a></li><li><a href="https://medium.com/@evenchange4/2018-graphql-漸進式導入的架構-aeb2603f2223">2018 GraphQL 漸進式導入的架構</a></li><li><a href="https://medium.com/@evenchange4/graphql-microservice-with-apollo-df4ae25a6d43">GraphQL Microservice with Apollo</a></li><li><a href="https://medium.com/@evenchange4/五分鐘-kubernetes-有感-e51f093cb10b">五分鐘 Kubernetes 有感</a></li></ol><h4>References</h4><ol><li><a href="https://github.com/zeit/next.js/blob/canary/examples/with-universal-configuration-runtime/README.md">https://github.com/zeit/next.js/blob/canary/examples/with-universal-configuration-runtime/README.md</a></li><li><a href="https://github.com/zeit/next.js/blob/canary/examples/with-docker/README.md">https://github.com/zeit/next.js/blob/canary/examples/with-docker/README.md</a></li></ol><p><em>Thanks to </em><a href="https://medium.com/u/3472da9830f6"><em>Te-Chi Liu</em></a><em> and </em><a href="https://medium.com/u/80944c4fdcd0"><em>Daniel Tseng</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b58680688f6a" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[2018 GraphQL 漸進式導入的架構]]></title>
            <link>https://medium.com/@evenchange4/2018-graphql-%E6%BC%B8%E9%80%B2%E5%BC%8F%E5%B0%8E%E5%85%A5%E7%9A%84%E6%9E%B6%E6%A7%8B-aeb2603f2223?source=rss-aa88256220c4------2</link>
            <guid isPermaLink="false">https://medium.com/p/aeb2603f2223</guid>
            <category><![CDATA[apollo]]></category>
            <category><![CDATA[apollostack]]></category>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[graphcool]]></category>
            <dc:creator><![CDATA[Michael Hsu]]></dc:creator>
            <pubDate>Tue, 19 Dec 2017 08:57:37 GMT</pubDate>
            <atom:updated>2017-12-29T01:53:25.314Z</atom:updated>
            <content:encoded><![CDATA[<h4>GraphQL API Gateway with Schema Stitching</h4><p>GraphQL 出來幾年熱度依然不減，從今年數場演講與文章中，看得出越來越成熟的趨勢，筆者有在 Side Project 先試用看看覺得挺不錯的，直到近期公司新專案總算開始準備導入 GraphQL，這篇文章主要整理了置底 Reference 段落列出的內容，來看看 GraphQL 可能有哪些架構方法，建議有時間可以自行詳讀一遍。</p><p>雖然說是新專案，仍然是建立在 Legacy System 之上，會使用到舊有的 REST API，因此需要分別從 Client Side 與 Server Side 的角度來探討，怎麼樣的架構適合漸進式的導入 GraphQL。</p><h3>Client Side</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/800/1*jvr8kVeiWkC69xEHV4JHzQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Q7DlcZRO6MYnDZK_GnJdBw.png" /><figcaption>REST vs. GraphQL moves data requirements to the client side. (<a href="https://dev-blog.apollodata.com/the-graphql-stack-how-everything-fits-together-35f8bf34f841">Image credit</a>, <a href="https://dev-blog.apollodata.com/graphql-vs-rest-5d425123e34b">Image credit</a>)</figcaption></figure><p>不免俗套先從 REST 的缺點來探討，現階段我們維運三年前開發的 MCS (<a href="https://mcs.mediatek.com/">MediaTek Cloud Sandbox</a>) 中是全面採用 REST 的架構，要瀏覽 Detail 頁面至少打 7 支 Resource API，可以想像頁面的負擔相當大，在上圖 GraphQL 的設計理念中，透過將 Data Requirements 往前端搬移，讓頁面所需的資訊盡量能在一個 Request 內完成，來優化網路的傳輸問題。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yauOdsdLAL37sQD6c_mGPQ.png" /><figcaption>Apollo Client 2.0</figcaption></figure><p>前端今年最熱門的狀態管理肯定非 Redux 莫屬，一旦要轉換為 GraphQL 的架構，可以挑選由 GraphQL 為出發點設計的 Relay 或 Apollo 框架，筆者比較偏好社群活躍度高的 Apollo Client 2.0，社群導向也相較有趣很多，另外 Apollo 背後用 Observable 實作相當不錯。</p><h3>a. REST + GraphQL Hybrid</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*EyzWD7nroBZS3PuK3f4Y4Q.png" /></figure><p>如果我們直接建立一個新的 GraphQL Server，會造成前端維護的困難，因為要同時處理 REST 與 GraphQL 的結構。為了提供彈性的調整空間，Apollo Client 2.0 抽象出的 Apollo Link 可以解決此類問題，其中 Apollo Link REST (<a href="https://github.com/apollographql/apollo-link-rest">apollo-link-rest</a> or <a href="https://www.npmjs.com/package/@n1ru4l/apollo-link-rest">@n1ru4l/apollo-link-rest</a>) 的擴充，讓你在定義 Schema 時可以使用 @rest Directive 整合額外用到的 REST API，而其背後會自動幫你轉換為 REST API 送出，Query 寫起來像是：</p><pre><em>// example: </em><a href="https://github.com/sabativi/apollo-link-rest/blob/example/examples/simple/src/Person.js"><em>GitHub</em></a><em><br></em>query userProfile {<br>  person <strong>@rest</strong>(type: &quot;Person&quot;, path: &quot;people/1/&quot;) {<br>    name<br>  }<br>}</pre><p>透過 Hybrid 的方式間接導入 GraphQL，頁面上新的功能用 GraphQL，部分也可以使用既有的 REST API，但是問題只解決了一半，還是會有上圖發出多個 Requests 的問題，官方套件也特別說明現階段還在開發中，尚未實作完 Mutation 的操作，所以暫時還不是一個很成熟的解法。</p><h3>b. GraphQL Layer</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ymz7hIKfbTNUtHG9_t-NYw.png" /></figure><p>我們也可以把 GraphQL Server 抽出來放到中間當作 GraphQL Layer，除了可以用來封裝背後任何既有 API Service 外，新的功能也能直接與 Database 溝通，並且將邏輯實作在這台 GraphQL Server 上，最後只提供一個 GraphQL Endpoint 給前端使用。</p><h3>c. GraphQL API Gateway</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*T_7JfkvojOWVqbwFruA1yA.png" /><figcaption>API Gateway delegate to GraphQL Native</figcaption></figure><blockquote>New features will be built in a GraphQL Native way, and combined into the existing GraphQL API using schema stitching. — <a href="https://blog.graph.cool/graphql-api-gateway-graphql-native-1e46e4f179f7">GraphQL API Gateway &amp; GraphQL Native</a></blockquote><p>筆者認為這是目前看到算是 Best Practice 的架構，中間多了一層 API Gateway 的設計，這台 GraphQL Gateway 不直接與 Database 溝通，而是把前端的 Requests 需求往後面的 API Service 送。而後面直接與 Database 串接的 GraphQL Server 稱為 GraphQL Native。</p><h4>Schema Stitching &amp; Delegate</h4><p>後面的 API Service 不論你要用 REST 實作，又或是直接使用 GraphQL 更好，能透過 Introspection Query 把 GraphQL Native 定義的 Schema 存放到 Gateway 機器上，一旦 API Gateway 收到 Request 就 Delegate 到 GraphQL Native Service 去，甚至是可以透過 Schema Stitching 的設定，將多個 GraphQL Microservice 合併成一個。</p><h4>Caching &amp; Error Tracing</h4><p>API Gateway 除了可以保有現有的架構外，可以在這個層級處理棘手的 Caching 以及 Tracing 問題，因此 Apollo 也提供 Engine 服務來幫你做掉這塊，詳情可以參考：</p><p><a href="https://dev-blog.apollodata.com/introducing-apollo-engine-insights-error-reporting-and-caching-for-graphql-6a55147f63fc">Introducing Apollo Engine: insights, error reporting and caching for GraphQL</a></p><h3>Conclusion</h3><p>這篇文章簡單整理了幾種可能的架構方法，新專案還沒有正式的啟動，算是 2018 的新年許願，希望能夠正式導入 GraphQL，也透過這次的 Survey 看到不同的設計理念。最後一個 Gateway 架構雖然是相較最彈性的，但也伴隨系統的複雜化，最後會評估團隊人力狀況做出不同的決定，如果對後續筆者專案採用的架構有興趣，歡迎關注並訂閱 Medium 喔！</p><h4>Further Readings</h4><ol><li><a href="https://medium.com/@evenchange4/graphql-microservice-with-apollo-df4ae25a6d43">GraphQL Microservice with Apollo — 使用 GraphQL 獲取 Medium 最新文章列表</a></li></ol><h4>References</h4><ol><li><a href="https://blog.graph.cool/how-do-graphql-remote-schemas-work-7118237c89d7">How do GraphQL remote schemas work? — Understanding GraphQL schema stitching (Part I)</a></li><li><a href="https://blog.graph.cool/graphql-schema-stitching-explained-schema-delegation-4c6caf468405">GraphQL Schema Stitching explained: Schema Delegation — Understanding GraphQL schema stitching (Part II)</a></li><li><a href="https://www.apollographql.com/docs/graphql-tools/schema-stitching.html">Apollographql Docs</a></li><li><a href="https://dev-blog.apollodata.com/graphql-schema-stitching-8af23354ac37">GraphQL schema stitching</a> / <a href="https://github.com/stubailo/schema-stitching-demo">GitHub</a></li><li><a href="https://dev-blog.apollodata.com/the-graphql-stack-how-everything-fits-together-35f8bf34f841">The GraphQL stack: How everything fits together</a></li><li><a href="https://dev-blog.apollodata.com/graphql-vs-rest-5d425123e34b">GraphQL vs. REST</a></li><li><a href="https://www.howtographql.com/basics/3-big-picture/">How to GraphQL? — Big Picture (Architecture)</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=aeb2603f2223" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Chrome Headless — Puppeteer 小妙用]]></title>
            <link>https://medium.com/@evenchange4/chrome-headless-puppeteer-%E5%B0%8F%E5%A6%99%E7%94%A8-9ac2c3b4e761?source=rss-aa88256220c4------2</link>
            <guid isPermaLink="false">https://medium.com/p/9ac2c3b4e761</guid>
            <category><![CDATA[chrome]]></category>
            <category><![CDATA[puppeteer]]></category>
            <category><![CDATA[javascript]]></category>
            <dc:creator><![CDATA[Michael Hsu]]></dc:creator>
            <pubDate>Sat, 18 Nov 2017 12:02:50 GMT</pubDate>
            <atom:updated>2017-11-18T12:02:50.014Z</atom:updated>
            <content:encoded><![CDATA[<h4>以三個案例來探討應用</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O5lJoGNhJ3oKmirQ1hUD1w.png" /></figure><p>若要模擬網頁操作，以往多半會採用 Selenium 等工具進行開發，無論安裝速度或啟動時間實在是有點緩慢，現在有了 Chrome Headless 讓整體流程順暢許多。本篇就由 Node.js 封裝的套件 Puppeteer，以及近期接觸的三個案例，快速簡介有什麼樣的應用。</p><blockquote><a href="https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md">Headless Chrome</a> is shipping in Chrome 59. It’s a way to run the Chrome browser in a headless environment. Essentially, running Chrome without chrome! It brings <strong>all modern web platform features</strong> provided by Chromium and the Blink rendering engine to the command line. — <a href="https://developers.google.com/web/updates/2017/04/headless-chrome">Getting Started with Headless Chrome.</a></blockquote><h3>#1. End-to-end testing with Jest in Travis CI</h3><p>前陣子做了個 <a href="https://github.com/evenchange4/gh-polls-bot">GitHub Polls App</a>，可以讓你在 Issue 中使用 /polls 指令來額外加載一個現成的投票系統 <a href="https://github.com/apex/gh-polls">Gh-Polls (by TJ</a>)。做的事情其實很簡單，當 Issue 新增或修改時，透過 Webhook 將所下達的指令轉換成 Markdown 取代原有的 Content：</p><h3>Probot on Twitter</h3><p>Create polls in your discussions on GitHub with `/polls` thanks to @evenchange4 https://t.co/WvrkdxJT2v https://t.co/paAD1AOLbT</p><p>雖然只是個 Side Project，但我還是想做點簡單的測試，確保系統是穩定的運作，因此我嘗試了使用 Puppeteer 與 Jest 做整合，寫一點簡易的 Script 走過 <strong>登入、新增 Issue、Content Assertion、關閉 Issue</strong>，並且搭配 Async/Await 的語法讓整個流程更為直覺，測試大致會像是這樣：</p><pre><em>// jest.test.js</em></pre><pre>it(&#39;should create a Polls label&#39;, <strong>async</strong> () =&gt; {<br><em>  // Login<br>  </em><strong>await</strong> page.goto(&#39;https://github.com/login&#39;)<br>  <strong>await</strong> page.type(&#39;input#login_field&#39;, ACCOUNT)<br>  <br><em>  // ...</em></pre><pre><em>  </em>const labelTitle = <strong>await</strong> page.$eval(&#39;#selector&#39;, el =&gt; el.title)<br>  expect(labelTitle).toBe(&#39;Polls&#39;)<br>})</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/796/1*JwZEi02hFmgre4mHV2Jo_A.gif" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nvJ98uYKcFVcfCcQ-DW0aQ.png" /><figcaption>Daily E2E testing in Travis Cron Jobs [<a href="https://travis-ci.org/evenchange4/gh-polls-bot-test/builds">Link</a>]</figcaption></figure><p>上左動畫圖演示的是平常打開 Slow motion 的開發模式，啟動時會跳出藍色 Icon 的 Chrome 視窗，並且放慢操作速度。另外，也可以設定 Headless 參數讓測試跑在 CI 中，因為不需要啟動 Chrome GUI，整合 Traivs Cron Jobs 每天跑一次，每次只需花費一分鐘。</p><pre>puppeteer.launch({<br>  headless: true,<em> // default<br>  </em>slowMo: 0,<em>      // default <br></em>});</pre><p>至於 Jest 與 Puppeteer 的整合安裝與配置建議可以參考文章：</p><p><a href="https://ropig.com/blog/end-end-tests-dont-suck-puppeteer/">End-to-end Tests that Don&#39;t Suck with Puppeteer - Ropig</a></p><h3>#2. Website crawler as an API service</h3><p>如果有爬蟲的需求也可以透過 Puppeteer 來處理，雖然 Node.js API 已經可以很簡單的一步驟一步驟撰寫 Scripts，但是每次寫起來也是蠻煩瑣的，因此我做了一個 Microservice 讓你可以透過 URL 定義 Action query（Click-and-wait）來操作網頁，最後只返回你感興趣的資訊，蠻適合拿來處理 SPA 類型網站，因為其實就是一個簡單的 Pre-render Service。</p><p>這個專案封裝了 Docker Image 並且 Deploy 到 Now.sh，因此直接在 Container 裡面執行 Chrome Headeless 也是沒有問題的！只是要注意可能會碰到 <a href="https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#chrome-headless-fails-due-to-sandbox-issues">Linux Sandbox</a> 的問題，記得要多一些設定：</p><pre>puppeteer.launch({<br>  args: [&#39;<strong>--no-sandbox</strong>&#39;], <em>// for docker<br></em>});</pre><figure><a href="https://micro-website-api.now.sh"><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*p446paXxGVE_EAf3qZ8RFA.png" /></a><figcaption>An API microservice that crawls dynamic website. [<a href="https://github.com/evenchange4/micro-website-api">GitHub</a>]</figcaption></figure><h3>#3. SPA pre-render tools</h3><p>我之前在『<a href="https://medium.com/@evenchange4/react-stack-開發體驗與優化策略-b056da2fa0aa">React Stack 開發體驗與優化策略</a>』中簡介 CRA（Create-react-app）搭配基於 jsdom 的 React-snapshot 來進行 Pre-render 來優化 SEO。但其實 Pre-render 還有很多實作可以參考，例如也有人用 Puppeteer 做了相似的套件 <a href="https://github.com/stereobooster/react-snap">React-snap</a>。有興趣可以直接去看看最近轉換過去的 <a href="https://github.com/MCS-Lite/mcs-lite/pull/478">PR</a>，這邊就不再做詳細說明了。</p><h3>Conclusion</h3><p>Puppeteer 對於 Javascript 開發者非常的方便，但畢竟是 Chrome 所封裝的工具，如果你要做跨瀏覽器的測試就沒辦法了，不過因為速度上很有感還蠻值得一試的！現階段也將公司的專案導入 Puppeteer 作為 E2E 的測試工具，如果之後有更近一步的研究心得，也會整理並分享出來，有興趣歡迎訂閱我的 Medium 文章喔！</p><h4>References</h4><ol><li><a href="https://ropig.com/blog/end-end-tests-dont-suck-puppeteer/">End-to-end Tests that Don’t Suck with Puppeteer</a></li><li><a href="https://medium.com/@evenchange4/react-stack-開發體驗與優化策略-b056da2fa0aa">React Stack 開發體驗與優化策略</a></li></ol><p><em>＊如果你喜歡這篇文章，別忘了可以點個 👏 👏 👏 讓我知道喔！</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9ac2c3b4e761" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[五分鐘 Kubernetes 有感]]></title>
            <link>https://medium.com/@evenchange4/%E4%BA%94%E5%88%86%E9%90%98-kubernetes-%E6%9C%89%E6%84%9F-e51f093cb10b?source=rss-aa88256220c4------2</link>
            <guid isPermaLink="false">https://medium.com/p/e51f093cb10b</guid>
            <category><![CDATA[k8s]]></category>
            <category><![CDATA[helm]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[kubernetes]]></category>
            <dc:creator><![CDATA[Michael Hsu]]></dc:creator>
            <pubDate>Mon, 25 Sep 2017 07:36:39 GMT</pubDate>
            <atom:updated>2017-09-26T09:03:23.906Z</atom:updated>
            <content:encoded><![CDATA[<h4>開發工程師也懂的系統架構</h4><p>下半年最有趣莫過於 K8S 的認識，台灣社群也蹦出好多相關的活動，除了 K8S day 之外也有 <a href="https://www.facebook.com/events/132683897356185/">Kubernetes 101 — 半小時理解 Pokemon 背後的開源容器技術</a> 一連好幾天有大神分享的活動，激起我想要了解架構層的興趣，本篇主要是學習過程紀錄。</p><h3>Kubernetes</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SlO0L1NDF85nnXoWj5SlvQ.png" /><figcaption>Learn the Kubernetes Key Concepts in 10 Minutes (<a href="http://omerio.com/2015/12/18/learn-the-kubernetes-key-concepts-in-10-minutes/">Link</a>)</figcaption></figure><p>左架構圖雖然乍看複雜，但其實中只要認識其中幾個基本的元件：</p><ol><li><em>Cluster</em>：由紫色 Master 來管理底下橘色的 Node。</li><li><em>Pod</em>：對 Container 再一次封裝，之後如果要 Scale，就會自動新增刪減 Pod，是 Immutable deploy 的最小單位。</li><li><em>Service</em>：Pod 運行後需要透過定義 Service 才能讓外部的使用者訪問。</li></ol><h4>Install</h4><p>學習上最快速方便的方法是，直接在本機端安裝單機版 <strong>Minikube</strong> Cluster ，因其安裝在 VituralBox 內任你盡情把玩。安裝 <strong>kubectl</strong> 工具稍後能進行簡易的 K8S API 操作。接著，透過 <em>start</em> 將 Cluster 於背景運行就能看到 Dashboard 介面：</p><pre># Install virtualbox <a href="https://www.virtualbox.org/wiki/Downloads">https://www.virtualbox.org/wiki/Downloads</a><br>$ brew cask install minikube<br>$ brew install kubectl</pre><pre>$ minikube start<br>$ minikube dashboard</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hpyuyI8OJzmnDrl6Dho0Mg.png" /><figcaption>K8S Dashboard GUI</figcaption></figure><h4>部署 Docker Image</h4><p>可以選擇從 Dockerhub 下載 Image 到你的 Cluster 中，這邊使用筆者一個 <a href="https://hub.docker.com/r/evenchange4/micro-medium-api/">Side Project Docker Image</a> 來示範，最後會看到 Deployment / Pod 列表多了個運行中的項目：</p><pre>$ kubectl run medium-api \<br>  --image=evenchange4/micro-medium-api:latest \<br>  --port=3000<br>$ kubectl get deployments<br>$ kubectl get pods -o wide</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uHqBBfo3yhL1JfniO74-UQ.png" /><figcaption>Run docker image</figcaption></figure><h4>揭露 Service</h4><p>Pod 中的 Container 雖然已經運行了，但外部的使用者並不能訪問到，因此接下來要 Expose Service。透過 Minikube 的指令能方便查詢 VitrualBox 的 IP 以及 Service ，最後會自動打開瀏覽器頁面，下右側圖即為 Side Project 的頁面，能夠透過 GraphQL 查詢 Medium 的文章列表，有興趣可以參考 <a href="https://medium.com/@evenchange4/graphql-microservice-with-apollo-df4ae25a6d43">GraphQL Microservice with Apollo</a>：</p><pre>$ kubectl expose deployment medium-api --type=LoadBalancer<br>$ kubectl get service<br>$ minikube service medium-api</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xu8oAUWUlqEUeS0w72wWLg.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*m9Sb2u0YTuP4GSPc9vtwFA.png" /><figcaption>Expose service</figcaption></figure><h3>Scale</h3><p>雖然單機版環境只有一個 Node，依然可以模擬如果要 Scale 成兩個單位的 Pod，可以透過 replicas 參數來配置。一旦設定完畢，K8S 就會幫你自動新增一個 Pod，又或是自動把多餘的 Pod 關閉，因此不必自己擔心 Pod 的 Lifecycle：</p><pre>$ kubectl scale deployment/medium-api --replicas=2<br>$ kubectl get pods -o wide</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*yAiC1Rsw31tN9uwUVX013w.png" /><figcaption>Scale Pods</figcaption></figure><h4>Rolling 版本更新</h4><p>Container 是基於 Docker Image 的 Tag 做控制，所以可以透過指定 Tag 來達到 Rollout 機制，例如這邊把原先吃 Latest Tag 降版為 2.1.0，也可以透過 <em>undo</em> 來達到 Rollback 效果：</p><pre>$ kubectl set image deployments/medium-api \<br>  medium-api=evenchange4/micro-medium-api:2.1.0<br>$ kubectl rollout status deployments/medium-api<br>$ kubectl rollout <strong>undo</strong> deployments/medium-api</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LZTCIBG07Fh9Bd79c5HLyg.png" /><figcaption>Rollout and Rollback</figcaption></figure><h4>其他指令</h4><p>當然還有其他很豐富的用法，例如追蹤 Log、到 Pod 內進行 Debug 等等，特別推薦 Oh-my-zsh 使用者安裝方便的 Autocompletion Plugin：</p><pre># Logs<br>$ kubectl logs --follow &lt;POD-NAME&gt;<br><br># Execute Commands<br>$ kubectl exec &lt;POD-NAME&gt; -it -- ls<br><br># Kill<br>$ kubectl delete service medium-api<br>$ kubectl delete deployment medium-api<br>$ minikube stop</pre><pre># zsh pulgin<br># plugins=(kubectl)</pre><h4>Cofiguration file</h4><p>到這邊你可能會覺得想要部署服務，還要透過上述的指令一一操作未免太麻煩，所以 kubectl 其實能使用定義好的 YAML 格式檔來執行指令。例如這邊先匯出之前操作的 Expose Service 動作，並嘗試重新使用 <em>service.yaml</em> 來建立 Service：</p><pre>$ kubectl get services medium-api -o yaml &gt; ./service.yaml<br>$ kubectl create -f ./service.yaml</pre><p>雖然可以透過單一 YAML 檔案來取代繁複的 Commands，但是 YAML 檔案要怎麼生出來呢？大部分情況是需要手動去進行撰寫配置的。緊接著又冒出另一個問題，如果我要更新一個新的版號，是不是就得產生一份新的 YAML 檔？</p><p>所以，我們需要透過 <strong>Helm</strong> 來幫忙把整個服務的配置 Pre-Cofiguration，並且把盡量把 YAML 檔案定義出 Template，如此一來就能重複地使用同一份設定配置了。</p><h3>Helm &amp; Chart</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/500/1*9mJCHusRXGq9PViqeajcZw.png" /><figcaption><a href="https://helm.sh/">https://helm.sh/</a></figcaption></figure><p>Helm 等同於前端開發常用的 Npm，只不過安裝的東西不是套件而是一個運行服務，而 K8S 這邊把整包服務稱作 <strong>Chart</strong>，事實上類似 package.json 定義出 Chart.yaml 來描述系統的架構。</p><h4>Install and setup</h4><p>Helm 主要分為 CLI Client 與 Tiller Server ，可以透過 <em>init</em> 指令會幫你在 Cluster 中快速地建立環境與配置：</p><pre>$ brew install kubernetes-helm<br>$ helm init</pre><h4>Use a Chart</h4><p>當然，我可以一鍵安裝別人提供的 Chart，例如我要在 Cluster 建置一個 Wordpress Chart 是什麼感覺呢？官方的 Chart Repository <a href="https://kubeapps.com/charts?q=wordpress">KubeApps</a> 有一個 Wordpress 的穩定版可以下載使用：</p><pre>$ helm install stable/wordpress</pre><p>打完下班。可以看到下左圖 Wordpress Chart 運行了兩個 Service，分別為 Mariadb 以及 Wordpress Server，右圖為跑起來的頁面。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*__Mm8FpPrREnDcFAJnLHsQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2sG2OgXOYce3iRA4ixC8Ig.png" /><figcaption>Install Wordpress Chart via Helm</figcaption></figure><p>但是多半時候可能想要設定一些參數做客製化，Wordpress Chart 就有提供後台相關的變數供設定，透過 <em>set</em> 參數來進行覆寫，又或是直接透過 <em>values.yaml</em> 來一次給定所有的參數，詳情可以參考 <a href="https://github.com/kubernetes/charts/tree/master/stable/wordpress#configuration">Wordpress Configuration</a>。</p><pre>$ helm install \<br>  --name my-release \<br>  --set wordpressUsername=admin \<br>  stable/wordpress</pre><pre># or<br>$ helm install \<br>  --name my-release \<br>  <strong>-f values.yaml</strong> \<br>  stable/wordpress</pre><h4>Define your Chart</h4><p>Helm 提供快速 <em>create</em> Chart 的指令， 並且可以透過 --dry-run --debug 來預先瀏覽產出的設定是不是你想要的，確認後才真正的執行安裝：</p><pre>$ helm create mcs-lite-chart<br>$ helm install . --dry-run --debug<br>$ helm install . --name mcs-lite-chart</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LnyHM9p-ObHB_ZpOpxuynA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8U0FwF9KLlBHydK6rpAB2A.png" /><figcaption>MCS Lite Chart 中定義的 Resouces</figcaption></figure><p>可以看到上右圖 Create Chart 指令所建立出來的資料夾，真的就是幾個 YAML File 所組成的，因此提供一個很好的格式化管理方式，也可以用 Helm Linter 檢測，複雜的系統肯定要花更多功夫設計這份 Template！</p><blockquote>如果你想要了解 Chart Template 的每個元件，建議可以跑過 <a href="https://hackernoon.com/the-missing-ci-cd-kubernetes-component-helm-package-manager-1fe002aac680">The missing CI/CD Kubernetes component: Helm package manager</a>，慢慢地一步驟一步驟跟著學習。</blockquote><h4>Deployment Version Control</h4><p>使用 Helm 管理 Chart 除了可以參數化 K8S 設定檔外，另外一個重點是版本控管。上版的時候可以用 <em>upgrade</em> 重新部署 Chart，有點類似 <em>kubectl rollout</em> 的機制，但是這邊是整個系統 Deployment 層級的版本控管，因此也可以隨時 <em>rollback </em>回到上一次的版本：</p><pre>$ helm ls<br>$ helm upgrade mcs-lite-app .<br>$ helm rollback mcs-lite-app 1</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YS_kk_2G4oBClWgSws8dow.png" /><figcaption>Helm Chart list</figcaption></figure><p>最後你可以自架 Helm Repository 提供你的 Chart.yaml，也可以透過 <em>package</em> 指令打包目前的 Chart 資料夾，進而交付給使用者去下載安裝：</p><pre>$ helm package . --debug -d ./charts<br>$ helm install ./charts/mcs-lite-chart-0.1.0.tgz</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nFrO9HMOt3nlnrdlnpJliA.png" /><figcaption>Pack helm chart (<a href="https://github.com/evenchange4/mcs-lite-chart">GitHub Link</a>)</figcaption></figure><h3>後記</h3><p>前陣子在玩 Zeit 的 Now.sh，發現跟 K8S 的指令非常相像，感覺背後應該也是 K8S 的技術。架構這種東西很燒腦也很好玩，如果流程建置的很完善，身為前端工程師平常應該是不會碰觸，但為了避免錯誤的想像，自己玩過會比較有感一點。我覺得<a href="http://omerio.com/2015/12/18/learn-the-kubernetes-key-concepts-in-10-minutes/">十分鐘系列的懶人版</a>對於想了解但不需要自己操作的人很有幫助，因此稍微紀錄一下順便與前端團隊分享。</p><h4>Further Readings</h4><ol><li><a href="https://medium.com/@evenchange4/microservice-產品交付-9f2954c7167d">Microservice 產品交付 — Dockerize 與 Zeit JavaScript 跨平台解決方案</a></li><li><a href="https://medium.com/@evenchange4/graphql-microservice-with-apollo-df4ae25a6d43">GraphQL Microservice with Apollo — 使用 GraphQL 獲取 Medium 最新文章列表</a></li></ol><h4>References</h4><ul><li><a href="https://hackernoon.com/the-missing-ci-cd-kubernetes-component-helm-package-manager-1fe002aac680">The missing CI/CD Kubernetes component: Helm package manager</a></li><li><a href="https://daemonza.github.io/2017/02/20/using-helm-to-deploy-to-kubernetes/">Using Helm to deploy to Kubernetes</a></li><li><a href="https://docs.helm.sh/using_helm/#quickstart-guide">Helm Quickstart Guide</a></li><li><a href="http://dockone.io/article/932">十分钟带你理解Kubernetes核心概</a></li><li><a href="https://kubernetes.io/docs/tutorials/kubernetes-basics/">Kubernetes basics</a> / <a href="https://kubernetes.io/docs/tutorials/stateless-application/hello-minikube/">Hello minikube</a></li><li><a href="https://github.com/kubernetes/charts/tree/master/stable/wordpress">https://github.com/kubernetes/charts/tree/master/stable/wordpress</a></li></ul><p><em>Thanks to </em><a href="https://medium.com/u/a8a021c40b3"><em>Lynn Lin</em></a><em>, </em><a href="https://medium.com/u/decec0d0baf"><em>施舜元</em></a><em>, </em><a href="https://medium.com/u/c5cc81699dc2"><em>李孟修</em></a><em> and </em><a href="https://medium.com/u/80944c4fdcd0"><em>Daniel Tseng</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e51f093cb10b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[GraphQL Microservice with Apollo]]></title>
            <link>https://medium.com/@evenchange4/graphql-microservice-with-apollo-df4ae25a6d43?source=rss-aa88256220c4------2</link>
            <guid isPermaLink="false">https://medium.com/p/df4ae25a6d43</guid>
            <category><![CDATA[zeit]]></category>
            <category><![CDATA[graphql]]></category>
            <category><![CDATA[microservices]]></category>
            <category><![CDATA[apollo]]></category>
            <dc:creator><![CDATA[Michael Hsu]]></dc:creator>
            <pubDate>Fri, 18 Aug 2017 09:44:52 GMT</pubDate>
            <atom:updated>2017-08-21T08:03:12.560Z</atom:updated>
            <content:encoded><![CDATA[<h4>使用 GraphQL 獲取 Medium 最新文章列表</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wCFATppZGtoiSKYUIxgwQw.png" /><figcaption>Microservice for fetching the latest posts of Medium with GraphQL. [<a href="https://github.com/evenchange4/micro-medium-api">Link</a>]</figcaption></figure><p>星期三參加了由優拓主辦的 <a href="https://graphqltaiwan.kktix.cc/events/meetup01"><strong>GraphQL Taiwan 社群小聚 #1</strong></a>，沒想到蠻多公司都有在使用，各種資源都蠻到位的，觀望很久也躍躍欲試，大大們都鋪了幾年的路了，還不上車嗎？現場跟其他工程師交流，也有跟 MediaTek 情況蠻類似的公司，專案由前後端分離的團隊個別負責，要導入 GraphQL 早已經不是技術上的問題，人力工作分配影響更廣。</p><p>這篇文章主要從前端角度驗證，能不能在既有系統下，Client Side、API Server 中間再多增加一層 GraphQL Server，而我 DEMO 上拿 Medium API 來使用，架構大致如下圖所示：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/906/1*_VhjLNj9YU9-UsgF59wHdQ.png" /><figcaption>GraphQL layer that integrates existing systems. (<a href="https://www.howtographql.com/basics/3-big-picture/">How to GraphQL</a>)</figcaption></figure><h3>Medium API</h3><p>用 Medium 撰寫文章很方便，但就是不知道為什麼官方都沒提供獲取文章列表的正常 API，目前有兩個方向：</p><ol><li>RSS XML：但 Parse XML 有一點麻煩，而且我只要要列表 Title 以及 URL 就好。範例： <a href="https://medium.com/feed/@evenchange4">https://medium.com/feed/@evenchange4</a>。</li><li>JSON String：其實是有返回 JSON 的隱藏 API，但應該是為了JSON Hijacking 安全性問題，開頭多塞了 ])}while(1);&lt;/x&gt; 字串，並且限制不支持 CORS 資源共享。但事實上文章列表本來就只要拿 Public 的，所以我在 Server Side 把格式做了簡單的轉換。範例：<a href="https://medium.com/@evenchange4/latest?format=json&amp;limit=10">https://medium.com/@evenchange4/latest?format=json&amp;limit=10</a>。</li></ol><h3>GraphQL Schema</h3><p>因為是第一次開發 GraphQL Server，問了其他開發者建議直接用 Facebook 的 Graphql-JS 來使用，寫起來像是一個個 Object 的定義，為了感受一下結構，挑了有名氣的<a href="https://github.com/cofacts/rumors-api">真的假的 Rumors-api Open Source</a> 來看，剛開始看眼花繚亂的，但因為我要做的 Side Project 其實非常的簡單，所以最後拿 Apollo 的 GraphQL-Tools 來定義 Schema，用起來簡潔多了。</p><p>GraphQL-Tools 只要定義好兩件事情：Type 與 Resolver。</p><h4>1. Type</h4><p>其實就是一個 String，用來表示 GraphQL 型別的語言。Medium 文章的定義如下 <em>typeDefs</em> 所示，其中還可以自己宣告 <em>type </em>或是用 <em>scalar</em> 來擴充基本型態：</p><pre>const typeDefs = `<br>  <strong>scalar</strong> Timestamp</pre><pre>  type Content {<br>    subtitle: String<br>  }</pre><pre>  type Post {<br>    id: ID!<br>    subtitle: String!<br>    content: Content!<br>    firstPublishedAt: Timestamp!<br>  }<br>`;</pre><h4>2. Resolver</h4><p>定義取值的函示，可以返回 Promise 或是 Value。實際 Query <em>posts</em> 的時候會向 Medium 打 API，接到返回的 Response 來做後續處理。又或是想把巢狀的資料扁平化，新增一個 <em>subtitle</em> 欄位，就是返回一個 Value：</p><pre>const resolvers = {<br>  Query: {<br>    <strong>posts</strong>: (obj, { username }, context) =&gt;<br>      API.getPost(username).then(...),<br>  },<br>  Post: {<br>    <strong>subtitle</strong>: R.path([&#39;content&#39;, &#39;subtitle&#39;]),<br>  },<br>};</pre><p>最後，透過 GraphQL-Tools <em>makeExecutableSchema</em> 來生成 <em>schema</em>。也因為GraphQL 是強型別，所以測試的時候可以把 Type 定義的資料都 Mock 掉，例如預設 String，測試時就會產生 &quot;Hello World&quot;</p><pre>const { makeExecutableSchema } = require(&#39;graphql-tools&#39;);<br>const schema = makeExecutableSchema({ typeDefs, resolvers });</pre><pre><em>// Mock for test</em><br>const { addMockFunctionsToSchema } = require(&#39;graphql-tools&#39;);<br>addMockFunctionsToSchema({ schema });</pre><h3>Apollo Server</h3><p>小聚議題上有提到，Apollo 維護了數種 Server Framework 的 Plugin，同樣的接口真的很方便。我挑選非常輕量的 Zeit Micro 來實作，因為範例實在太精簡了，<a href="https://github.com/apollographql/apollo-server/blob/master/packages/apollo-server-micro/README.md#example">文檔</a>一字不刪直接貼過來：</p><pre>import { microGraphiql, microGraphql } from &quot;graphql-server-micro&quot;;<br>import micro, { send } from &quot;micro&quot;;<br>import { get, post, router } from &quot;microrouter&quot;;<br>import schema from &quot;./schema&quot;;<br><br>const graphqlHandler = microGraphql({ <strong>schema</strong> });<br>const graphiqlHandler = microGraphiql({ endpointURL: &quot;/graphql&quot; });<br><br>const server = micro(<br>  router(<br>    get(&quot;/graphql&quot;, graphqlHandler),<br>    post(&quot;/graphql&quot;, graphqlHandler),<br>    get(&quot;/graphiql&quot;, graphiqlHandler),<br>    (req, res) =&gt; send(res, 404, &quot;not found&quot;),<br>  ),<br>);<br><br>server.listen(3000);</pre><p>其中 <em>schema</em> 即為上個段落最終生成的結果，這邊還做了另一件事是快速 Setup GraphiQL 作為開發使用。</p><h4>Per-request Memory Cache</h4><p>小聚最後一個議程 <a href="https://medium.com/u/80944c4fdcd0">Daniel Tseng</a> 介紹的 Dataloader，可以幫助 GraphQL 提升效能。設定上可以在 <em>microGraphql</em> 中進行傳遞，如此一來每個 Resolver 都能透過 <em>context</em> 來共享同一份 Dataloader Instance 來達到 Memory Cache 目的，記得把原本 Object 參數改為 Function 使用：</p><pre>const graphqlHandler = microGraphql(() =&gt; ({<br>  schema,</pre><pre>  <em>//  Per-request basis</em><br>  <strong>context</strong>: {<br>    loader: new DataLoader(...),<br>  },<br>}));</pre><pre>Query: {<br>  posts: (obj, { username }, context) =&gt;<br>    <strong>context</strong>.loader.load(username).then(...),<br>},</pre><h3>Fetch to Apollo-Fetch</h3><p>議程上也有提到 Apollo Client 的輕量套件，封裝的跟原本 Fetch 很像，使用起來很簡單，但比較複雜的前端架構下次可能就會試試看用 React-Apollo 來管理了：</p><pre>const uri = &#39;<a href="https://micro-medium-api.now.sh/graphql">https://micro-medium-api.now.sh/graphql</a>&#39;;<br>const apolloFetch = createApolloFetch({ uri });</pre><pre><strong>apolloFetch</strong>({<br>  query: `query PostQuery($username: String!, $limit: Int!){<br>    posts(username: $username, limit: $limit) {<br>      title<br>      firstPublishedAt<br>      url<br>      content {<br>        subtitle<br>      }<br>    }<br>  }`,<br>  variables: `{<br>    &quot;username&quot;: &quot;evenchange4&quot;,<br>    &quot;limit&quot;: 100<br>  }`,<br>})<br>.then({ data } =&gt; { this.setState(...) })<br>.catch(error =&gt; { ... });</pre><h3>後記</h3><p>直接打別人的 API 來封裝實在也不太好，所以後來還補上了 Rate-limit 的 Middleware 來限制每個 IP 一秒只能打一次 Request，有興趣可以去看看 GitHub Source Code。</p><p>很久沒寫後端 Server 了，因為 GraphQL 實在太紅，後端相關的 Framework/Library 都想試著玩玩看，一開始還搞不太清楚套件彼此間的關係。透過這樣一個 Side Project 來簡易的驗證 GraphQL Server 的可行性，希望有朝一日也能運用在公司專案上！</p><h4>Further Readings</h4><ol><li><a href="https://medium.com/@evenchange4/microservice-產品交付-9f2954c7167d">Microservice 產品交付 — Dockerize 與 Zeit JavaScript 跨平台解決方案</a></li><li><a href="https://medium.com/@evenchange4/使用-circleci-2-0-workflows-挑戰三倍速-9691e54b0ef0">使用 CircleCI 2.0 Workflows 挑戰三倍速</a></li></ol><h4>References</h4><ol><li><a href="https://www.slideshare.net/ssuserab25b41/graphql-taiwan-meetup-001-graphql-overview">GraphQL Taiwan Meetup 001 — GraphQL</a></li><li><a href="https://hackmd.io/p/Syk9o9xuZ#/">GraphQL Taiwan Meetup 001 — Dataloader</a></li></ol><p><em>＊完整的專案放在 </em><a href="https://github.com/evenchange4/micro-medium-api"><em>Evenchange4/micro-medium-api</em></a><em>，如果你喜歡這系列文章，關於 Michael 在 OSS 專案開發心得，別忘了可以點個 👏 讓我知道喔！</em></p><p><em>Thanks to </em><a href="https://medium.com/u/a8a021c40b3"><em>Lynn Lin</em></a><em> and </em><a href="https://medium.com/u/80944c4fdcd0"><em>Daniel Tseng</em></a><em>.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=df4ae25a6d43" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Microservice 產品交付]]></title>
            <link>https://medium.com/@evenchange4/microservice-%E7%94%A2%E5%93%81%E4%BA%A4%E4%BB%98-9f2954c7167d?source=rss-aa88256220c4------2</link>
            <guid isPermaLink="false">https://medium.com/p/9f2954c7167d</guid>
            <category><![CDATA[continuous-integration]]></category>
            <category><![CDATA[zeit]]></category>
            <category><![CDATA[travis-ci]]></category>
            <category><![CDATA[docker]]></category>
            <category><![CDATA[continuous-delivery]]></category>
            <dc:creator><![CDATA[Michael Hsu]]></dc:creator>
            <pubDate>Fri, 11 Aug 2017 03:15:24 GMT</pubDate>
            <atom:updated>2017-08-14T03:33:01.979Z</atom:updated>
            <content:encoded><![CDATA[<h4>Dockerize 與 Zeit JavaScript 跨平台解決方案</h4><p>繼上一篇，<a href="https://medium.com/@evenchange4/使用-circleci-2-0-workflows-挑戰三倍速-9691e54b0ef0">使用 CircleCI 2.0 Workflows 挑戰三倍速</a> 中將 Docker 導入 CI 設定測試環境外，這次著重在 CD 與產品交付，也試著使用 Docker 來建置 Production 機器，並且提交到其他服務平台上。</p><h3>選擇交付方式</h3><p>首先，從使用者最終會拿到什麼討論。產品交付有很多手段，常見可以透過部署公有雲、上架 APP 或是發布套件等等，另外也有像 MediaTek IoT Cloud 離線版，整體架構採用 NW.js 來開發桌面程序，只要幾個點擊就讓前端網頁以及伺服器端運行起來，並且輸出跨平台的執行檔。</p><p>當不需要 GUI，或單純想把 HTTP Service 專案跑起來，常碰的問題在於執行時所造成不可預期的錯誤，因此可以考慮連同執行環境一同交付。採用 Docker 來封裝是一個不錯的選擇，只要提供 Image，就能夠透過統一的接口來執行 Container。另外一種方法是本篇將提到 Zeit 的 <em>Pkg</em>，透過 Node.js 輸出 Binary 執行檔的做法。</p><h4>JavaScript 的解決方案</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PjRCNp1Viy1LmXzAjcZFow.png" /><figcaption>列出其中幾個有名氣的官方產品。<a href="https://zeit.co/oss">https://zeit.co/oss</a></figcaption></figure><p>開始關注 Zeit 是因為其前端的 Next.js 框架，Zeit 專案幾乎都是 JavaScript 實作的，所以對於前端工程師要追 OSS 原始碼非常地方便。他們在這一兩年也持續不斷地推出其他非常實用的產品，每當發布 Twitter 就是一陣好評，是一個值得學習的組織。</p><p>Zeit 旗下產品有一個很大的特性，<strong>只專注於處理單一事情上</strong>，接口也設計跟他的 Logo 一樣的簡潔，這一點非常符合 Microservice 的特性。本篇文章也是基於其底下產品的使用心得分享。</p><p>另外還有很多由社群開發的延伸套件與工具，可以查看 <a href="https://github.com/zeit/awesome-zeit">Awesome-zeit</a> 列表，而這次我也做了兩個 Side Project Microservice 來驗證這整個流程，可以參考並玩玩看，接下來段落會討論四種部署方法以及工具：</p><ol><li><a href="https://github.com/evenchange4/micro-medium-api">Micro-medium-api</a>：封裝 Medium API，撈取文章列表 [<a href="https://micro-medium-api.now.sh/">DEMO</a>]。</li><li><a href="https://github.com/evenchange4/micro-github-latest">Micro-github-release</a>：下載最新的 GitHub release asset [<a href="https://micro-github-latest.now.sh">DEMO</a>]。</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6eQYwBbr47BTXKyWPOGv1w.png" /><figcaption>Side Project 驗證四種交付流程</figcaption></figure><h3>Micro</h3><blockquote>Asynchronous HTTP microservices.</blockquote><p><a href="https://zeit.co/blog/micro-8">Micro 8</a></p><p>透過 Micro 要建立一個 HTTP Service 再簡單不過了，只要揭露一個 Handler Function，就能快速地跑起來。可以參考上面 Micro 8 的文章，Zeit 是怎麼針對 HTTP Server 進行優化的，主要把 HTTP Server 分為 Production 使用的 <em>micro</em> 以及 Development 使用的 <em>micro-dev</em>。開發上如果是 Node 8.0 以上版本，就可以直接寫 async/await 語法：</p><pre><em>// index.js<br></em>const <em>handler</em> = <strong>async</strong> (req, res) =&gt; {<br>  const response = <strong>await</strong> fetch(&#39;<a href="http://...&#39;">http://...&#39;</a>)<br>  return response.json()<br>};</pre><pre>module.exports = handler;</pre><p>單一職責的 Microservice 設定單一 Entrypoint 來處理 Request 應該就足夠，如果有 Routing 的需求，可以使用社群開發的 <em>micro-router</em>，至於 Middleware 部分其實就是層層的 Function Composition，可以使用 Ramda 來達成：</p><pre><em>// index.js<br></em>const R = require(&#39;ramda&#39;);<br>const { router, get } = require(&#39;microrouter&#39;);</pre><pre>const enhance = <strong>R.compose</strong>(<br>  cors({<br>    allowMethods: [&#39;GET&#39;],<br>    origin: &#39;*&#39;,<br>  }),<br>  compress,<br>)</pre><pre>module.exports = router(<br>  get(&#39;/:id&#39;, <strong>enhance</strong>(handler)),<br>)</pre><p>執行上用 npm script 來區分，本機端使用 <em>npm run dev</em>，當你部署到正式環境，就可以直接執行 <em>npm start</em>：</p><pre><em>// package.json</em><br>&quot;scripts&quot;: {<br>  &quot;start&quot;: &quot;micro&quot;,<br>  &quot;dev&quot;: &quot;micro-dev&quot;<br>}</pre><p>雖然 Micro 實做上很簡單，處理稍微複雜的系統，也可以有很不同的玩法，建議可以瀏覽 <a href="https://github.com/amio/awesome-micro">Awesome-micro</a> 來看看其他人是怎麼設計 Microservices 的。</p><h3>a. 𝚫 Now.sh Service</h3><blockquote>Realtime global deployments.</blockquote><p>部署前端專案雖然我還是最推薦 Netlify 的服務，不過其只能 Serve Static Files，因此通常有 Server 需求的專案，就必須找一些 PaaS 來使用。因此推薦 Now.sh，他們的服務非常的精簡，包含部署上也不太需要什麼設定，比起以前用過類似的 Heroku 服務簡單很多。</p><h4>簡易的 JSON 設定格式</h4><p>專案若是 Node.js Server，就直接透過預設的 npm 方式進行部署，特別要注意加上 <em>NODE_ENV=production</em>，才不會下載太多非必要的開發套件，也可以透過下方範例中的 <em>env</em> 來設定。當然，較複雜的環境或甚至是非 Node.js，Now.sh 也支援透過 Docker 來設定專案的環境，只要將 Dockerfile 推上 Now Service，就會建置 Docker Image。最後透過 <em>now-cli</em> 快速部署：</p><pre><em>// package.json</em><br>&quot;now&quot;: {<br>  &quot;type&quot;: &quot;npm&quot;, <em>// or &#39;docker&#39;</em><br>  &quot;env&quot;: {<br>    &quot;NODE_ENV&quot;: &quot;production&quot;<br>  }<br>},</pre><pre><em># now-cli deploy</em><br>$ now</pre><h4>Subdomain Alias</h4><p>因為 Now.sh 預設新增專案都會自動產生含一串 Hash 的網址，如果想要讓網址看起來簡單一點，可以註冊一個 Subdomain，只要透過下方的 <em>alias</em> 設定即可，然後同樣透過 CLI 執行：</p><pre><em>// package.json<br></em>&quot;now&quot;: {<br>  &quot;alias&quot;: &quot;mcs-lite-app&quot;<br>}</pre><pre>$ now alias</pre><h4>Instance automatically scaled</h4><p>Now.sh 服務有一個 Sleep 機制，每當一段時間沒有流量或是超過你的 Instances 額度，就會暫時被 Frozen 直到下次啟動，如果你需要也可以將專案設定一個最大最小的範圍，讓 Now.sh 幫你依需求自動設定，又或是只設定最小 Instance 1 就不會暫停了：</p><pre>$ now scale mcs-lite-app.now.sh 1</pre><h4>Continuous Delivery</h4><p>官方提供完整的 Now CLI 套件，也可以拿來串接 CI，每當測試成功就執行部署到 Now.sh 上。下方流程中透過 Wait for ready 的指令，等待新的網站佈署成功，中間可以加上一點 End-to-End 測試，完成後將 Alias 指向新的 Instance，最後才把舊的 Instance 砍掉，來達到 Zero Downtime 的目的：</p><pre><em>// .travis.yml</em><br>after_success:<br>  <em># 1. Wair for deployment ready<br></em>  - URL=$(now --public)<br>  - await-url $URL</pre><pre><em>  # 2. Alias and purge old services</em><br>  - now alias set &quot;$URL&quot; &quot;$ALIAS&quot;<br>  - now rm $PROJECT --safe --yes</pre><p>Now.sh 對於 OSS 專案雖然是免費的，仍然有使用量限制，另外也沒支援 Docker Compose 或是多個 Port，不過還蠻適合拿來放一些 Side Project 或是 Demo page 的。特別要注意如果有用 DB 在每次重新部署都會洗掉，可能要搭配其他 Database 服務一併使用。</p><h4>Deploy Button</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1004/1*_kV4qHJCnoJBfTE3L6L6QA.png" /><figcaption>Put a deploy button in the GitHub README file. (<a href="https://github.com/MCS-Lite/mcs-lite-app-demo">Link</a>)</figcaption></figure><p>由社群開發的，類似 Heroku Deploy Button，可以放在 README 文件上讓使用者一鍵快速部署自己專屬的 Microservice，可以參考左圖 MCS Lite Demo 在文件上的部署流程。</p><h3>b. Pkg</h3><blockquote>Package your Node.js project into an executable.</blockquote><p>將 Node.js 執行環境也一並打包，只輸出單一執行檔，如此一來使用者就不需要額外下載 Node.js 環境。可以透過 <em>package.json</em> 來設定 Binary 的執行入口，接著透過 <em>pkg</em> 指令就能輸出預設的三種平台的檔案：</p><pre><em>// package.json</em><br>&quot;bin&quot;: &quot;bin/server.js&quot;</pre><pre>$ pkg . --out-path pkg</pre><pre>&gt; pkg@4.2.2<br>&gt; Targets not specified. Assuming:<br>  <strong>node8-linux-x64, node8-macos-x64, node8-win-x64</strong></pre><p>交付上，透過 TravisCI 設定自動部署，一旦有 Git Tag 推上 GitHub，就將 Pkg 輸出的執行檔上傳到 GitHub Release：</p><pre><em>// .travis.yml</em><br>deploy:<br>  provider: releases<br>  api_key:<br>    secure: ...<br>  file_glob: true<br>  skip_cleanup: true<br>  file: &quot;./pkg/*&quot;<br>  on:<br>    repo: evenchange4/micro-medium-api<br>    <strong>tags: true</strong></pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RiM9z6Z-JsFNKmKvpAKlGg.png" /><figcaption>GitHub Release via Travis</figcaption></figure><h3>c. NPM Release</h3><p>Node.js 使用者肯定偏好使用 NPM 來安裝，既然在上個段落 Pkg 已經設定好 <em>package.json</em> 的 <em>bin</em> 檔，那不如就順便把它給 Release 到 NPM 上吧，同樣透過 Travis 來做交付：</p><pre><em>// .travis.yml</em><br>provider: npm<br>  email: <a href="mailto:evenchange4@gmail.com">evenchange4@gmail.com</a><br>  api_key:<br>    secure: ...<br>  on:<br>    repo: evenchange4/micro-medium-api<br>    tags: true</pre><h4>CLI Argument</h4><p>關於接口設計上，之前有用過其它套件做過，但都沒有 <em>yargs</em> 套件開發時來的簡潔，建議可以試試看。當然，最後也可以直接輸出當作文件用：</p><pre>$ micro-medium-api --help<br>Usage: micro-medium-api &lt;command&gt; [options]<br> <br>Options:<br>  -p, --port     HTTP server PORT       [default: 3000]<br>  -h, --help     Show help              [boolean]<br>  -v, --version  Show version number    [boolean]</pre><p>一旦有套件更新，使用者要怎麼知道呢？推薦使用 <em>update-notifier </em>來幫你達成，其實最新的 npm 5 也是用這套來做的。在你的執行檔入口中，預先跑過版本檢查，當使用者下指令時就會跳出如下圖資訊：</p><pre><em>// bin/cli.js</em><br>const updateNotifier = require(&#39;update-notifier&#39;)<br>const pkg = require(&#39;../package.json&#39;)</pre><pre>updateNotifier({ pkg }).notify()</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/954/1*GaJkI_HAqSAIV5etLw3QjA.png" /><figcaption>update-notifier 更新通知</figcaption></figure><h3>d. Dockerize</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Qjm0LpGGRZotuNxtxPkOKg.png" /><figcaption>Dockerize 為跨平台提供了統一接口</figcaption></figure><p>Server 環境問題是專案初期設定上的盲點，要一勞永逸地解法就是封裝 Docker Image。目前許多平台都早已支援使用 Dockerfile 進行設定了，包含各大 CI/CD 服務工具等等。以下 MCS Lite 為例，其中封裝了運行環境、執行程序，最後只揭露 Port 即可。當使用者建置完 Docker Image 後，就可以簡單地使用 Docker Run 指令執行 Container：</p><pre><em># Dockerfile</em></pre><pre>FROM node:6.10.3<br>RUN ...<br>EXPOSE 3000<br>ENTRYPOINT [&quot;npm&quot;, &quot;start&quot;]</pre><pre>$ docker run --rm -it -p 3000:3000 -p 8000:8000 mcslite/mcs-lite-app</pre><h4>DockerHub Automatic Build</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*lGkjrw5cBjfWdqLs0HQGjQ.png" /><figcaption>DockerHub 連結 GitHub Automatic build (<a href="https://hub.docker.com/r/mcslite/mcs-lite-app/">Link</a>)</figcaption></figure><p>除了在本機端下載 Dockerfile 建置 Docker Image 外，也可以透過 DockerHub 自動地將 Image 預先建立好，其他人就能直接下載使用。左圖使用 DockerHub Automatic Build 功能，也就是能夠連動放在 GitHub 上的 Dockerfile，一旦 GitHub 有更新就會 Trigger DockerHub 建立出新的 Image，當然也可以設定 Git Tag 來做版本控制。</p><h4>Multi-Stage Builds in Docker Cloud</h4><p>Multi-stage Build 是 Docker 17.05 的新功能，可以設定建置、測試以及最後 Runtime Stage 來自不同的 Image，來達到最終揭露的 Image 最小化的目的。下方的 Dockerfile 可以看到有兩個 <em>FROM</em> ，輸出後從原本 266 MB 縮減為 16MB：</p><pre><em># Dockerfile</em></pre><pre><em># Build 266 MB</em><br>FROM node:8.2.1 <strong>AS</strong> builder<br>RUN npm install</pre><pre><em># Runtime 16 MB</em><br>FROM mhart/alpine-node:base-8.2.1<br>COPY <strong>--from=builder</strong> /app .<br>CMD npm start</pre><p>又舉例來說，你可能需要 Node.js 環境來建置前端靜態檔案，但是最終在 Server 上只需要 Python 跑 Web Server，就可以用這個方法讓 Production Image 有 Python 但不包含 Node.js 環境。可惜目前在 DockerHub 上仍然是 Docker 穩定版號 17.03，所以並不能使用。根據 <a href="https://github.com/docker/hub-feedback/issues/1039">Issues#1039</a> 討論，看來要能等到 Multi-stage 支援遙遙無期，官方也沒辦法給個確切的日期，反倒是看到很多文章都在推 Docker Cloud：</p><p><a href="https://blog.docker.com/2017/07/multi-stage-builds/">Multi-Stage Builds - Docker Blog</a></p><p>Docker Cloud 是 Based on DockerHub 所開發，因此介面與功能上幾乎都有涵蓋到，並且多了許多設定可以做選擇，例如下圖可以選擇 Docker 版本。但缺點是需要登入才能看到 Dashboard，對於 OSS 專案資訊不夠透明：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Vr5eK5DS7WZPuI7d11Exeg.png" /><figcaption>Docker Cloud 選擇 Docker Version 17.05</figcaption></figure><p>另外在 Automatic Build 的設定上也多了一點彈性，例如想要將 npm 習慣上的 Git Tag <em>v1.0.1</em> 轉換成 <em>1.0.1</em>，也就是去掉 <strong>v</strong> 開頭。如此指定 Docker Image 版號時也比較符合一般大眾的格式 Image:1.0.1。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*E2vt43fzrTxgH0GLXzZu6g.png" /></figure><h3>後記</h3><p>透過 Side Project 驗證的四種部署方式，才會真正的思考如何交付才能最順暢。將專案設定 Docker 也可幫助整合測試，例如以往要將前後端分離的數個專案跑起來才能進行 End-to-End 實測，封裝後就能簡單地跑起來。</p><p>下一步，也許會參考幾個 OSS 專案對於 Now.sh 與 GitHub 的整合，來模擬看看 <a href="https://stage.now.sh/">Staging</a> Continuous Deployment 的功能，如果你對後續嘗試感興趣，可以關注我的 Medium 喔。</p><p>最後，工商時間。快試試建置您專屬的物聯網雲平台吧！</p><p><a href="https://github.com/MCS-Lite/mcs-lite-app-demo">MCS-Lite/mcs-lite-app-demo</a></p><h4>Further Readings</h4><ol><li><a href="https://medium.com/@evenchange4/使用-circleci-2-0-workflows-挑戰三倍速-9691e54b0ef0">使用 CircleCI 2.0 Workflows 挑戰三倍速</a></li><li><a href="https://medium.com/@evenchange4/react-stack-%E9%96%8B%E7%99%BC%E9%AB%94%E9%A9%97%E8%88%87%E5%84%AA%E5%8C%96%E7%AD%96%E7%95%A5-b056da2fa0aa">React Stack 開發體驗與優化策略</a></li><li><a href="https://medium.com/@evenchange4/build-a-web-app-in-mediatek-61b0a26215a0">Build A Web App in MediaTek</a></li></ol><h4>References</h4><ol><li><a href="https://zeit.co/docs/deployment-types/lifecycle#instances">Deployment lifecycle and Scalability in now</a></li><li><a href="https://github.com/sholladay/await-url">sholladay/await-url</a></li></ol><p><em>＊完整的專案放在 </em><a href="https://github.com/mcs-lite/mcs-lite-app-demo"><em>MCS-Lite/mcs-lite-app-demo</em></a><em> 以及 </em><a href="http://Micro-github-release"><em>evenchange4/Micro-github-release</em></a><em>，如果你喜歡這系列文章，關於 Michael 在 OSS 專案開發心得，別忘了可以點個 ❤️ 讓我知道喔！</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9f2954c7167d" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[使用 CircleCI 2.0 Workflows 挑戰三倍速]]></title>
            <link>https://medium.com/@evenchange4/%E4%BD%BF%E7%94%A8-circleci-2-0-workflows-%E6%8C%91%E6%88%B0%E4%B8%89%E5%80%8D%E9%80%9F-9691e54b0ef0?source=rss-aa88256220c4------2</link>
            <guid isPermaLink="false">https://medium.com/p/9691e54b0ef0</guid>
            <category><![CDATA[mediatek]]></category>
            <category><![CDATA[circleci]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[ci]]></category>
            <dc:creator><![CDATA[Michael Hsu]]></dc:creator>
            <pubDate>Fri, 28 Jul 2017 07:12:13 GMT</pubDate>
            <atom:updated>2017-07-28T18:28:26.442Z</atom:updated>
            <content:encoded><![CDATA[<h4>減少 Monorepo CI 所需花費時間</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2by80tI8UG_v_IJWjfvwNg.png" /><figcaption>CI Workflows Parallel Job Execution</figcaption></figure><p>CircleCI 2.0 雖然出來一年多了，不久前七月初才正式 Launch，聽說加速不少，並且基於 Docker 以及 Workflow 設定模式，與公司內部的 DroneCI 設定起來蠻相似的，所以來試試看。</p><h3>專案背景簡介</h3><p>這是 MediaTek IoT Cloud 開源專案之一，文章討論的設定專注於前端的部分，基本上皆為 JavaScript 實作。此專案屬於 Monorepo 性質，也就是一個 Repository 包含數個 Project (Website) 或是 Library (NPM)。</p><p>在年初的時候因為還是 Private 狀態，當時考慮了 Circle 1.0 一個 Private 免費額度來作為主要 CI，開源後換到主流的 Travis，畢竟整合的服務較為完善，策略上兩個 CI 同時分別跑一部分的 Job，然後再設定 GitHub Protect 功能，必須都要綠勾勾才能 Merge PR。</p><p>以下作為數據參考：</p><ul><li>Project 頁面類型數量：4</li><li>Library 類型數量：12</li><li>Test File 數量：<a href="https://codecov.io/gh/MCS-Lite/mcs-lite/list/master/">262</a>、Coverage: 87%</li></ul><pre>// jest<br>Test Suites: 225 total<br>Tests:       628 total<br>Snapshots:   479 total</pre><h3>CircleCI 2.0 VS Travis Beta</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*4QEeBY8Jj19pE08iuRLILA.png" /></figure><p>本篇文章一部分在於 Travis 與 Circle 2.0 的比較。雖然 Travis 也可以用 Docker，但是其整合的還不完整，基本上都要用 <em>docker run</em> 來執行 Script；Travis 也同樣提供 Stage Jobs 的概念，但仍然在 Beta 階段，我實際玩起來並沒有加速到反而變慢，所以最後選擇用 CircleCI 2.0。</p><h3>CI 做了哪些事</h3><h4>a. Dependency Install</h4><p>安裝 NPM 相依套件，全篇都以 Yarn 來作為 NPM Client 進行討論。</p><h4>b. Bootstrap</h4><p>使用 Lerna 進行 Monorepo 管理，其中最關鍵的步驟在於 Hoist Bootstrap，把所有子目錄的 <em>node_modules</em> 都提升到根目錄去，若彼此之間若有相依性會透過 Symbolic Link 關聯起來。最後把所有子目錄使用 Babel 轉為 CommonJS Module。</p><h4>c. Lint</h4><p>包含了 JavaScript Coding Style 的 Eslint、CSS Coding Style 的 Stylelint 以及少部分 Static Type Checker 的 Flow。</p><h4>d. Unit Test</h4><p>絕大部分都使用 Jest 做 Snapshot Testing。</p><h4>e. Building Test</h4><p>除了功能性 Unit Test 外，另一個重點是要測試頁面建置過程（Webpack）有沒有問題。</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3k_SykpL-7z8d_Gem0OycQ.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/891/1*v5xxgeaxHUb0ADHW8KrexA.png" /><figcaption>CircleCI 2.0 Workflows with GitHub</figcaption></figure><p>如上圖，將 Job 切分為上個段落的 a~e 個步驟，以往依序執行將會是這些片段時間的總和，大致會是 7 分鐘。</p><p>事實上，除了 a 與 b 是需要預先跑完才能往下執行外，其他步驟皆可獨立執行，因此可以採取 CircleCI 2.0 的 Workflows，預設免費可以同時跑 4 個 Job，所以切成四個恰恰好。如此一來，整體所需時間就會是 a + b + d 的總和，差不多三分半。</p><p>理論上，單純透過 Parallel Job Execution 就能加速兩倍左右。</p><h3>CI 還做了哪些事</h3><h4>環境設定</h4><p>最重要的就是開發時的版號能夠固定，這樣一旦發生錯誤時才能將範圍縮小。如果是 Travis 或 CircleCI 1.0 的設定方式是透過 YAML 設定 Node.JS 版號，但是像是 Yarn 的版號就比較麻煩，寫起來大概像是：</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*g0tlfN8HyzfH4JlubZS8qw.png" /><figcaption><em>設定 Node.JS 與 Yarn 版號 (</em><a href="https://github.com/MCS-Lite/mcs-lite/blob/master/.travis.yml"><em>.travis.yml</em></a><em>)</em></figcaption></figure><p>以往會需要自己寫些 Script 來判斷 Yarn 的環境，進行鎖版的動作。那如果是 CircleCI 2.0 則是使用 Docker Image，設定起來簡單很多：</p><pre><em># .circleci/config.yml</em><br>docker:<br>  - image: node:8.2.1</pre><p>可以直接使用官方維護的 Node Docker Image 即可，已經預設有把 Yarn 包在裡面。</p><h4>Caching</h4><p>Cache 絕對是 CI 的一大重點，在 Travis 時，可以透過簡單的目錄指定來設定哪些檔案要 Cache 起來：</p><pre><em># .travis.yml</em><br>cache:<br>  yarn: true  <em># Global .cache/yarn</em><br>  directories:<br>    - ~/.yarn <em># 上個段落安裝 yarn 鎖版用</em><br>    - node_modules</pre><p>雖然 CircleCI 2.0 變得稍微複雜一點，但是功能就更為強大了，例如這邊可以計算 Lockfile 的 Checksum 來指令要哪一包 Cache：</p><pre>dependency-cache-{{ <strong>checksum &quot;yarn.lock&quot;</strong> }}</pre><p>因為是 Docker Based，記得要把 Yarn Cache 目錄設在專案內：</p><pre>yarn config set cache-folder <strong>.yarn-cache</strong></pre><p>這樣稍後才能把專案內的資料夾做 Cache：</p><pre><em># .circleci/config.yml</em><br>- save_cache:<br>    key: yarn-cache-{{ checksum &quot;yarn.lock&quot; }}<br>    paths:<br>      - <strong>.yarn-cache</strong></pre><h4>Workflows</h4><p>在 CircleCI 2.0 的設計中會先把 Job 獨立設定好，而後才會在底下設定 Job 的執行順序與相依性，例如這個專案中會先執行完 a + b (Bootstrap)，才會執行後續的 c、d、e：</p><pre><em># .circleci/config.yml</em><br>jobs:<br>  - bootstrap<br>  - lint:<br>      requires:<br>        - bootstrap<br>  - test:<br>      requires:<br>        - bootstrap<br>  - test-page:<br>      requires:<br>        - bootstrap</pre><h3>不嚴謹的實驗結果</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/838/1*tCaUMNBzjvqpmWSKgTOfkA.png" /><figcaption>Circle CI 1.0（左）與 2.0（右）每個 Job 所需時間減少 — <a href="https://circleci.com/build-insights/gh/MCS-Lite">CircleBuild Insights</a></figcaption></figure><h4>Without cache:</h4><p>Travis: 12 min<br>Circle2: 4 min</p><h4>With cache:</h4><p>Travis: 9.5 min<br>Circle2: 3.5 min 🚀</p><h3>後記</h3><p>雖然 Travis 與 CircleCI 各有優缺點，像要設定多個 Node.JS 環境來跑還是 Travis 的 Matrix 方便。他們都對於 OSS 非常的有善，基本上免費就很夠用，不如多設定幾個 CI 多一份保障。</p><p>下一步，可能會從根本的問題來處理，例如 Monorepo 在最外層設定一個 Test Runner 來跑 Unit Test 就好，減少啟動時間；另外 Lerna + Yarn 的方案之後可以考慮換成 Yarn 新版內建的 Workspace 功能，相信會有更好的整合。未來如果想看這部分的嘗試，可以關注我的 Medium。</p><p>本篇文章都沒有提及 CD 的部分，事實上我更推薦使用 Netlify 來做為前端專案的部署，在<a href="https://medium.com/@evenchange4/react-stack-開發體驗與優化策略-b056da2fa0aa#1255">我之前的經驗</a>中跑得比 Travis 或 CircleCI 還來的快。</p><h4>Further Readings</h4><ol><li><a href="https://medium.com/@evenchange4/react-stack-%E9%96%8B%E7%99%BC%E9%AB%94%E9%A9%97%E8%88%87%E5%84%AA%E5%8C%96%E7%AD%96%E7%95%A5-b056da2fa0aa">React Stack 開發體驗與優化策略</a></li><li><a href="https://medium.com/@evenchange4/build-a-web-app-in-mediatek-61b0a26215a0">Build A Web App in MediaTek</a></li></ol><h4>References</h4><ol><li><a href="https://circleci.com/docs/2.0/">CircleCI 2.0 Documents</a>、<a href="https://github.com/circleci/frontend/blob/master/.circleci/config.yml">Circleci/Frontend Config</a></li><li><a href="https://blog.wu-boy.com/2017/06/downsize-node_modules-to-improve-deploy-speed/">減少 node_modules 大小來加速部署 Node.js 專案</a></li></ol><p><em>＊完整的 Migrate PR 放在 </em><a href="https://github.com/MCS-Lite/mcs-lite/pull/424"><em>MCS-Lite/mcs-lite</em></a><em>，如果你喜歡這系列文章，關於 Michael 在 OSS 專案開發心得，別忘了可以點個 ❤️ 讓我知道喔！</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9691e54b0ef0" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>