2023年5月23日 星期二

出一張嘴寫程式的時代真的會來嗎?

古時候,有個字叫做人機介面,也就是人和機器溝通的通道。一直以來,人和機器的溝通是不直接的。

操作電腦,你得透過滑鼠、鍵盤、或觸控螢幕,而不是像你跟身邊其他人溝通時,可以透過對話和語言來進行。

這是一種阻礙,即便由來已久,即便你已經習慣,這仍是一種阻礙。
然而阻礙帶來了限制,但也帶來了機會。

所以開始有程式設計師這個行業,程式設計師把人們的需要,翻譯成電腦可以看得懂的程式碼,操控電腦來進行人們想實現的功能。自從電腦誕生以來,這個活兒就沒消失過。

上週,我去集英信誠和百敬老師聊天(其實是錄影),從ChatGPT談到哲學和宗教,中間有一個話題,我們談到了人機介面即將改變。在過去,開發人員必須得把輸入(input)變成結構化的資料,例如表單、欄位、這樣電腦才有機會能夠理解,從而進行處理和運算。

先不管ChatGPT輸出的正確性如何(如果你上完我們的課就知道,現階段根本不該期待一個生成式AI所產出的東西是正確的,它只是依照格式產生看起來像樣的內容,而非正確的內容,內容的正確性得靠你的加工),但這一波ChatGPT會造成轟動的潛在原因是,它似乎聽得懂你說的話,而這才是重點

Chat Bot幾年前早過炒過一波了,結果如何? 成功了嗎?
別問我,你自己說說你對現在哪一家銀行的AI助理或是聊天機器人滿意的? 幾乎沒有。而過去Siri能夠回應你的,也總是那麼幾個簡單的指令,複雜一點的對談,整個就不行了。

但這一波ChatGPT帶來的第一個驚訝,是你發現,它似乎還真聽得懂你說的話。
沒錯,ChatGPT從去年11月底問世到今天才半年,你跟它的溝通大致上不會讓你失望,對吧?

也就是說,ChatGPT對於自然語言的理解(NLU),好過市面上可以見到的大部分ChatBot。它能夠清楚地抓到你的intent和entities,並且對前後文的理解有很高的掌握度。

這也是我上周六在彰化小聚的時候,分享的重點之一。
ChatGPT對於ChatBot最大的價值是,這是有史以來第一次,NLU開始讓社會大眾滿意(相當高比例的可以接受),並且API成本還算是負擔的起。

ChatBot開發人員,終於有機會突破過去的制約,讓你的ChatBot有機會真的理解用戶在說些什麼(至少讓用戶這樣覺得),而且是透過自然語言,不用再去管傳統的表單輸入或結構化資料輸入了,這是人機介面上的一個突破。

知道用戶說的是什麼之後,接著就是與企業內部資訊系統做整合,這時,開發人員可以輕易地透過API,依照用戶的口語命令,查找或分析資料、請假或是建立表單、操控資訊系統、建立試算表、產生圖表、文件、投影片,甚至最後把資料摘要輸出給用戶(有必要時還可以再用ChatGPT的API來潤飾一下,讓輸出更像是人話。

我們在科幻片中看到的,人類透過自然語言命令電腦工作的場景,已經不會太遠了。

現在的GitHub Copilot、Office Copilot,說不定將會成為一個時代的里程碑,傳統的人機介面輸入方式,過去這幾十年間並不是沒有被挑戰過,從鍵盤、滑鼠、觸控螢幕,手勢操作…都曾經帶來機會和改變。而如今,真正的語音操控時代是否會來臨?

我充滿期待。

2023年5月8日 星期一

微服務(或API)的用戶身分驗證

最近不只一次被問到這個問題。

在應用程式(或前端展示層App)呼叫API(或微服務)的時候, 雖然都會有攜帶API KEY來驗證呼叫者(App)的身分,但若是要知道用戶的身分(就是誰操作這個App來呼叫API的?),又該怎麼辦呢?

一般來說,當我們討論微服務開發時,大多不一定會包含UI(套句Uncle Bob說的,UI是細節)。因此,在實作用戶端(End-User)的身分驗證時,往往只會做到UI(展示層)為止。但實務上,不管是多層式架構或是微服務架構,UI(展示層)又會需要呼叫微服務(或應用層API)來實現系統功能。

像是底下這樣:
圖片

但在UI呼叫微服務(應用層API)時,被呼叫端需要確認呼叫端(UI/展示層)具有呼叫微服務(或API)的權限,這一段,大多只會採用API KEY來驗證。

例如:

POST /APIName HTTP/1.1
Host: domain.com
Content-Type: application/json
ApiKey: 3126616e-c14b-4ba8-8617-d7349541ce40
Content-Length: 23

{"data" : "infomation"}

然而,若某些微服務(或API)運作時還需要得知用戶端身分,又會怎麼做呢?

簡單一點的作法,是直接透過JSON Body告知API當前用戶是誰(前提當然是API信任這個呼叫端),這樣既暴力又簡單,如果想更嚴謹一點,可以在Header攜帶先前用戶已經驗證過的Access Token/JWT Token來實現:

UserWeb App (UI)Microservices(API)登入(Cookies/JWT/OAuth)登入成功(保留User Token)...若不須用戶身分資訊...發送請求呼叫API (帶上API Key)(API確認呼叫者是哪個App)返回結果顯示結果...當需要用戶身分資訊...發送請求(帶上User Token)呼叫API (帶上API Key, User Token)(API確認呼叫者是哪個App, 哪位User)返回結果顯示結果UserWeb App (UI)Microservices(API)

但如此一來,微服務(或API)就必須具備解析Token的能力,或是再拿Token去問身分查驗的服務)。這樣不僅僅可以知道是哪個APP呼叫自己,也能得知是哪一個用戶操作這個APP來呼叫自己(上圖最底下的部分)。

總的來說,API KEY和User Token是兩個不同的機制(別搞混了),API KEY只用做用戶端對伺服器端的驗證,也就是呼叫者(這個APP或UI)的驗證,而OAuth Access Token或JWT Token則用作用戶端身分的驗證,兩者的目的並不相同。


相關教育訓練:
https://www.studyhost.tw/NewCourses/Architecture
https://www.studyhost.tw/NewCourses/aspnetcoreauth

若這篇文章對您有所幫助,請點選這裡加入FaceBook專頁按讚並追蹤,也歡迎您幫我們分享出去,謝謝您的支持。

單體式系統與微服務

最近重讀 Sam Newman的『建構微服務』,覺得實在精彩。真心以為,想作微服務系統的開發人員,都值得花點時間讀一下第一章,絕對值回票價。

我把讀完的感想和摘要寫在下面,希望能夠幫助大家釐清什麼是微服務(以及什麼不是)。

傳統來說,古典的單體式應用(Monolith)大多意指在同一個行程內運行的應用系統,底下是典型的單體式應用程式與其變形。

圖片

古典的單體應用程式

一個應用程式,後面搭配一套DB,但上面這樣的系統已經愈來愈少了,只剩下像是手機app或是極端傳統的desktop app。

如今大部分的單體式應用系統(不管是desktop或web),大多會是底下這樣:
圖片

模組化單體式應用程式
  1. 模組採用同樣(或近乎類似)的開發技術。
  2. 模組間有共用的基礎設施(infrastructure),像是log, security…etc.
  3. 即便模組可以獨立運行,但依舊必須一起佈署。

單體式應用系統的優點

相較於微服務,單體式應用系統其實有很多優點:

  1. 簡化開發人員工作流程
  2. 簡化監控、故障排除、與測試

這是事實,但Sam Newman強調『最近人們開始將單體式系統視為「時代遺產」的同義詞,仿佛這種系統有著什麼本質上的問題,是一個需要避免的東西…』

但其實,單體式架構只是一種選擇,甚至是一種有效的選擇。Sam Newman認為:『這是一個作為架構風格合理的預設選項(我欣賞這句話)』,換句話說,我也認為,面對架構選擇,人們應該尋找一個能被說服使用微服務的理由…

微服務架構

圖片

我們很久以前就談過微服務,但到底什麼是微服務?

Sam Newman在書中,對微服務的核心概念做了說明,我覺得包含幾個重點:

  • 可獨立佈署:微服務的每一個單元應該能夠單獨部署,而不需要依賴其他服務。這種可抽換性使得應用程式更加靈活,可以更快地部署和更容易地維護。這也意味著,當你抽換一個微服務、另一個微服務不該受到影響。
  • 圍繞著Business Domain塑模:每個微服務都應該圍繞一個具體的Business Domain來設計,而不是根據技術或企業組織架構來劃分。這種業務驅動的作法使得應用程序更加緊密地與業務相關,並能夠更好地支撐業務需求的變化。(這一點很多公司其實先天就無法做到,參考底下『架構和組織的調整』)
  • 大小:微服務應該要足夠小,以便能夠單獨佈署和管理。這也意味著每個微服務應該只關注一個特定的Business Domain,而不是試圖做太多的事情。
  • 擁有自己的狀態:每個微服務都應該擁有自己的狀態,包括自己的資料庫、Configration和State。這種狀態的獨立性使得微服務更加可靠,並能夠更好地處理高併發流量和分散式操作。這也表示,多個微服務不該共用同一個資料庫或資料表。如同物件導向程式設計的封裝概念,微服務應當可以自己決定如何與其他服務共享資料、或是隱藏資料。如此才能夠自由的變更修改服務內部的行為(而不影響其他單元)。
  • 架構和組織的調整(這一點很殺,幾乎沒收了台灣大部分有規模的企業進入微服務的入場券):傳統的三層式架構之所以會出現,除了它本身容易理解與簡單之外,還應驗了康威定律(Conway’s Law),也就是,系統的架構會自然相似於組織架構。請看這個傳統的架構:
    圖片

有沒有發現,上面每一層剛好是由不同的團隊負責的。這導致在許多大公司內,一個系統需要多個部門一起負責。

但在上面這個架構底下,當我們想要變更(或增加)一個功能時,就得動用到三個部門的人,而部門的設計所造成的Silo(穀倉效應),使得三個部門的人多半有著彼此不同的立場和目標,導致新功能的實現曠日廢時。

隨著敏捷開發與自組織概念的發展,現在軟體開發團隊逐漸採用混合型組織來設計。意即,每一個團隊直接對應到一個客戶的商業需求或服務,而非對應到一項企業內員工的專業技能:
圖片

DB、後端、前端、UIUX設計、業務商業人員…都被打散重組在一個團隊中(所謂的團隊,是擁有著共同的目標,或說的通俗一點,有共同KPI和考績的一群人)。這樣的一個混合型團隊中,會有各種不同技能的人員,以便於能夠好好地滿足客戶的一種服務需求。而我們的系統架構,也從傳統(以職能為區分)的三層式的架構,轉換成(以Business Domain為導向)的微服務系統設計。請注意這也意味著若微服務如前述的以Business Domain塑模,那企業的團隊結構就很可能會被打散重新調整,這樣的微服務才更有意義。

以上這些就是微服務的本質,也是切分服務邊界的重要思路。有沒有發現很多人卡關在哪裡?很多公司常常想要在不動組織架構和既定思維的狀況下,實踐敏捷或微服務,若是這樣,又何苦找自己麻煩呢?

微服務並不意味著比單體式應用程式更優秀,相反的,為了得到微服務所可能帶來的好處(像是獨立佈署、獨立運行、資訊隱藏、高延展性、技術異質性、企業組織對齊…等),必須附上許多代價,像是應用程式開發的複雜度就大大提升。

因此開發團隊必須認真找到你使用微服務的理由,正如同 Sam Newman說的:『…面對架構選擇,人們應該尋找一個能被說服使用微服務的理由,而非尋找一個不使用的理由。


相關教育訓練:
https://www.studyhost.tw/NewCourses/Architecture
https://www.studyhost.tw/NewCourses/aspnetcoreauth

若這篇文章對您有所幫助,請點選這裡加入FaceBook專頁按讚並追蹤,也歡迎您幫我們分享出去,謝謝您的支持。

2023年5月1日 星期一

找回被遺忘了的目標

圖片

跟以前同事吃飯…

這位朋友在幾年內,從公司的副理升到副總,最近買了新車,讓人挺羨慕的。

本來吃飯只是閒聊技術問題,席間不知道為何,談到目標設定。這主題我在上敏捷課程時,多多少少也有涉獵,看他這幾年事業突飛猛進,我不禁好奇地問,他對目標設定的看法,我心想 : 會不會還有什麼我還不知道的秘訣?

朋友說:『首先,目標設定要明確。』我心裡想,這個自然,這我很清楚。
因此我說:『要有時間、有數量、我上課時也這麼跟學員說。』

『對,就像你若是跟上帝禱告要有錢,但什麼叫做有錢呢? 每個人都有錢啊,一塊錢也是錢。你要很清楚地寫下來,你要多少錢?』他說。
我心想:『恩,有時間,有數量、雖然感覺很容易,但這一關其實很多人已經做不到了。白紙黑字寫下來,確實是有力量的。也能夠幫助自己認真面對和思考。』

他接著說:『不管是個人還是我的團隊,我都是這樣做的。一般來說,一定會有一個數字,例如今年要賺多少錢,存多少錢之類的。』『幾年前我換工作時也是,希望不要距離太遠…那多遠? 幾公里,我就寫下來,然後開始找這個距離以內的所有公司…』我聽得嘖嘖稱奇。

我繼續追問: 『就這樣? 還有其他秘訣嗎?』

『沒有啦,只剩…』他語帶保留的讓我好奇,我立刻追問『甚麼秘訣?』

朋友說 : 『寫下來具體目標之後,每天早上起來就看著大聲唸。』
『啊?』我有點不相信:『真的嗎?你每天都有唸?』

他有點不好意思的說:『呵,也不是每天啦,但…一年365天大概也有300天吧。』這回換我訝異了,其實即便只是300天,也很不可思議。

他說,大部分人即便訂了一個清晰的目標,結果卻還是沒做到。常常因為他們都『忙到忘了』。我們每天生活中有太多太多的雜事,和『看起來重要』的緊急事項,如果你沒有每天唸一次自己當初訂下的目標,常常很容易忘記哪些才是你真正重要的。

這個做法其實很簡單,我也跟很多人分享過,然而大部分人都不會真的去做。但是我覺得,它對我幫助很大…

『每天唸?』…這倒是我之前沒有做到的事情。

我心裡開始想…明確可量測的目標、有時間、有數量,這些我早就有做到,沒問題…但確實,我還是常常因為其他看起來更重要的緊急工作,打亂了一周甚至一個月的規劃。讓自己忘了已經訂好的目標,在努力的事情上走偏了。讓自己忙的焦頭爛額,卻把時間花在當下看起來重要,但對整體目標不痛不癢的事情上…

每天唸,其實就是校正羅盤的方向,如此一來,才能讓自己每天專注在正確的路徑上。

咦? 這似乎跟敏捷的 daily meeting 異曲同工? 每天早上自己花十分鐘跟自己開個會,釐清有沒有走偏目標、重新校正自己的羅盤,這時間CP值很高啊。

呵呵,今天是一個月的開始,我還真的想嘗試看看,每天十分鐘,這樣是否能夠讓我更專注地聚焦在給自己訂好的目標上。

#敏捷人生

2023年4月21日 星期五

在 .net core 中實現AOP

圖片

AOP(Aspect-Oriented Programming,剖面導向程式設計)是一種軟體開發方法論,其主要目的是將商業邏輯(Business Logic)和橫切關注點(Cross-Cutting Concern, 例如 Logging, Permission , Exception Handling…etc.)分離出來,進而使系統更加職責清晰、易於維護和擴展。

在AOP中,橫切關注點被稱為切面(Aspect),切面是一種跨越多個物件和方法的模塊化程式碼,用來實現特定的橫切關注點。在AOP中,我們可以將切面應用(Apply)到一個或多個類或方法上,從而實現這些類或方法的橫切關注點。

AOP的主要概念是,若能將橫切關注點從主要商業邏輯(Business Logic)中分離出來,即可達到降低耦合、提高軟體重用性的效果。

舉例說明

首先來整理一下這個命題,我們知道AOP想把Business Logic和Infrastructure Logic做一個切割,達成cross-cutting concerns的獨立性,但這樣講很抽象,具體情境是什麼呢?

看底下這一段code:

using System;

namespace testAOP
{
    class Program
    {
        static void Main(string[] args)
        {
            //建立BMIProcessor物件
            BMIProcessor BMI = new BMIProcessor();
            BMI.Height = 170;
            BMI.Weight = 70;
            //計算BMI
            var ret = BMI.Calculate();
            //顯示
            Console.WriteLine($"BMI : {ret}");

            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    }

    public class BMIProcessor
    {
        public int Weight { get; set; }
        public int Height { get; set; }
        public Decimal BMI
        {
            get
            {
                return Calculate();
            }
        }

        //計算BMI
        public Decimal Calculate()
        {
            Decimal result = 0;
            Decimal height = (Decimal)Height / 100;
            result = Weight / (height * height);

            return result;
        }
    }
}

BMIProcessor 是負責計算BMI的類別,上面這段程式碼的第14行使用了Calculate()這個Method來計算BMI,但這段Code有些小問題,例如,傳入身高體重是0時候,可能發生Exception。

你會想,這不要緊,我們在BMI類別的Calculate()方法中加入try…catch,類似底下這樣(當然我知道可以加上傳入參數的檢查,但我想講的點是try…catch啦,所以我們先這樣幹):

//計算BMI
public Decimal Calculate()
{
    Decimal result = 0;
    try
    {
        Decimal height = (Decimal)Height / 100;
        result = Weight / (height * height);
    }
    catch (Exception ex)
    {
        //錯誤處理
        //send mail
        //send line message...etc.
        //ex.....
    }
    return result;
}

這樣做本身沒問題,但如此一來,我們就讓計算BMI的這段code,嚴重的與Exception Handling中的『發email』或『發LINE訊息』的功能相依!

此外,倘若我們想針對Calculate()這個Method被呼叫幾次,做一個Log,你可能也會想在Calculate()這個Method中加入讀寫檔案存取log或寫入DB的Code,這下不得了,簡單的BMI計算(business logic)開始與檔案系統、DB、發mail…等等(這些我們稱為 Infrastructure logic的程式碼)相依。

這邊定義的相依,具體而言就是,只要你用到了該method,就勢必得一起用到DB、檔案、發mail…等機制。

而你的程式碼中肯定也不只這一個地方需要進行Log或是exception handling機制,而是有好幾個方法都需要用戶,那就更精采了…

我們的程式碼可能開始會變成這樣…

private void MethodA()
{
    try
    {
        //Business Logic block
        //Business Logic block
        //Business Logic block
    }
    catch (Exception)
    {
        //完全一樣的exception Handling block
    }
}

private void MethodB()
{
    try
    {
        //另一段Business Logic block
        //另一段Business Logic block
        //另一段Business Logic block
    }
    catch (Exception)
    {
        //完全一樣的exception Handling block
    }
}

有沒有發現,其實一般我們在寫每一段程式碼的過程當中,都有除了Business Logic以外,圍繞著實現主需求的其他很多事情,其實並非核心的運算(例如上面的catch區塊…),仔細想想,這樣的情況還非常多,在每段方法的核心的代碼中,總是有一堆跟核心邏輯無關,卻又無法不寫的程式碼,有哪些?

真的非常多,諸如:

  • 權限檢查(permission)、
  • 參數檢查(Validation)、
  • 例外處理(Exception Handling)、
  • 日誌紀錄(Logging)

  • 等都是典型,些都是程式碼必須–但卻跟我們的Business Logic不全然直接相關–的邏輯。

這些,我們稱之為 “Infrastructure Logic”,在架構上屬於 cross-cutting concerns。

這類的程式碼,若能與Business Logic切割開來,將立即得到許多好處,諸如:

  • Infrastructure Logic和Business Logic耦合性降低,兩者分開後,各別將更容易重用。
  • 程式碼維護變的更加容易,開發時也可以更專注在Business Logic,不用分心去管雜七雜八的其他程式碼。
  • 程式碼可讀性也變高,閱讀時不會被不相關的code干擾…

總之,這麼做好處多多。但由於您可能還沒有辦法理解我們待會要怎麼分割兩者,因此你現在可能完全沒感覺,不急,繼續看下去就會慢慢理解。

而AOP希望實現的,就是讓這些Business Logic(BMI計算)和Infrastructure Logic(Log, 錯誤處理, …etc.)之間可以不要彼此過分倚賴…

好,那如何實現呢?
有很多種作法,我自己最喜歡的,是透過Attribute。

想像一下,倘若我們可以這樣做…
(注意底下的 Calculate()掛上了Attribute):

//👉當發生錯誤的時候發通知給 Someone
[ExceptionNotify(LineTo = "Someone")]
//👉這函式被呼叫,就寫log到檔案中
[Logging(LoggingTo = File)]
//計算BMI
public Decimal Calculate()
{
    Decimal result = 0;
     
    Decimal height = (Decimal)Height / 100;
    result = Weight / (height * height);

    return result;
}

倘若我們可以透過對Calculate()這個Method掛上我們寫好的Attribute,就賦予Calculate()這個Method特殊的功能,那就可以實現了。例如,掛上Attribute後,一旦發生exception,就自動發LINE訊息通知Admin,如果該Method被呼叫,就自動在Log file中記錄…

倘若這一切只需要掛上Attribute就能實現,我們就毋須破壞原本計算BMI的Calculate()這個method中的程式碼了…

但,真的可以這樣嗎?可以。

以Attribute實現AOP

請在專案中透過NuGet加入一個套件 isRock.Core.AOP,完成後,請先引用using isRock.Core.AOP ,接著設計一個Logging Attribute(請注意繼承自PolicyInjectionAttributeBase):

public class Logging : PolicyInjectionAttributeBase
{
    //指定Log File Name
    public string LogFileName { get; set; }
    //override AfterInvoke方法
    public override void AfterInvoke(object sender, PolicyInjectionAttributeEventArgs e)
    {
        var msg = $"\r\n Method '{((MethodInfo)sender).Name}' has been called - {DateTime.Now.ToString()} ";
        SaveLog(msg);
    }
    //寫入Log
    private void SaveLog(string msg)
    {
        if (System.IO.File.Exists(LogFileName))
        {
            System.IO.File.AppendAllText(LogFileName, msg);
        }
        else
        {
            System.IO.File.WriteAllText(LogFileName, msg);
        }
    }
}    

我們在上面這個自行設計的Attribute當中,override了AfterInvoke()這個方法,並在其中呼叫了SaveLog(),透過SaveLog()這個方法來儲存Log。

上述動作完成後,再將原本計算 BMI的Class修改如下(請注意只改了10, 22行, 並且為BMIProcessor 抽出了一個 IBMIProcessor 介面(1-8行):

public interface IBMIProcessor
{
    decimal BMI { get; }
    int Height { get; set; }
    int Weight { get; set; }

    decimal Calculate();
}

public class BMIProcessor : IBMIProcessor
{
    public int Weight { get; set; }
    public int Height { get; set; }
    public Decimal BMI
    {
        get
        {
            return Calculate();
        }
    }

    [Logging(LogFileName ="log.txt")]
    //計算BMI
    public Decimal Calculate()
    {
        Decimal result = 0;
        Decimal height = (Decimal)Height / 100;
        result = Weight / (height * height);

        return result;
    }
}

最後,再調整一下Main中的程式碼(只改了第4行)。

static void Main(string[] args)
{
    //建立BMIProcessor物件
    IBMIProcessor BMI = PolicyInjection.Create<IBMIProcessor>(new BMIProcessor());
    BMI.Height = 170;
    BMI.Weight = 70;
    //計算BMI
    var ret = BMI.Calculate();
    //顯示
    Console.WriteLine($"BMI : {ret}");

    Console.WriteLine("Press any key for continuing...");
    Console.ReadKey();
}

神奇的事情發生了。

你會發現,我們的主程式,上面的main()中其實只改了小小一行(第4行),就讓我們自己寫的Logging Attribute作用到Calculate()的身上了。由於Logging Attribute中的第6行override了AfterInvoke()這個Method,該Method會在Calculate()方法被呼叫後自動被執行,其中的SaveLog()程式碼是開檔寫Log…

如此一來,每當Calculate()方法被呼叫,log.txt檔案就會被開啟並被加入一列,變成:
圖片

這樣就實現了我們一開始的需求👉👉當某一個Method被掛上Attribute之後,就能夠增加原本沒有的功能。且將Business Logic和Infrastructure Logic做一個切割,達成cross-cutting concerns的獨立性。

我們回憶一下我們做了什麼?

  1. 首先我們引入了NuGet套件 isRock.Core.AOP
  2. Using了 isRock.Core.AOP
  3. 接著設計了一個自己的Logging Attribute(繼承自PolicyInjectionAttributeBase)
  4. 在上述Logging Attribute當中,override了AfterInvoke()方法,該方法會在目標Method(被掛上Attribute的Method)被叫用之後自動被呼叫
  5. 在上述AfterInvoke()方法中呼叫SaveLog()開檔並加上『目標Method被呼叫』的Log
  6. 為了讓Attribute生效,我們修改原始的BMIProcessor類別,在Calculate()方法上掛上LoggingAttribute:
[Logging(LogFileName ="log.txt")]
  1. 為BMIProcessor類別抽出介面IBMIProcessor
  2. 調整主程式為 IBMIProcessor BMI = PolicyInjection.Create(new BMIProcessor());

就這樣,我們透過這個框架,可以讓開發人員輕鬆的設計自己的Attribute,並且將Attribute掛在Method上,實現Business Logic和Infrastructure Logic分離的AOP情境。

骨子裡,他還是使用PolicyInjection的手法,透過reflection與Proxy的手法,完成以Attribute實現類似AOP的功能。

如果你深入看,會發現我們的PolicyInjectionAttributeBase已經實做了BeforeInvoke、AfterInvoke、OnException三種Method,足夠讓開發人員輕易的實現自己想實現的Attribute。

嚴格說起來,我們用PolicyInjection這樣的手法,雖然可以實現部份AOP的目標,儘管還夠不全面,但是,即便只是能實現到目前這樣,也已經很符合我們在日常開發工作當中使用了。

未來有機會,我們再來介紹剛才使用的 AOP 框架(套件)是如何開發的…


相關教育訓練: http://www.studyhost.tw/NewCourses/Architecture
若這篇文章對您有所幫助,請點選這裡加入FaceBook專頁按讚並追蹤,也歡迎您幫我們分享出去,謝謝您的支持。

2023年4月18日 星期二

使用IoC與DI有何意義? (五) 透過設定實現注入

圖片
這一篇文章,照說應該接在之前寫過的這篇之後。但沒想到是,上一篇已經是 2020年的事情! 這連載也間隔太久了,哈。

在理解了DI的原理,並且知道注入的做法之後,我們繼續往下看,如果要動態實踐注入,該如何進行?

先前我們看到的程式碼中的注入,都是修改 program.cs 中的 AddTransient 來進行的,例如:

serviceCollection.AddTransient<ISalaryFormula, SalaryFormula>();

但如果我們想對 ISalaryFormula 這個介面,動態注入實作的類別,但不想改程式碼,可以怎麼做呢?

沒什麼複雜的,就是使用設定檔 appSettings.json,例如,我們可以建立這樣的配置:

{
  "MyServiceConfig": {
    "MyServiceType": "consoleDI.SalaryFormula"
  }
}

其中以字串描述了一個類別的路徑(NameSpace.ClassName),例如: “consoleDI.SalaryFormula”

如果要額外描述在哪一個組件(.dll)內,路徑則是 “NameSpace.ClassName, DllName”

有了設定之後,我們在主程式中,加入讀取該設定的程式碼(底下7-12行):

public class Program
{
    //這裡才是真正的進入點 main
    static void Main(string[] args)
    {
        //讀取appsettings.json中的設定,決定用哪一個類別實作
        IConfiguration config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false)
            .Build();
        //取得類別名稱
        var myServiceType = config.GetSection("MyServiceConfig").GetValue<string>("MyServiceType");

        //建立 DI Services
        var serviceCollection = new ServiceCollection();
        //取得類別型別
        Type serviceType = Type.GetType(myServiceType);

        //註冊 DI物件 (為了未來的注入)
        serviceCollection.AddTransient<MainApp>();
        //使用設定檔中設定的類別動態作注入
        serviceCollection.AddTransient(typeof(ISalaryFormula), serviceType);

        //建立 DI Provider
        var serviceProvider = serviceCollection.BuildServiceProvider();
        //執行 MainApp,這裡會自動注入 ISalaryFormula
        serviceProvider.GetRequiredService<MainApp>().Main();

        Console.WriteLine("press any key to exit...");
        Console.ReadKey();
    }
}

取得類別名稱後,透過17行取得該類別的 Type,接著一樣透過呼叫 serviceCollection的AddTransient方法設定注入(22行),如此一來,在27行透過 serviceProvider.GetRequiredService() 建立 MainApp 執行個體時,就會自動注入剛才透過設定檔動態注入的元件類別,實現『只需要透過修改設定檔,就可以改變程式行為』的效果。

程式碼位於:

git clone https://github.com/isdaviddong/DotNetCoreConsoleDIServicesByConfigExample.git

透過底下影片,可以看到實際運行結果。


本文內容來自於『團隊開發與架構設計實務』課程,我們最近要開課囉,依照過去經驗這門課幾乎都是秒殺,如果你需要預先保留席次,請點選這裡登記唷。

若您要詢問問題,或是需要即時取得更多相關訊息,可按這裡加入FB專頁。
若這篇文章對您有所幫助,請幫我們分享出去,謝謝您的支持。

2023年4月10日 星期一

在Azure DevOps CI CD Pipeline中直接發送LINE通知訊息

這是一個我很久以前就想實現的功能。

儘管Azure DevOps的Pipeline本身可以搭配WebHook,依照監聽的事件掛上各種通知,但相對來說比較不直覺,如果能夠在 Pipeline 中,直接透過某個Task來發送LINE通知,會方便很多。

使用的情境很廣,你可以在自動化建置(Build)或佈署(Deploy)的過程中,將成功與否的狀態,直接通知某一個群組或個人(主管、團隊Leader),並且留下紀錄。

我們採用 LINE Notify 最主要的原因是 – 迅速、免費。

LINE 這個 App 在台灣幾乎每台手機上都有安裝,沒有人沒有LINE。速度甚至比簡訊還快,所以作為通知方式是相當有效的。

申請 Token 來發送 LINE Notify 訊息

要發送 LINE Notify 訊息,其實很簡單。
任何人(只要你有LINE帳號),都可以到底下網址 https://notify-bot.line.me/ 免費建立一個 Token ,即可透過程式碼以自動化的方式發送訊息到你的LINE帳號,或是你指定的群組。

建立 LINE Notify Token 的方式如下:

如果要發送到群組,記得要將 LINE Notify 這個帳號邀請進入群組中,將來,訊息會透過統一的 LINE Notify 帳號發到你的 App 中,收到時類似底下這樣。參考上面的影片,下圖中的【test20230408】,就是你在建立權杖時候設定的名稱。
圖片

而自動化發送訊息方式,原本得透過 Rest API,相關的文件在底下:
https://notify-bot.line.me/doc/en/

寫程式當然比較麻煩,筆者為 LINE Notify 做了一個 Azure DevOps Task,有了 LINE Notify Task之後,就可以直接在 Azure DevOps Pipeline 中發送。

使用 LINE Notify Task

現在,你可以透過使用該 Task 直接在Pipeline中發送訊息:
圖片

該Task安裝位置如下:
https://marketplace.visualstudio.com/items?itemName=tw-developer.LineNotify

你可以透過上面位置來安裝到組織中。

安裝好之後,即可直接在Pipeline當中使用:
圖片

使用的方式很簡單,只需要把 『Line Notify Token』 以及『你要發送的訊息』填入task中的欄位即可。

請留意,拿到你的 Line Notify Token 的人,都可以透過 API 發送訊息給你(或你指定的群組),因此,這個 Token 有一定的機密性質,請在填寫時留意資訊安全。 建議可以採用類似 Azure KeyVault 或secret variable 的方式設定以保障安全。

設定好之後,運行該 pipeline,不管是 Windows Agent 或是 Linux Agent 都可以順利發送 LINE 訊息唷:
圖片

未來,只需要把想要發送的訊息直接寫入(或利用變數帶入) Message 欄位即可。

熱門文章