Kerry 的筆記本
  • Table of contents
  • Kerry的Mac裝機必要
  • ASP.NET Core 教育訓練文件
    • .NET 9 OpenAPI 介紹與教學
    • 目錄
    • ASP.NET Core Authentication系列(一)理解Claim, ClaimsIdentity, ClaimsPrincipal
    • ASP.NET Core Authentication系列(三)Cookie選項
    • ASP.NET Core Authentication系列(二)實現認證、登錄和註銷
    • ASP.NET Core Authentication系列(四)基於Cookie實現多應用間單點登錄(SSO)
    • ASP.NET Core Consul 教學
    • ASP.NET Core Hangfire 排程管理
    • ASP.NET Core KeyCloak 實作
    • ASP.NET Core NLog-依照Environment使用Nlog.Config檔案
    • ASP.NET Core NLog-如何使用 NLog 將 log 寫到檔案
    • ASP.NET Core Nlog-發送訊息到ElasticSearch
    • 目錄
    • ASP.NET Core Quartz.NET 管理介面
    • ASP.NET Core RDLC 報表設計
    • ASP.NET Core SFTP (使用第三方套建 SSH.Net) - 類別庫為案例
    • ASP.NET Core 中使用 HttpReports 進行接口統計,分析, 可視化, 監控,追踪等
    • ASP.NET 使用 MassTransit 與 RabbitMQ,實現事件發佈、訂閱
    • Asp.Net Core 分散式Session – 使用 Redis
    • ASP.NET Core 前台會員修改個人資料
    • ASP.NET Core 前台會員忘記密碼與重設密碼
    • ASP.NET Core 前台會員登入
    • ASP.NET Core 前台會員註冊
    • ASP.NET Core 呼叫 API 發生 CORS 錯誤
    • ASP.NET Core 如何套網頁設計樣版
    • ASP.NET Core 客製化Model Validation 預設錯誤訊息
    • ASP.NET Core 後台查詢頁面教學
    • ASP.NET Core 網站生命週期
    • ASP.NET Feature Management 使用說明與教學
    • ASP.NET RulesEngine 介紹
    • ASP.NET WinForms APP 程式安裝檔
    • LinePay 支付完成後返回 LINE 應用而不跳出外部瀏覽器
    • EntityFramework
      • EF Core Migrations 完整教學手冊
      • EntityFramework Core DB Migrations
      • 使用 Entity Framework Core (EF Core) 的 Migrations 功能進行版本控制
    • NET 6
      • .NET 6 Autofac範例
      • .NET 6 Automapper範例
      • .NET 6 BenchmarkDotNet範例
      • .NET 6 Bogus範例
      • .NET 6 Dapper範例
      • .NET 6 Dapper語法說明
      • .NET 6 EFCore範例
      • .NET 6 EFCore語法說明
      • .NET 6 EPPlus圖表範例
      • .NET 6 EPPlus範例
      • .NET 6 Hangfire範例
      • .NET 6 HttpClient單元測試範例
      • .NET 6 MailKit前置作業
      • .NET 6 MailKit範例
      • .NET 6 Moq範例
      • .NET 6 NLog範例
      • .NET 6 NLog進階範例
      • .NET 6 Serilog範例
      • .NET 6 Serilog進階範例
      • .NET 6 Telegram.Bot前置作業
      • .NET 6 Telegram.Bot範例
      • .NET 6 Text.Json範例
      • .NET 6 swagger授權
      • .NET 6 swagger範例
      • .NET 6 xUnit範例
      • .NET 6 取得appsettings檔案內容
      • .NET 6 更改回傳Json時為大駝峰命名
      • .NET 6 解決System.Text.Json序列化後會將所有非ASCII轉為Unicode
    • WDMIS
      • CORS
      • FeatureManagement
      • Serilog
      • Spectre.Console
      • 資料模型實戰:從 MSSQL 設計到 .NET 8 WebAPI 實作(以刀具管理為例)
  • Azure
    • 如何在 ASP.NET CORE 5.0 WEB 應用程序中實現 AZURE AD 身份驗證
    • Azure App Configuration 使用教學
    • Azure Blob Storage
    • Azure DevOps 持續整合(CI) + Artifacts
  • CSharp
    • ASP.NET await 與 wait 的差異
    • AutoMapper —— 類別轉換超省力
    • C# 中的 HTTPClient — 入門指南
    • C# 正則表達式:從零到英雄指南
    • C# 集合, List<> 取交集、差集、聯集的方法
    • C#單元測試教學
    • CORS 介紹與設定方式
    • CSharp Coding Conventions
    • Using jQuery Unobtrusive AJAX in ASP.NET Core Razor Pages
    • 深入Dapper.NET源碼
    • 菜雞與物件導向
      • 菜雞與物件導向 (0): 前言
      • 菜雞與物件導向 (1): 類別、物件
      • 菜雞與物件導向 (10): 單一職責原則
      • 菜雞與物件導向 (11): 開放封閉原則
      • 菜雞與物件導向 (12): 里氏替換原則
      • 菜雞與物件導向 (13): 介面隔離原則
      • 菜雞與物件導向 (14): 依賴反轉原則
      • 菜雞與物件導向 (15): 最少知識原則
      • 菜雞與物件導向 (2): 建構式、多載
      • 菜雞與物件導向 (3): 封裝
      • 菜雞與物件導向 (4): 繼承
      • 菜雞與物件導向 (5): 多型
      • 菜雞與物件導向 (6): 抽象、覆寫
      • 菜雞與物件導向 (7): 介面
      • 菜雞與物件導向 (8): 內聚、耦合
      • 菜雞與物件導向 (9): SOLID
      • 菜雞與物件導向 (Ex1): 小結
  • DBeaver
    • 如何強制讓 DBeaver 在 Mac 上使用英文介面
  • DesignPattern
    • OAuth
    • Repository 模式 (Repository Pattern)
    • Single Sign On 實作方式介紹 (CAS)
    • 【SOP製作教學】新手適用,SOP範例、流程圖、製作流程全公開!
    • 【SOP製作教學】流程圖教學、重點範例、BPMN符號介紹!
    • 【SOP製作教學】流程圖符號整理、BPMN2.0進階符號教學!
    • 多奇數位 C# 程式碼撰寫規範 (C# Coding Guideline)
    • 軟體分層設計模式 (Software Layered Architecture Pattern)
    • 開源程式碼檢測平台 SonarQube
    • 菜雞新訓記
      • 菜雞新訓記 (0): 前言
      • 菜雞新訓記 (1): 使用 Git 來進行版本控制吧
      • 菜雞新訓記 (2): 認識 Api & 使用 .net Core 來建立簡單的 Web Api 服務吧
      • 菜雞新訓記 (3): 使用 Dapper 來連線到資料庫 CRUD 吧
      • 菜雞新訓記 (4): 使用 Swagger 來自動產生可互動的 API 文件吧
      • 菜雞新訓記 (5): 使用 三層式架構 來切分服務的關注點和職責吧
      • 菜雞新訓記 (6): 使用 依賴注入 (Dependency Injection) 來解除強耦合吧
      • 菜雞新訓記 (7): 使用 Fluent Validation 來驗證參數吧
  • DevOps
    • Repository 模式 (Repository Pattern)
    • pipeline工具研究
    • 單例模式 (Singleton Pattern)
    • 單元測試
    • 軟體分層設計模式 (Software Layered Architecture Pattern)
    • 雙重檢查鎖定模式 (Double-Checked Locking Pattern)
  • Docker
    • Docker 中部署 .NET 8 Web App 並支援 HTTPS
    • Docker指令大全
    • 第七章 安裝Nomad
    • Docker - 第三章 | 安裝 MSSQL
    • Docker - 第九章 | 安裝 datalust seq
    • 第二章 docker-compose 教學
    • Docker - 第五章 | 安裝 Redis
    • 第八章 安裝SonarQube
    • Docker - 第六章 | 安裝RabbitMQ
    • 第十一章 安裝 VtigerCRM
    • 第十二章 安裝KeyCloak
    • Docker - 第十章 | 安裝 Redmine
    • 第四章 安裝MySQL
    • Docker Desktop (含更改 Docker Image 路徑)
  • Git
    • Git Flow 指令大全(完整指令整理) 🚀
    • Git 安裝及配置SSH Key
    • Git 建立到上傳
    • 將現有專案的遠端儲存庫直接更改為新的儲存庫
    • Git 流程規劃
    • Git 語法大全
    • 30 天精通 Git 版本控管
      • 30 天精通 Git 版本控制
        • 第 01 天:认识 Git 版本控制
        • 第 02 天:在 Windows 平台必装的三套 Git 工具
        • 第 03 天:建立仓库
        • 第 04 天:常用的 Git 版本控制指令
        • 第 05 天:了解仓库、工作目录、物件与索引之间的关系
        • 第 06 天:解析 Git 资料结构 - 物件结构
        • 第 07 天:解析 Git 资料结构 - 索引结构
        • 第 08 天:关于分支的基本观念与使用方式
        • 第 09 天:比对文件与版本差异
        • 第 10 天:认识 Git 物件的绝对名称
        • 第 11 天:认识 Git 物件的一般参照与符号参照
        • 第 12 天:认识 Git 物件的相对名称
        • 第 13 天:暂存工作目录与索引的变更状态
        • 第 14 天: Git for Windows 选项设定
        • 第 15 天:标签 - 标记版本控制过程中的重要事件
        • 第 16 天:善用版本日志 git reflog 追踪变更轨迹
        • 第 17 天:关于合并的基本观念与使用方式
        • 第 18 天:修正 commit 过的版本历史记录 Part 1
        • 第 19 天:设定 .gitignore 忽略清单
        • 第 20 天:修正 commit 过的版本历史记录 Part 2
        • 第 21 天:修正 commit 过的版本历史记录 Part 3
        • 第 22 天:修正 commit 过的版本历史记录 Part 4 (Rebase)
        • 第 23 天:修正 commit 过的版本历史记录 Part 5
        • 第 24 天:使用 GitHub 远端仓库 - 入门篇
        • 第 25 天:使用 GitHub 远端仓库 - 观念篇
        • 第 26 天:多人在同一个远端仓库中进行版控
        • 第 27 天:通过分支在同一个远端仓库中进行版控
        • 第 28 天:了解 GitHub 的 fork 与 pull request 版控流程
        • 第 29 天:如何将 Subversion 项目汇入到 Git 仓库
        • 第 30 天:分享工作中几个好用的 Git 操作技巧
      • zh-tw
        • 第 01 天:認識 Git 版本控管
        • 第 02 天:在 Windows 平台必裝的三套 Git 工具
        • 第 03 天:建立儲存庫
        • 第 04 天:常用的 Git 版本控管指令
        • 第 05 天:了解儲存庫、工作目錄、物件與索引之間的關係
        • 第 06 天:解析 Git 資料結構 - 物件結構
        • 第 07 天:解析 Git 資料結構 - 索引結構
        • 第 08 天:關於分支的基本觀念與使用方式
        • 第 09 天:比對檔案與版本差異
        • 第 10 天:認識 Git 物件的絕對名稱
        • 第 11 天:認識 Git 物件的一般參照與符號參照
        • 第 12 天:認識 Git 物件的相對名稱
        • 第 13 天:暫存工作目錄與索引的變更狀態
        • 第 14 天: Git for Windows 選項設定
        • 第 15 天:標籤 - 標記版本控制過程中的重要事件
        • 第 16 天:善用版本日誌 git reflog 追蹤變更軌跡
        • 第 17 天:關於合併的基本觀念與使用方式
        • 第 18 天:修正 commit 過的版本歷史紀錄 Part 1
        • 第 19 天:設定 .gitignore 忽略清單
        • 第 20 天:修正 commit 過的版本歷史紀錄 Part 2
        • 第 21 天:修正 commit 過的版本歷史紀錄 Part 3
        • 第 22 天:修正 commit 過的版本歷史紀錄 Part 4 (Rebase)
        • 第 23 天:修正 commit 過的版本歷史紀錄 Part 5
        • 第 24 天:使用 GitHub 遠端儲存庫 - 入門篇
        • 第 25 天:使用 GitHub 遠端儲存庫 - 觀念篇
        • 第 26 天:多人在同一個遠端儲存庫中進行版控
        • 第 27 天:透過分支在同一個遠端儲存庫中進行版控
        • 第 28 天:了解 GitHub 的 fork 與 pull request 版控流程
        • 第 29 天:如何將 Subversion 專案匯入到 Git 儲存庫
        • 第 30 天:分享工作中幾個好用的 Git 操作技巧
  • Hands-On Labs - LineBotSDK 實作手札 (C#, .net core)
    • 00. 如何申請LINE Bot
    • CLI
      • 使用CLI來發送新的Channel Access Token(LINE Bot)
      • 使用CLI免費發送LINE Notify通知
    • basic
      • 如何發送LINE訊息(Push Message)
      • 如何發送LINE Template Messages
      • 如何發送ImageMap訊息
      • 如何發送Flex Message
      • 如何在訊息後面加上QuickReply快捷選項
    • liff
      • Lab 21: 建立第一個LIFF應用
    • webhook
      • 如何建立可Echo的基本LINE Bot
      • 如何在WebHook中取得用戶個人資訊(名稱、頭像、狀態)
      • 如何在WebHook中取得用戶上傳的圖片(Bytes)
  • Markdown
    • Markdown Cheatsheet 中文版
    • Markdown語法大全
    • 使用HackMD建立書本目錄
    • 使用HackMD建立簡報
  • SAP ABAP
    • ABAP開發環境和總體介紹
    • SAP MM模塊常用表總結
    • SAP QM數據庫表清單
    • SAP欄位與表的對應關係
  • SQL Server
    • [SQL SERVER] Like in
    • SQL Server 中,移除資料庫中所有的關聯限制
    • SQL Server 刪除資料庫中所有資料表
    • SQL Server View、Function 及 Stored Procedure 定義之快速備份
    • SSMS v18 清除登入畫面中,下拉選單歷史紀錄
    • [MS SQL]如何透過Database Mail進行郵件發送
    • [SQL SERVER]撰寫Stored Procedure小細節
    • 使用 Data Migration Assistant 移轉 SQL Server 資料庫與帳戶
    • 使用SSIS創建同步資料庫數據任務
  • Tools
    • 免費 FTP 伺服器 FileZilla Server 安裝教學 (新版設定)
  • VisualStudio
    • .NET CLI 指令碼介紹
    • Visual Studio 使用 Git 版本控制
    • 使用 Visual Studio 2022 可透過 .editorconfig 鎖定文字檔案的儲存編碼格式分享
  • Web API
    • ASP.NET Core 6 Web API 進行 JWT 令牌身份驗證
    • [ASP.NET Core]如何使用SwaggerAPI說明文件
    • ASP.NET Core Web Api實作JWT驗證筆記
    • ECFIT API 範例
    • JWT Token Authentication And Authorizations In .Net Core 6.0 Web API
    • 微服務架構 - 從狀態圖來驅動 API 的設計
  • Windows
    • [C#] 伺服器監控常用語法 (事件檢視器、CPU 硬碟使用率、程式執行狀況)
    • Configure IIS Web Server on Windows Server 2019
    • Log Paser Studio 分析 IIS W3C Log
    • Windows Server 2019 如何安裝 IIS 運行 ASP.NET 專案
    • 如何檢查安裝在 IIS 上的 .NET Core Hosting Bundle 版本
    • [IIS] 如何解決網站第一個請求 Request 特別慢 ?
    • IIS 不停機更版設置
    • SQL Server 2019 Standard 繁體中文標準版安裝
    • WINDOWS共用資料夾的網路認證密碼放在哪?如何清除?
    • 如何設定 ASP.NET CORE 網站應用程式持續執行在 IIS 上
  • 專案管理
    • SSDLC (Secure Software Development Life Cycle)
    • 系統開發原則
    • MIS及專案管理-使用Redmine
      • 第10章 - [日常管理]MIS部門週會工作進度追蹤
      • 第11章 - [日常管理]MIS部門主管月會報告管理
      • 第12章 - [日常管理]機房工作日誌
      • 第13章 - [日常管理]MIS部門耗用工時及工作進度檢討
      • 第14章 - [日常管理]MIS文件知識庫
      • 第15章 - [日常管理]整理及管理分享
      • 第16章 - [異常管理]使用者問題回報系統
      • 第17章 - [異常管理]資安事件及異常紀錄
      • 第18章 - [異常管理]整理及管理分享
      • 第19章 - [變革管理]MIS的專案及專案管理五大階段
      • 第1章 - [MIS及專案管理]中小企業MIS的鳥事
      • 第20章 - [變革管理]MIS的新專案管理:起始階段
      • 第21章 - [變革管理]MIS的新專案管理:規劃階段
      • 第22章 - [變革管理]MIS的新專案管理:執行階段
      • 第23章 - [變革管理]MIS的新專案管理:監控階段
      • 第24章 - [變革管理]MIS的新專案管理:結束階段
      • 第25章 - [變革管理]整理及管理分享
      • 第26章 - [ISMS管理]ISMS平台整體規劃
      • 第27章 - [ISMS管理]ISMS文管中心
      • 第28章 - [ISMS管理]ISMS表單紀錄的管理
      • 第29章 - [ISMS管理]整理及管理分享
      • 第2章 - [MIS及專案管理]專案管理的概念及MIS應用
      • 第30章 - 初心、來時路及感謝:系列文章總結回顧
      • 第3章 - [MIS及專案管理]管理工具的選擇
      • 第4章 - [Redmine]Redmine的安裝及設定
      • 第5章 - [Redmine]Redime系統邏輯說明
      • 第6章 - [Redmine]自行建立及維護表單
      • 第7章 - [Redmine]專案版面的規劃
      • 第8章 - [日常管理]AR管理
      • 第9章 - [日常管理]資訊服務申請
  • 微服務架構
    • DDD + CQRS + MediatR 專案架構
    • 微服務架構 #2, 按照架構,重構系統
    • 淺談微服務與網站架構的發展史
    • API First Workshop 設計概念與實做案例
      • API First #1 架構師觀點 - API First 的開發策略 - 觀念篇
      • API First #2 架構師觀點 - API First 的開發策略 - 設計實做篇
    • 基礎建設 - 建立微服務的執行環境
      • Part #1 微服務基礎建設 - Service Discovery
      • Part #2 微服務基礎建設 - 服務負載的控制
      • Part #3 微服務基礎建設 - 排隊機制設計
      • Part #4 可靠的微服務通訊 - Message Queue Based RPC
      • Part #5 非同步任務的處理機制 - Process Pool
    • 實做基礎技術 API & SDK Design
      • API & SDK Design #1, 資料分頁的處理方式
      • API & SDK Design #2, 設計專屬的 SDK
      • API & SDK Design #3, API 的向前相容機制
      • API & SDK Design #4, API 上線前的準備 - Swagger + Azure API Apps
      • API & SDK Design #5 如何強化微服務的安全性 API Token JWT 的應用
    • 建構微服務開發團隊
      • 架構面試題 #1, 線上交易的正確性
      • 架構面試題 #2, 連續資料的統計方式
      • 架構面試題 #3, RDBMS 處理樹狀結構的技巧
      • 架構面試題 #4 - 抽象化設計;折扣規則的設計機制
    • 架構師觀點 - 轉移到微服務架構的經驗分享
      • Part #1 改變架構的動機
      • Part #2 實際改變的架構案例
    • 案例實作 - IP 查詢服務的開發與設計
      • 容器化的微服務開發 #1 架構與開發範例
      • 容器化的微服務開發 #2 IIS or Self Host
  • 系統評估
    • RPA 與 WebAPI 評估
    • 數位轉型:從現有系統到數位化的未來
    • 數位轉型:從現有系統到數位化的未來
  • 面試
    • CV_黃子豪_2024
    • HR 問題集
    • .NET 工程師 面試問題集
    • 資深工程師 問題集
    • 資深開發人員 / 技術主管
    • 題目
Powered by GitBook
On this page
  • API & SDK Design #2, 設計專屬的 SDK
  • 什麼是 “SDK”?
  • Microsoft Azure SDK
  • Flickr SDK
  • YOUR FIRST SDK!
  • SDK / API 的更新及維護問題?
  • API: APP 與 Service 之間簽訂的合同
  • SDK client interface: APP 與 SDK 之間簽訂的合同
  • 結論
  1. 微服務架構
  2. 實做基礎技術 API & SDK Design

API & SDK Design #2, 設計專屬的 SDK

PreviousAPI & SDK Design #1, 資料分頁的處理方式NextAPI & SDK Design #3, API 的向前相容機制

Last updated 1 year ago

API & SDK Design #2, 設計專屬的 SDK

什麼是 “SDK”?

SDK: Software Development Kit 的縮寫,一樣重點我擺在最後的 “K” (Kit) 上面。 SDK 的意思就是指讓你能順利開發軟體使用的套件。為何會跟 API 扯在一起? 通常 SDK 都是提供 API 的開發者,為了方便 其他的開發者使用他的 API 所提供的對應套件 SDK。

在過去都是單機的時代,API 除了早已存在 OS 內的 System API 之外,其他的 API 通常都需要你先在本機上安裝某些套件 之後才能使用,以 Microsoft 為例,就會有 XXXX redistributed 套件讓大家下載安裝 (一般使用者)。而如果你是開發者, 就會有 XXXX SDK 可以下載安裝。SDK 除了 redistributed 套件的內容之外,還會附上文件,vs template, sample code, 甚至搭配一些配套的工具。

現在雲端時代,就不大一樣了。API 可能已經是泛指某個雲端上提供的 HTTP API / REST API。服務老早就存在那邊,甚至文件 也都放在網站上了,所以單純的 “使用者” 不需要安裝任何東西,只要你看的懂文件,熟悉 HttpClient 就可以呼叫使用了。這時 SDK 的存在目的就有點不同了,他可能是為了讓你更方便的使用 API 而包裝的 LIBRARY,當然也可能搭配其他的工具及 sample code 等。

既然 SDK 可能會包含某些 Library, 那麼他就會是跟你使用的 OS, 程式語言,開發平台等等有關聯。舉個例子來說:

Microsoft Azure SDK

Microsoft 在 Azure 上面提供了相當多的 PaaS (Platform As A Service) 的服務,自然也有很多的 API 給所有開發者使用。 不過寫過 HttpClient 的人就知道,看完 API DOC 後,你光是要成功的呼叫 API,可能就要寫十幾行 code 了,SDK 裡面附上的 Library 就能讓你簡化這些千篇一律的呼叫動作。其他有些 client 端就能完成的運算或是檢查,效能的最佳化,甚至有些動作可以 在 client side 先行 cache 的部分,都有可能在 SDK 就處理掉。

看看 Azure SDK 的吧! 幾種主流的開發環境都有提供了 (.NET, Java, Node.js … 等等)。

Flickr SDK

再看第二個例子 Flickr。Flickr 是廣受使用者歡迎的相片服務,他也提供了完整的 API。在他的 API 下方就有個 “API 開發工具” 專區,放的就是各個平台的 SDK,包含 Flash (Action Script), C, Go, Java, .NET … 等等。 這些 SDK 甚至不是由 Flickr 官方維護的,很多連結都是連到 GitHub 上面的 open source project.

YOUR FIRST SDK!

定義搞清楚後,接下來就繼續延續文章的範例程式了。

上次的 Source Code 有兩個 projects, 分別是:

  1. Demo.ApiWeb 提供 API service 的 Web App

  2. Demo.Client.ConsoleApp 呼叫 API 的 console application

(2) 的部分,我只列片段就好,全列實在太長了… @@

/// <summary>
/// 一般寫法,直接呼叫 HttpClient 分多次讀取資料分頁
/// </summary>
static void ListAll_DirectHttpCall()
{
    HttpClient client = new HttpClient();
    client.BaseAddress = new Uri("http://localhost:56648");
    
    int current = 0;
    int pagesize = 5;

    do
    {
        Console.WriteLine($"[info] loading data... ({current} ~ {current + pagesize}) ---");
        HttpResponseMessage result = client.GetAsync($"/api/birds?$start={current}&$take={pagesize}").Result;

        var result_objs = JsonConvert.DeserializeObject<Dictionary<string, string>[]>(result.Content.ReadAsStringAsync().Result);


        foreach (var item in result_objs)
        {
            // filter: 調查地點=玉山排雲山莊
            if (item["Location"] != "玉山排雲山莊") continue;
            ShowBirdInfo(item);
        }

        if (result_objs.Length == 0) break;
        if (result_objs.Length < pagesize) break;

        current += pagesize;
    } while (true);
}

這段 code, 其實都在處理呼叫的細節, 而不是針對真正的問題處理。一開始為了準備 HttpClient 呼叫 API 的細節,就花了好幾行 做準備。成功呼叫後為了順利解析 JSON 的格式,又定義了 class 來讓 Json library 做反序列化的動作。最後為了有效率的分批 取回資料,又搭配了 server paging API + C# yield return, 做了額外的包裝,讓主程式可以用 for-each loop 或是 linq 來 使用資料。這一切就花掉了 150 行的 code.

想像一下,如果你的服務實在太紅了,全球有上萬個不同的開發者,都在使用你的 API。那麼上面的這些 code, 是不是每個人都要做 一次? 也許每個人做的不大一樣,但是應該也都大同小異吧! 我寫軟體最不能忍受的,就是存在兩份以上的 code 做一樣的事情…。 解決方法很單純,不過就是寫個 library, 然後讓大家共用他。

因此,這個 solution, 我就在切出第三個 project, 當作我們的 SDK, 也就是呼叫這個 API 共用的 library. 我做了一些調整, 先進行重構,把我想區隔開的邏輯切開,接著把它拆成獨立的 class library project。 調整後你的 APP 主程式會長的像這個樣子:

using Demo.SDK;
using System;
using System.Diagnostics;
using System.Linq;

namespace Demo.Client.ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch timer = new Stopwatch();
            timer.Start();
            
            // 方法3: 使用 SDK
            ListAll_UseSDK();
            
            Console.WriteLine($"* Total Time: {timer.ElapsedMilliseconds} msec.");
        }
        
        static void ListAll_UseSDK()
        {
            Demo.SDK.Client client = new Demo.SDK.Client(new Uri("http://localhost:56648"));
            foreach(var item in (from x in client.GetBirdInfos() where x.SerialNo == "40250" select x))
            {
                ShowBirdInfo(item);
                break;
            }
        }

        static void ShowBirdInfo(BirdInfo birdinfo)
        {
            Console.WriteLine($"[ID: {birdinfo.BirdId}] -------------------------------------------------------------");
            Console.WriteLine($"        流水號: {birdinfo.SerialNo}");
            Console.WriteLine($"      調查日期: {birdinfo.SurveyDate}");
            Console.WriteLine($"      調查地點: {birdinfo.Location}");
            Console.WriteLine($"     經度/緯度: {birdinfo.WGS84Lon}/{birdinfo.WGS84Lat}");
            Console.WriteLine($"          科名: {birdinfo.FamilyName}");
            Console.WriteLine($"          學名: {birdinfo.ScienceName}");
            Console.WriteLine($"中研院學名代碼: {birdinfo.TaiBNETCode}");
            Console.WriteLine($"        鳥中名: {birdinfo.CommonName}");
            Console.WriteLine($"          數量: {birdinfo.Quantity}");
            Console.WriteLine($"      鳥名代碼: {birdinfo.BirdId}");
            Console.WriteLine($"      調查站碼: {birdinfo.SiteId}");
            Console.WriteLine();
            Console.WriteLine();
        }
    }
}

主程式現在看起來就乾淨多了。程式裡面看到的,都是使用 APP 的人應該要處理的細節,包括從 server 取回資料後的篩選動作 (Linq), 以及篩選出來的資料顯示動作。

至於要連線 SERVER 的過程,全部被濃縮成這一行:

Demo.SDK.Client client = new Demo.SDK.Client(new Uri("http://localhost:56648"));

就好像我們使用 SQL server 一樣,建立好 connection 物件後就可以開始查詢了。至於 SDK project 的部分,來看看 SDK Client:

using Demo.SDK;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace Demo.SDK
{
    public class Client
    {
        private HttpClient _http = null;

        public Client(Uri serviceURL)
        {
            // do init / check
            this._http = new HttpClient();
            this._http.BaseAddress = serviceURL;
        }

        public IEnumerable<BirdInfo> GetBirdInfos()
        {
            int current = 0;
            int pagesize = 5;

            do
            {
                //Console.WriteLine($"--- loading data... ({current} ~ {current + pagesize}) ---");
                HttpResponseMessage result = _http.GetAsync($"/api/birds?$start={current}&$take={pagesize}").Result;

                var result_objs = JsonConvert.DeserializeObject<BirdInfo[]>(result.Content.ReadAsStringAsync().Result);

                foreach (BirdInfo item in result_objs)
                {
                    yield return item;
                }

                if (result_objs.Length == 0) break;
                if (result_objs.Length < pagesize) break;

                current += pagesize;
            } while (true);

            yield break;
        }

        public BirdInfo GetBirdInfo(string serialNo)
        {
            HttpResponseMessage result = _http.GetAsync($"/api/birds/{serialNo}").Result;
            var result_obj = JsonConvert.DeserializeObject<BirdInfo>(result.Content.ReadAsStringAsync().Result);
            return result_obj;
        }
    }
}

我的目的是先讓開發者建立 client 物件後,其餘存取 SERVER 的動作都透過這個 client 來進行,所以 SDK 這個 project 就把 BirdInfo 物件的定義, 還有 HttpClient 使用的細節都封裝了起來,也把 server side paging 的細節用 C# 的 yield return 方式轉譯封裝起來,第一版的 SDK 就完成了。

效果如你所見,使用上不再需要 APP 開發者處理呼叫 API 的細節了。所有想要用我 API 的人,只要拿 SDK 去使用就可以了。當然 前提是你也要用 .NET, 如果你用其他語言 (如 Java), 那如上篇文章類似的動作你還是得照做一次。

SDK / API 的更新及維護問題?

問題到這邊就結束了嗎? 如果以第一版來說,的確結束了。接下來我們來看看 API 或是 SDK 改版會面臨的問題。我先用一張圖來 描述 APP / SDK / API 三者之間的關係:

這裡面有四個部分,分別是 APP, SDK, API, SERVER。其中由於到目前為止,API 都只是紙上的 “定義”,所以我用虛線表示。

專案
誰負責維護?
更新時間

API

原廠

立即更新

SERVER

原廠

立即更新

SDK

原廠

原廠立即發行,開發者決定何時更新至APP

APP

開發者

開發者決定

好,問題開始浮現出來了。萬一 API 已經不敷使用,或是 API 設計存在問題,需要修正的時候… SERVER 跟 API 是原廠可以 立即處理的,沒有問題。如果對應的 SDK 也需要修改,原廠也可以立即發行,沒有問題。

問題在於 APP 開發者啊,如果他沒辦法立即跟上最新版,則會有一段時間,有使用者用的 APP,是用舊版的 SDK,呼叫新版的 API SERVER。 會造成什麼結果,其實是無法預期的。既然有這種風險,就該在他真的發生之前想辦法去控制他才對。

我舉個例子,假設這次的修正,是調整 BirdInfo 的一個欄位名稱,把 SerialNo 改成 BirdNo, 我們只改 SERVER,但是不改 SDK,看看 會發生什麼事情?

修改前: Demo.ApiWeb/Models/BirdInfo.cs

public class BirdInfo
{
    public string SerialNo { get; set; }
    public string SurveyDate { get; set; }
    public string Location { get; set; }
    // 以下略過
}

修改後: Demo.ApiWeb/Models/BirdInfo.cs

public class BirdInfo
{
    public string BirdlNo { get; set; }
    public string SurveyDate { get; set; }
    public string Location { get; set; }
    // 以下略過
}

真糟糕,雖然不會有錯誤,但是程式執行結果也不正確了。一筆資料都查不出來…

* Total Time: 1932 msec.
Press any key to continue . . .

這個問題直到 SDK 對應的 BirdInfo 定義也跟著更新後才解決:

[ID: B0443] ----------------------
        流水號: 40250
      調查日期: 2013-06-21
      調查地點: 玉山西峰下
     經度/緯度: 120.937047/23.468776
          科名: Reguliidae
          學名: Regulus goodfellowi
中研院學名代碼: 380442
        鳥中名: 火冠戴菊鳥
          數量: 1
      鳥名代碼: B0443
      調查站碼: C37-02-04


* Total Time: 295 msec.
Press any key to continue . . .

如果這是 real case, 那麼緊急解決線上的問題,到這裡應該告一段落了。不過更新的問題一定會碰到,要徹底解決 這問題的話,還是得從架構設計上著手。這過程我抓了兩個關鍵的原因,需要從架構設計面解決:

  1. API 的定義,必須有具體 (code 化) 的表達方式,來確保兩端的定義是一致的

  2. 向前相容的問題處理,在 API 定義異動時,sdk 要有能力偵測並且正確的回應

接下來就針對這兩點,我們來看看怎麼持續改進 SDK 的 implementation

API: APP 與 Service 之間簽訂的合同

對比到現實世界,接案子時都會簽合約,雙方約定的事項條列在上面,之後一切按照合約條款進行,這是現實世界的規矩。 套用到這邊也一樣,API 的定義也是講一樣的事情。在 .NET 裡面,你可以用 interface, 或是定義 data object 之類的方法 讓前後端共用,編譯器就能幫你做完後面的檢查及驗證等等苦工。只要編譯能通過,就代表雙方是符合合約規範的。

後面的例子,我會把 API 定義的部分,再拆成獨立的 project: Demo.Contracts 。將來,除非你的 API 要正式異動,否則 就不要去改這個 project. 在板控系統上的紀錄,這個 project 的任何異動,就視同 API 的修訂。在我們團隊實際的運作也是如此, 因此這個 project 通常會限定異動的權限,必須是 architect, 或是 team leader, product owner 之類的角色才有權限調整。

原本的架構,調整後變成這樣,前後端都必須依照 contracts 的約定,來進行溝通:

首先,在整個 solution 內新增 Demo.Contracts 這個 class library project, 每個其他的 project 加入 reference. 接著就把需要統一規範的 interface or data contract 搬過來。

第一個要搬移的,就是前面案例碰到的 BirdInfo.cs,分享一下我調整的過程,可以最輕鬆的完成這件事:

  1. 由於改專案,我希望同時調整 namespace. 因此我先個別在兩個專案內把 BirdInfo 這 class 的 namespace 改成 Demo.Contracts, 透過 refactory 功能可以自動修正所有使用到他的 code

  2. 完成 (1) 之後,再把它搬到 Demo.Contracts project 內,由於 namespace 早就先改好了,所以只要單純的搬移 code

  3. 調整 projects 之間的 reference, 原本的 project 都加上 Demo.Contracts 的參考,重新編譯確認無誤後就完成了。

這個過程中,能夠善用 refactory 的話,可以替你節省不少力氣,否則你就要搬完 code, 改完 reference 後,編譯看看有多少 error 再一條一條修改。修正後的 solution 結構,長的像這樣:

SDK client interface: APP 與 SDK 之間簽訂的合同

接下來,很容易被忽略掉的一點,就是 APP 與 SDK 的相容性。還記得前面的表格嗎? SDK 是原廠維護的,而 APP 是開發者維護的。 原本 APP 應該直接按照 API 規範來使用 SERVICE 的,所以之間的合約只要處理 API 就夠了。但是 SDK 替 APP 解決了這段問題, 因此 APP 開發者真正要面對的規範,就往前提到 APP 與 SDK 之間了。

講具體一點,就是 SDK 有任何異動,是否能維持 APP 的相容性? 這邊的相容性,我分成幾個情況來說明:

  1. APP 只要更新 SDK DLL,APP 不需要重新編譯

  2. APP 需要更新 SDK 及重新編譯,不需要改 Code

  3. SDK 大版本更新,APP 需要配合 SDK 重新調整 Code

一樣,我們用同樣的技巧,訂定 SDK contracts, 來解決 APP 跟 SDK 之間的問題。架構圖再調整一下,變成這樣:

Demo.Contracts 我也增加了 SDK Client interface:

namespace Demo.Contracts
{
    public interface ISDKClient
    {
        IEnumerable<BirdInfo> GetBirdInfos();
        BirdInfo GetBirdInfo(string serialNo);
    }
}

原本的 Demo.SDK.Client 配合調整,同時我也改成 Factory Pattern, 控制 SDK Client 產生的過程

namespace Demo.SDK
{
    public class Client : ISDKClient
    {
        private HttpClient _http = null;

        public static ISDKClient Create(Uri serviceURL)
        {
            return new Client(serviceURL);
        }

        private Client(Uri serviceURL)
        {
            // do init / check
            this._http = new HttpClient();
            this._http.BaseAddress = serviceURL;
        }

        public IEnumerable<BirdInfo> GetBirdInfos()
        {
            // 略
        }

        public BirdInfo GetBirdInfo(string serialNo)
        {
            // 略
        }
    }
}

最後 APP 呼叫端的 Code, 只改了取得 client 的部分。原本是:

Demo.SDK.Client client = new Demo.SDK.Client(new Uri("http://localhost:56648"));

// Do something...

修改後變成:

ISDKClient client = Demo.SDK.Client.Create(new Uri("http://localhost:56648"));

// Do something...

一樣,這樣以後就有一樣的效果了。除非這個 interface 內容有任何異動,否則你就再也不需要擔心 interface 異動造成問題了。

結論

到目前為止,整個 API service 的開發及部署架構已經出來了。除了 Demo.Client.ConsoleApp 之外,其他都是原廠的 API service 開發團隊負責的。隨著 API 的版本推進,不斷的 build solution, 不斷的 deployment, 做好 CI / CD 就沒問題了。

SDK 到這邊就結束了嗎? 還沒有… 下一篇要接著來講 API 的版本控制,及如何做好向前相容的版本管理。敬請期待 :D


最後補充一下,這邊說明的作法,我都已不需要太多外加的 framework, 或是套件為主,盡可能的自己實作。原因很簡單,倒不是 說要自己發明輪子,而是不經過這樣的過程,你 (尤其是有打算朝架構師發展的朋友) 很難徹底理解為什麼要這樣做的原因。理解之後 你可以在適當的領域,挑選適合的 framework 來使用。

之前在 facebook 上面曾經分享過,現在的技術發展太快了,不論做什麼事都有一堆框架 (framework) 可以選擇。年輕的工程師 沒有足夠的時間跟經驗,從基礎開始學起,一開始就得面對龐大的框架,往往分不出甚麼是基礎,什麼是 platform / language, 什麼 是 framework (舉例來說,javascript engine / dom / jquery / react / vue … 之間的關係你說的清楚嗎?)。當面臨 framework 的世代交替時,換到下一個新的 framework 能留下的經驗跟能力就很有限了,碰到 framework 不足的部分,或是根本 都還沒有適合的 framework 時就會束手無策。

我這種年紀的傢伙比較幸運一點,當年還沒這麼多 framework, 我還有機會把基礎學好再來研究 framework, 因此現在看到不同的 framework, 我還能保有選擇跟理解的能力,碰到不足的部分我也有能力找出解法。我希望把這些經驗整理下來,分享給需要的朋友們。

Source Code 一樣可以直接從 GitHub 上面拉下來看,不過這篇的進度 請參考 . 隨著文章一路寫下去,Source Code 也會隨著一直修改,如果你一直抓最新版的,應該會跟文章內容對不起來.. 請特別留意。

架構圖

不知各位有無留意到這細節? 前面的案例調整了 BirdInfo 這資料格式的定義,導致 service 端 (Demo.ApiWeb) 與 SDK 端 (Demo.SDK), 都需要 手動 的調整 code 去配合。手動調整事小,沒有一個機制來約束,確保這件事正確無誤完成事大。這邊我很愛用的機制,就是以前 使用 (windows communication foundation) 學來的技巧: 制定合約 (contract)

API service contract
img
sdk contract

比如這篇提到的 API 規範,我用 .NET 的 interface 來實作,實際上有其它的技術已經可以做好這件事了 (如 等)。 講的 server side paging + yield return, 你一樣也可以找到現成的 ODATA 來取代。甚至更早的一系列文章,講到 API key, 我用了 AES 數位簽章的方式來實作也是。實際使用時,你可以選擇一樣觀念做出來的 JWT… 這些案例很多,我就不一一細說。 如果你是實際上的專案需要,可以考慮使用這些現成的框架。成熟的框架可以解決很多實際上會碰到的問題,但是自己土炮一次則可以 讓你了解框架為何耀這要做的原因跟理由。

下載網址
說明頁面
上一篇
dev-SDK 分支
WCF
swagger
上一篇