2019年4月22日 星期一

敏捷的軟體架構設計:可擴展的軟體架構

敏捷底下的軟體架構

圖片取自:http://m.001zf.com/newshow.php?itemid=696

前一篇,我分享了您的軟體架構夠敏捷嗎?而我寫過許多關於架構設計的相關文章,但今天這一篇,可能是最難寫的一篇?因為,在(確定的需求/沒有 Deadline 的時間壓力下),寫程式並不件難事,對吧?那麼真正難的在哪裡?真正難的是在,有限的時間 與 資源內,發展出(符合客戶需要/為客戶產生價值)的商用軟體,且這個軟體須具備一定的可延展性、擴充性、可維護性,並在企業的可控制的成本範圍內,或者,在敏捷的每一個迭代開發裡,逐漸發展成為客戶所需要的軟體,對吧?軟體公司為企業創造與量身訂做軟體也是為了賺錢,若軟體開發所投注的成本成為無限的話,虧本的生意沒有人會想做的。


傳統的軟體開發產生的架構問題

圖(一)、傳統軟體開發的前身 SDLC (Software/System Development Life Cycle)

SDLC 是早期用以描述一個資訊系統,從無開始規劃、分析、建立、測試、直到最終完成的一個過程。看似無任何問題,事實上,它也運作了幾十年,似乎也確實沒有任何問題,許多團隊也是能夠創造與交付出符合客戶需求的軟體,可是,慢慢的,許多團隊與軟體公司漸漸地發現,當需求變動性高、需求不確定因素高的時候、或者在許多實際的市場機制下,軟體的『開發』與『發展』逐漸與慢慢地跟不上時代的演進 與 市場的變化了。這怎麼說呢?因為當不了解需求、不了解客戶想要的價值時,你所花的(工/成本)絕大部分是浪費的,因為軟體開發越到後期修復的成本是越高的。

以傳統瀑布式開發來說,我得先做完需求分析、才可進行系統的設計,而當系統設計完成後、才可開始進行系統的(程式碼撰寫/Implementation/實作),但問題來了,什麼叫做【系統設計完成】?難道我得在一開始,弄清楚客戶的所有需求?因為系統設計完成、也代表架構設計完成了?如果軟體架構可以在程式碼撰寫前就確定,這不就代表需求必須固定?問題是…. 這是不可能的….. 這其實是一個【雞生蛋、蛋生雞的問題】

圖(二)、雞生蛋、還是蛋生雞?


0 缺陷的架構 = 0 擴展的軟體架構

在早期,曾經有團隊/軟體公司 將所有功能均提供在軟體產品裡,也就是大雜燴的方式,嘗試滿足市場各種不同的需求,筆者很早期待過的軟體公司也還真的經歷過這樣的模式,而在當時,筆者也還真的認定為,這是理所當然的,因為這麼一來,我們的軟體架構只需要被設計一次,如果我們能涵蓋市場裡 70% 的客戶的需求,而我們也能夠接下這些客戶,而不費吹灰之力,就能快速的開發出客戶所需的軟體,因為我們只是把開發好的眾多的模組(Module/Functions)、根據同一個 Domain Known-how 所可能會需要的功能,都加以開發好,並分門別類。再透過抽換、或抽離,客製成目前這個客戶所需的一個軟體。

這樣的方式看似很美好,但是事實上淺藏極大的架構問題,為什麼我這麼說呢?

(1). 使用單一功能滿足所有企業的使用者難以永久滿足… (因為總是會有需要客製的部分)

(2). 功能一定會微調、若為此開立分支(Version Control)將產生另一個災難

(3). 客戶一多,表示你的功能分支越多、這又會產生另一個災難

(4). 軟體架構因為為滿足 A 客戶的某 Function ==> 而導致 B 客戶另一個 Function 失敗

(5). 疊床架屋的軟體終究越來越龐大、每修正一個 Function 或是 擴增一個 Function 都會是一個耗時耗力的過程

(6). 更不用說測試了….

…. 還有說不完的問題…..


軟體架構應該要可以逐步的發展

圖(三)、Implementing Lean Software Development: From Concept to cash

再近代的軟體開發理論裡,包括在『Implementing Lean Software Development: From Concept to cash』的書中,都提到了應該不需要等到軟體架構都完全確定後,才可以開始撰寫程式。且實際的軟體開發中,筆者最近2-3年來實做專案時,嘗試以敏捷方法與迭代方式進行專案的開發,不過筆者以 OOAD/UML 來驅動 Domain Model 的設計,以 Use Case Model 來確定需求的 boundary,但我是搭配 User Story Mapping ,就是用一種方法,一種如何將你最後產出的『用戶活動 (User Activity)』與 最後更加細節的『用戶故事 (User Story)』推展至 Use Case 中,但我不是完全的設計,有些朋友看見 UML 後就會認為會花費許多時間在做文件,其實事實上,我通常只需要半小時就可以畫出一個三角關係的 UML 圖形,只是這個圖形必須夠抽象。有興趣的讀者可參考我另一篇文章『決戰 OOAD 系列(一):使用 UML 分析的黃金三角』,並結合 Clean Architecture 的插件式軟件架構,逐步展現出架構的全貌。詳細關於 Clean Architecture 在實務上的例子,可以參考筆者先前的文章:『從使用者需求、談架構設計(二)- Clean Architecture 一個整潔的架構篇』。

傳統的瀑布式 Waterfall 為人所詬病的就是,當你確定完需求、開始做系統設計、接著開始實作 Programming 時,而這時候,往往已經過度設計了。


測試是迭代開發的最佳保護網

在 『Specification by Example中文版:團隊如何交付正確的軟體』一書中提到,因為敏捷的思想,幾乎改變了整個軟體開發對於【交付階段】的看法,因為我們不可能凍結『變化』、市場隨時會改變,透過『迭代』為基礎,使的 Spec 的實例化變得可行,而且可獲得極大好處,加上將軟體開發切割成多個衝刺(Sprint),要在每一個極短的衝刺時間內、 完成一個交付階段 (MVP, Minimum Viable Product),每一個衝刺所需要完成的代辦項目(PBI)得透過價值來排列優先順序、並消除常見的重工 (Rework) 的問題=漸少浪費,這與先前提到的 Clean Architecture (插件式設計) 避免過度的設計也完全的相互輝映(避免更多浪費與技術債),從價值來決定軟體 Scope 規模。


TDD/ATDD

而然而,這樣就夠了嗎?在極短的時間內交付 MVP 增量開發+根據功能設計勘用與可擴展的軟體架構時,若沒有測試的保護,軟體開發仍然存在極大的風險,即便是敏捷開發也一樣,沒有撰寫測試的軟體,除了測試成本無法降低外,也代表著系統物件的職責容易牽扯在一起,也表示容易設計出系統複雜高的軟體架構,複雜度高也導致軟體不容易維護。所以,結合 TDD 與 ATDD 的開發方法會是迭代開發中,最佳的保護網。

待續…

2019年4月10日 星期三

ASP.NET Blazor 怎麼處理狀態?(二)- 保存預設範本的 Count 值

前言

上一次的文章【ASP.NET Blazor 怎麼處裡狀態?】中,筆者說明了幾種在 Blazor 上保存狀態的常見用法,但是當時筆者無法在 [YourProjectName].Client 的專案裡安裝『Microsoft.AspNetCore.Http』、『Microsoft.AspNetCore.Http.Abstractions』、『Microsoft.AspNetCore.Http.Feature』這三個套件 均無法安裝成功,後來我發現,無法成功是正常的,這因為 [YourProjectName].Client 是執行在 browser 的沙箱(Sand box)裡面,試想,這三個套件都是相依於在沙箱裡,而這個沙箱內又以 Mono Runtime 來執行 .NET Standard,如果 .NET Standard 本身不支援那個 API 也無法執行對吧?更何況又在 browser 的沙箱環境中。這更不用說 Microsoft.AspNetCore.Http.Abstractions 裡還參照了在 Mono Runtime 裡不支援的 advapi32.dll & kernal32.dll 等

就算技術上可行,安全性其實也不允許你在沙箱內部存取外部 OS 資源。

也就是說,上次的那個作法是錯誤的,我們根本就不應該在 [YourProjectName].Client 裡面安裝那三個套件,應該回頭過來思考,我今天的需求是,要從 browser 將 COUNT 內容傳遞到 Server 上,並保存下來!也就是說,我們應該將 [YourProjectName].Client 這樣的專案當作是一個執行在前端browser 中包含 Front-End Framework 的 SPA 應用程式,這麼一來,是不是就清楚多了?因為 Client 是執行在 Browser 中,若不是 Blazor ,傳統 SPA 作法也是透過 JavaScript 執行 Ajax 的 Call Back 呼叫,將 Count 傳遞至 Web Server 上保存下來對吧?所以,Blazor 也會是相同的做法。


專案結構


正確做法是什麼?

(1). WebBlazorStateTest1.Client 的 Counter.cshtml

在 Counter.cshtml 裡,透過 view inject 注入 HttpClient 來撰寫呼叫 Web API 傳遞 currentCount 到後端並保存下來,由於程式碼非常簡單就不多做說明了。


(2). 啟用 MemoryCache


(3). 在 WebBlazorStateTest1.Server 的 SampleDataController 裡增加兩個 API 方法


接著,我們可以在 Counter 頁面得到保存 Count 數的效果如下圖:


但是,這時,如果開另外一個 browser 的話,別的使用者也會看見這個 Count 數,也就是說,它就像個 static 變數一樣,等於是共想於所有使用者,這似乎不是我們想要的,如果我們要每個人可以保存、並有自己的 Count 數值該怎麼做呢?

其實,最簡單的方式,就是用 Session,或者,在 Cache 的 Key 加上 By 登入的 User 的 userId 唯一值,像是 (”_user_id_xxxx”+”_COUNT”)使不同使用者不會重複即可,並將 Session 的 Provider 導向 Redis,以便適用 (Scale-Out/Load Balance )環境中,達到 狀態共享/高效能。

什麼是 gRPC(二)- 來撰寫第一個 Hello World 吧!

前言

上一篇 什麼是 gRPC,筆者大致介紹了 gRPC Service 的基本概念,這一次,我們就來小試一下身手,撰寫一個 Hello World (迷之音:嘗試新技術時,總是得先來一個 Hello World ~我不想違背這良好傳統阿阿阿阿)


使用環境

  • Visual Studio 2019
  • .NET Core 3.0.100-preview3-010431


開始進行

撰寫 gRPC 除了先準備好基礎環境、選定一種 作業系統/ 程式語言 Language 外、(Framework/SDK/Runtime)等環境備妥後,就可以開始撰寫,

(1). 開始撰寫 gRPC 第一步就是先定義 .proto 協議了

上一篇筆者提到,gRPC 的 Server 與 Client 必須使用相同的協議才可進行溝通,一般來說,你應該使用 protoc.exe 編譯器先行編譯過 greet.proto 協議檔,並產出 C# 程式碼檔案後才可繼續,而在 Visual Studio 2019 裡,拜 MSBuild 之賜,已經有人預先寫好 .Target 檔案了 (什麼是 MSBuild?什麼是附檔名 .Target 檔案可參考:MSBuild .targets 檔案

筆者先撰寫一個 SayHelloWorld 方法如下:

在協議裡,除了在 service Class 撰寫要公開存取的方法外,必須在提供『傳入訊息型態』、『回傳訊息型態』,這邊我先定義兩個 message,一個是『HelloMyFirstgRPC』、另一個是『HelloMyReply

在撰寫 proto 協議檔時,建議先決定 message 回傳型態與傳入的型態,那麼 Visual Studio 2019 會有 InteillSense 的完整支援。不得不說 Visual Studio 2019 在 .proto 協議編輯上的支援已經算不錯了。

注意:
一般來說,Visual Studio 2019 會在背景裡自動編譯 .proto 協議檔案,但始有可能不會有任何提示,所以您也可以自行使用命令列工具進行手動編譯,如下圖,若編譯失敗會出現錯誤訊息,如下圖顯示 return 關鍵字錯誤:

protoc.exe 編譯器 & 相關 MSBuild Tools 會隨著 NuGet 套件在 Visual Studio 2019 進行自動還原的時候,安裝在你的 User Profile 的 .nuget 快取路徑下:C:\Users\Gelis\.nuget\packages\grpc.tools\1.19.0-pre1

我目前安裝的是 preview 1

我的完整的 protoc.exe 編譯器路徑是:"C:\Users\Gelis\.nuget\packages\grpc.tools\1.19.0-pre1\tools\windows_x64\protoc.exe"

而手動進編譯成功的情況如下:


(2). 撰寫 C# 的伺服器方法

完成 .proto 協議之後,沒有實際的 C# 伺服器方法,程式還是跑不起來的,所以我們必須在 GreeterService.cs 裡,加入一個 SayHelloworld 方法,如下:



(3). 撰寫 Client

完成 Server 端的伺服器方法後,當然就是接著住撰寫 Client 呼叫的程式碼了,Visual Studio 2019 在這裡都有完整的 InteillSense 的支援,不得不再說一次 XD,實在太棒了!

最後,完成的 Client 完整程式碼如下:

如上程式碼,由於是拿 Visual Studio 2019 內建範本來練習,紅色部分是增加的部分。在呼叫遠端方法時,建立 HelloMyFirstgRPC() 類別時,我們傳入的 UserName 就是我們在 greet.proto 協議中所定義的回傳 message 中,我們自己給的,還記得嗎?

到這裡,基本上就完成了。


(4). 測試程式

我們先將 Server 執行起來,再到 "bin\Debug\netcoreapp3.0" 路徑下,執行 "gRPCServer.Client.exe" ,如果沒有任何意外,應該可以將 UserName 傳入至 Server 中,由 Server 的伺服器端方法重新組字串 "test My gRPC"+UserName 並回傳至 Client 的 .NET Core 3.0 的 Console App 並秀出結果 。

執行結果如下:

程式也如預期般運作,如何?是不是非常的容易?


後記:

這雖然不是非常新的協定,目前開發上仍然不是那麼普及,不過在某些大型企業內部已經高效運轉中,雖然的的優勢就是在成為標準之前就已經在許多實務上經過許多考驗,但是面對許多各種(協定/標準)的競爭下,會不會脫穎而出還有待觀察。

2019年4月4日 星期四

什麼是 gRPC ?

圖片取自:https://grpc.io/docs/guides/

gRPC 是什麼?能吃嗎?

在安裝了 .NET Core 3.0 之後,眼尖的朋友應該會發現,在新增 ASP.NET Core 網頁類型專案裡,會看見一個 gRPC Service 類型的專案,其實它也不是什麼很新的東西,早在十多年前 Google 就使用這樣的 RPC 協定進行一些大量的可靠性微服務的存取,聽到這,難道現有的 Web API 不可靠嗎?其實不是的,它主要用在移動設備等提供高效簡單且跨平台的通訊與熱插拔的驗證機制。


gRPC 的運作原理

gRPC 顧名思義,是種基於 RPC 的通訊協定,不同於 Web API 走的傳統 HTTP 基礎協定,它有點像是 Socket ,但應該說是基於 Socket,或精確地來說它是基於 HTTP/2 的協定,gRPC 是種協議緩衝協定,它必須事先定義 IDL (Interface Definition Language) 描述檔,看到 IDL 對於一些熟悉 COM/DCOM 的朋友一定非常的熟悉,對!沒錯!就是類似的東西!但,這邊的 IDL 是用一種副檔名為 .proto 檔案來是先定義在分散式環境中,要傳輸的序列化結構資訊,當然它也可以跟 JSON 一起使用。

也就是說,要使用 gRPC 您必須先定義 .proto 協議,然後透過 Compile 的支援,編譯成特定語言框架。而很高興的是,C# 也支援了,然而其實根據官方的資料顯示,C# 應該在 .NET Core 2.1 開始就支援了,只是到了 .NET Core 3.0 才有樣版的支援。

而使用 gRPC 的協議緩衝區的服務可以做到 Stub 的機制,也就是說,它就像是許多 RPC 一樣,Client 在呼叫 Server 端時,其實是對 Stub 進行叫用,Stub 在這就像個緩衝區一般,但更細部的說,應該是一個遠程服務的替身,這應該又讓許多熟悉 COM/RPC 通訊的朋友覺得熟悉了起來,是的!沒有錯。


在 .NET Core 3.0 裡使用 gRPC

目前支援 .proto 擴展為 gRPC 協議緩衝區的編譯器只有 .NET Core 了根據 Github 上的說明 .NET Framework 4.5+ 以上或 Mono 4+ 以上也可支援,而 gRPC 也完全的跨平台。要在 .NET Core 3.0 裡建立非常容易,因為已經有內建範本的支援了,就是建立一個 ASP.NET Core 3.0 類型的 Web 專案,選擇 gRPC Service 即可。

建立好的專案會有兩個:一個就是 gRPC 的 Server,另一個就是 gRPC 的 Client

在範例程式碼當中,可以看到 Server 與 Client 同時都定義了 greet.proto 檔案,這是它們彼此溝通的協議,預設的範本裡定義的一個類別 Greeter ,並提個一個 SayHello 方法。

.proto 協議可以被編譯特定平台的語言,在這裡我們可以經由 Visual Studio 看到 greet.proto 被編譯的結果。

當 NuGet 還原如果完成,應該可以直接執行,它不是走 HTTP 協定,所以無法使用 PostMan 進行測試,gRPC Server 執行畫面如下。

這時候我們可以 Run 起 Client 來對 gRPC Server 進行 SayHello 的呼叫,他會接受 Server 回傳的 message 內容,並顯示在 Console 畫面中。

很有趣,對吧?待續…

下次,我們再討論一些實際的應用。


References:

https://grpc.io/docs/guides/

https://grpc.io/docs/quickstart/csharp.html

https://developers.google.com/protocol-buffers/docs/overview

https://grpc.io/about/

https://grpc.io/blog/principles

https://github.com/grpc/grpc/tree/master/src/csharp

2019年4月2日 星期二

您的軟體架構夠敏捷嗎?

圖片來源:https://www.magzter.com/articles/488/256119/5a2a9554df1c0

什麼都要敏捷?

近年來,敏捷概念幾乎已經深入人心,不光是軟體業談敏捷,慢慢的可以看見各行各業也在談敏捷的思想,前幾次的 Agile 活動裡,不再像以前一樣,只會出現軟體相關產業的面孔。為什麼敏捷思想開始深入人心?其實是因為『變化』不只存在於軟體產業,要面對未來、面對變化、面對挑戰,唯有敏捷以對,隨時快速地調整自己,提升自我能力,靈活的面對這些改變,隨時再出發、隨時再出擊。


測試理念的崛起

測試可說是近十年來,發展最快速的軟體開發理論之一,早期的軟體開發將測試定義為軟體開發生命週期裡的其中一個階段,通常後期進行,也就是當軟體完成到某一程度時才進行,但是當軟體完成到某一程度時,也代表著你要對軟體做任何的需求改變、功能的改變、又或者是新增功能,所需的成本可能是非常的高的,這也是傳統的瀑布式開發慢慢為人所棄用的最主要原因之一,因為人們慢慢地察覺到,軟體的『測試』應該與『開發』這件動作密不可分才是,又或者說,測試其實應該算是軟體開發的其中一個動作,而且是非常重要的一個動作,因為經由測試,你曾能夠確認軟體正常運行無誤、經由測試才能確保軟體能夠被交付、符合客戶需求 + 如預期的運作。

而早在十多年前,TDD, Test Driven Development 的大師 Kent Beck 所撰寫的 TDD by Example 一書就已經提到這樣的觀念,先撰寫測試的程式碼 (Unit Test),在撰寫 Production 的程式碼


從 TDD/ATDD 談傳統 SDLC 的缺陷

在 ATDD 裡提到了左移測試的 V-Model 模型,這個被討論了十多年,只是不幸的是左移這件事常常被誤解成只是將測試移轉至左側,而忽略了由上而下的測試進行。然而,當然,許多的軟體開發者也都知道,在軟體開發生命週期越後期測試所發現問題的時候,修復的代價就越高,這個狀況幾乎可以解釋為傳統 SDLC 的缺陷了,所以,這表示什麼?這意味著在軟體開發生命週期裡應該盡早進行測試才能夠降低這些風險,因為在這些年來因為沒有盡早測試而產生的問題如下:

  • 測試人員可能沒有參與軟體的初始規劃,通常導致測試對系統了解有限+最後分配給測試的資源又嚴重不足
  • 許多需求,架構和設計缺陷都沒有再一開始被『發現』和『修復』,直到實作後而浪費了大量的精力修正
  • 隨著更多軟件的生成和集成,越到後期,修復和回歸測試變得更加困難(代價非常高昂)
  • 封裝使得執行白箱測試變得困難,並且在測試期間實現高水平的程式碼涵蓋率
  • 不足夠的測試與修復缺陷的時間,而延長了可以發行新版本的週期,這會產生『技術債』的波動,如果專案越大,越可能會使產品陷入一個困境中


使用 TDD 也不代表不需要 Modeling 分析

許多人都誤解了一件事情就是,直接學習 TDD 而跳過學習 Modeling 的分析像是 OOAD/UML 之類的,許多人以為不需要特別分析與設計物件就會自動跑出來,這其實是不太正確的,因為 TDD/BDD/SBE 少了強調建立 Domain Model 的這個步驟,認為反正找到的物件若不恰當,也可以透過重構不斷地來改變產品的品質。但是這其實會產生另一種技術債與浪費

TDD 在開始撰寫測試時,也是要經由一個分析的過程找出 Use Case Model 並透過 modeling 分析或者您慣用的 UML 分析也可以,找出 Domain Model 後,才可確定 User 要的是什麼(價值/功能),許多人會覺得 OOAD 很難學,相較之下 TDD 容易上手多了,但是事實上,軟體開發這些工作並不會減少,沒有 Use Case Model 與 Domain Modeling 的分析過程,你所撰寫出來的 Production 只是可能在養大另一個技術債而已,你的軟體架構應該還是來自於使用者需求才是。

雖然(敏捷/TDD)都不鼓勵事前的大量分析,但是也不表示完全不分析啊。


軟體架構的敏捷?

有些讀者看到這裡可能會覺得,軟體架構跟敏捷有什麼關係?透過插件式軟件架構 Clean Architecture 便可說是完全為了這個問題最佳解決方案了,先前筆者的課程『跨平台的 Web API Framework 框架開發』便是在說明這件事情,下一次,筆者會將目前正在研究的使用 ASP.NET Blazor 來實作的 Easy Architecture 架構的實現, Easy Architecture 是我所開發的基於 Clean Architecture 架構的一個實現。


References:

https://insights.sei.cmu.edu/author/donald-firesmith/

https://insights.sei.cmu.edu/sei_blog/2015/03/four-types-of-shift-left-testing.html?fbclid=IwAR24flSk6qsS8OKuuHSl5NtfZS7_Mf5ZhynN5erkDN5jUMLk0b-O4QygS64

https://www.infoq.com/articles/quick-guide-atdd