Integrate with OPA, Go 可安裝 OPA package, go install github.com/open-policy-agent/opa v0.60.0
完成後就具備 SDK
和 rego API
的能力,這篇指探討 API 的部分
透過程式碼基本上也有三個元素 1. Policy, 2. Data, 3. Input
1 | import ( |
但有時候會希望 policy 檔案可以從外部某一個儲存空間取得,寫法就會變成這樣
1 | store := inmem.NewFromObject(json) |
rego.New
接受多個 Options,表示如果有其他的設定,可以在前面先組好後,再放進 rego.New
中rego.Load
的方法可以設定讀取檔案的位置 API 說明,會讀 *.rego
、*.json
、*.yaml
檔案rego.Load
時又有 rego.Store
設定,就必須指定 rego.Transacction
proto
的基本語法後,就可以來用一個程式語言實作 gRPC 服務了,本篇就用 Golang 來作範例吧,練習內容是根據官方文件所提供的教學內容,細節可以到這邊閱讀。Golang 環境如何安裝這邊就不說明了。需要安裝 Protobuf Compiler
(安裝連結),如果環境沒有安裝 C++,也可以下載預先 compile 好的執行檔,並設定好環境參數 (步驟說明),如果一切設定正確,在命令視窗內應可執行 protoc
指令了
練習題目: 建立一個 address book,功能是可以從檔案中存取聯絡資訊,聯絡資訊包含姓名、ID、Email、連絡電話
建立一個新的資料夾
執行 go mod init <project name>
指令
建立 addressbook.proto
檔案
1 | syntax = "proto3"; |
安裝 go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
開啟命令視窗,執行 protoc -I=. --go_out . addressbook.proto
指令,即可看到程式碼產生再 tutorialpb
的資料夾下
建立 main.go
引用剛剛產生的程式碼,其內容會包含
AddressBook
structure with a People
field.Person
structure with fields for Name
, Id
, Email
and Phones
.Person_PhoneNumber
structure, with fields for Number
and Type
.Person_PhoneType
and a value defined for each value in the Person.PhoneType
enum.1 | package main |
1 | package main |
1 | package main |
練習題目: 建立一個可以回 Hello World 的 gRPC Service
安裝 protocol compiler plugins
1 | go install google.golang.org/protobuf/cmd/protoc-gen-go@latest |
建立 helloWorld.proto
1 | syntax = "proto3"; |
執行 protoc --go_out=. --go-grpc_out=. helloworld.proto
,會產生兩個檔案
建立 server/server.go
,建立 gRPC Server 需要完成兩件事情
1 | package main |
建立 client/client.go
1 | package main |
什麼是 gPRC,根據官網的說明
A high performance, open source universal RPC framework
為什麼選擇 gPRC 呢
gRPC is a modern open source high performance Remote Procedure Call (RPC) framework that can run in any environment. It can efficiently connect services in and across data centers with pluggable support for load balancing, tracing, health checking and authentication. It is also applicable in last mile of distributed computing to connect devices, mobile applications and browsers to backend services.
這表示 gRPC 是一個可以在各種語言/環境中,做到高效且擴充性佳的框架,十分有趣。繼續研讀下去
這張圖說明了 gRPC Server 與 Client 間的溝通模式,基於 Proto 的定義,以 Protocol Buffer 格式來進行雙方的溝通。
gRPC 需定義 .proto
的文件,再透過工具就可以產生對應語言的程式碼,了解 proto
的語法是必須的
1 | syntax = "proto3"; |
proto2
語法message
格式,可以想成是在定義 data model1 | syntax = "proto3"; |
SearchRequest
有定義三個欄位,分別為 string query
, 兩個 integers (page_number
, results_per_page
),使用 scalar types
後面的數字定義,可使用範圍為 1 到 536,870,911,不能使用的數字區間限制有
一個 message 定義內的數字不能重複
一旦 message 有被使用,就不能更改數字
數字修改的行為意思,等同欄位被刪除
為了 message size,效能考量,數字應從 1 開始使用,size 規則為 1 到 15 會使用 1 byte,而 16 到 2047 會占用 2 bytes。(Ref: Message Structure)
message 可以定義在單一 proto
檔案或多個,但會建議一個 proto
檔案內的 message 定義不應該太多
註解 syntax : //
或是 /*....*/
如果要刪除欄位,需要 reserve
原本欄位使用的數字,已避免未來被誤用
1 | message Foo { |
可定義及使用 Enum
型別
1 | enum Corpus { |
如果 Enum 內有出現值重複的需求,這時候需要設定 allow_alias = true
1 | enum EnumAllowingAlias { |
有 Nested Types
1 | message SearchResponse { |
Angular v17 幾個 Highlight 重點
相信第一點應該不用多說什麼,如果有開始寫 standalone component 的朋友,大概都會覺得開發體驗還不錯,少了一些多餘的事情要做,在 Angular 17 建立新專案時就不需要再多加 --standalone
的參數,預設就會使用開啟 standalone 的模式😍
如何從 ngModule
搬到 standalone
,可以參考官方提供的搬家手冊
過往有寫過 Angular SSR 的朋友就知道那個步驟有多麼繁瑣,現在只要在建立專案時,選擇啟用 SSR 功能時,Angular CLI 就會自動幫我們加入 SSR 所需的相關檔案,也可以從 angular.json
內做設定
Angular hydration 在 v17 也是預設啟動, Hydration 相關資訊可以參考這篇文件
這新功能應該不用多說了吧,在 preview 階段就已經有不少大神在做嘗試,自己也有玩一下,真的很期待,雖然過往習慣使用 directive 來控制畫面的人,我相信新的寫法會讓 template 更容易閱讀和管理,也很期待 @defer
的威力。
這邊也提供幾篇文章給大家
Angular Team 從 v16 後就陸續提供很多讓人驚豔的新功能,同時也為 Angular 賦予新能量,真心期待接下來的發展,例如 standalone component, zone.js optional 等
]]>開啟 Coverage 的顯示,先講效果
從畫面的左邊就可以知道有哪些程式尚未被覆蓋到,在撰寫 test case 時是很直覺的,設定方式如下
1 | "go.coverOnSave": true, |
coverOnSave
: 設定為 true
時,在 save 檔案時會執行 go test -coverprofile
go.coverageDecorator.type
有兩種模式,highlight
和 gutter
,自己是比較偏愛 gutter
的模式,各位可以自己玩看看原來 Go extension 內有支援 generate unit test 的功能,這樣還有什麼理由不寫測試呢
或是用 command Palette 方式執行
引用方式
1 | import "time" |
1 | time.Now() // 目前時間 (with 時區資訊) |
time Format 對應的數字
1 | 月份 1,01,Jan,January |
time.Add
補充說明
time.Add(<duration>)
, duration 的單位, Go 有提供以下幾種
time.Second
time.Minute
time.Hour
time.Add()
會回傳 time.Time
的型別,表示可以串接下去,例如
1 | time.Now().AddDate(0, 0, 1).Add(-1 * time.Nanosecond) |
time.AddDate
補充說明,function 接受三個數字,分別代表 年,月,日
比較時間
1 | now := time.Now() |
計時器
1 | time.After(duration) |
每多少時間執行一次
計算時間長度
1 | start := time.Now() |
計算時間差
1 | start := time.Now() |
1 | Add(d Duration) Time |
1 | Hours() |
寫一個 function 取得一天的開始與結束時間,類似 date-fns 的 startOfDay
和 endOfDay
結合
1 | func DateRange(t time.Time) (beginOfDate time.Time, endOfDate time.Time) { |
這篇 HTTP client - Test requests 內說明如何使用 HttpTestingController
進行 http request 的測試,而因為後期的專案我都是使用 Nx 來建立,Nx 建立的專案是使用 Jest 來跑 Unit Test. 不知道是幸運還是怎樣,竟然遇到超乎預期的結果
情境是這樣的,測試一個 server 呼叫 API 回傳的結果是否符合預期,寫法是
1 | callApi() { |
測試程式碼為
1 | it('test api', () => { |
上述的測試預期結果會是 fail 的,因為 line: 5 的結果理當為 [mockData]
,但實際跑完測試的結果卻是 passed
這時候第一個想法會是非同步的關係,可能要用到 callback 的方式,所以調整了 test case ,調整後如下
1 | it('test api', (done) => { |
確實也讓 test failed 了
但是接著噴了另外一個錯誤訊息, 竟然說 done()
必須要呼叫到,timeout.
經過一段時間的研究,發現原來是這個原因
If
done()
is never called, the test will fail (with timeout error), which is what you want to happen.If the
expect
statement fails, it throws an error anddone()
is not called. If we want to see in the test log why it failed, we have to wrapexpect
in atry
block and pass the error in thecatch
block todone
. Otherwise, we end up with an opaque timeout error that doesn’t show what value was received byexpect(data)
.
在 jest 內如果 expect 的結果不符合預期,會噴 exception,所以當使用 done
callback 時,需要用 try catch 包起來
1 | it('test api', (done) => { |
這樣改寫完,timeout 的錯誤訊息就沒有再出現了。看來過往沒有特別留意到這塊,真的是太不小心了。所以寫這篇筆記分享給大家
Rest Client
可以從 VS Code 的 extension market 內搜尋並安裝,使用方式也很簡單,只要檔名的結果是 .http
或是 .rest
都是 Rest Client
可以支援的檔案類型,先用一個簡單的範例作展示
真的很簡單使用,但當然不只有這樣,這篇筆記會記錄我在使用上的一些心得或技巧
Content-Type
設定: 在做 POST 或是 PUT 時會傳 JSON 格式為 body 內容,這時候就需要設定 Content-Type
,設定方式如下
1 | POST https://jsonplaceholder.typicode.com/todos |
可使用 ###
來分隔 Request 內容
#
或是 //
可用來寫註解
自定義變數
Environment variables 可定義在 VS Code setting 內,這種變數可以跨檔案使用
File variables: 定義在 .http
檔案內
Request variables: 將 Request 的內容存在 Request variable
內,供同一個檔案內的其他 request 使用,# @name <<request name>>
或是 // @name <<request name>>
定義 request variable
透過 # @prompt {var1} {description}
或是 // @prompt {var1} {description}
定義,可讓使用者輸入變數值
透過 {{ variable }}
來使用 variable
範例
1 | // File variable |
設定 Response Preview 內容,在 VS Code 設定檔內,可以設定 previewOption
Option | Description |
---|---|
full | Default. Full Response is previewed |
headers | Only response header |
body | Only response body |
exchange | Preview the whole HTTP exchange |
在 Markdown 內也可以被使用,當使用 code block 時,只要標註為 http
或是 rest
時,REST Client 套件也能認得並可以被執行,要留意的是,該動作沒法再 preview 的畫面上執行,必須是在編輯 md
檔時。變數設定在不同的 code block 一樣可以被吃到,只要是在同一份 markdown 檔案內即可
Grafana Faro 的運作方式如下圖,
前端開發可以透過提供的 SDK 將相關資訊傳到後面的 Agent/Collect ,就完成了。只是 SDK 雖然簡單套用,裡面有很多細節設定是需要深入研究的,這篇就是這些設定的學習筆記
@grafana/faro-web-sdk
provides instrumentations, metas and transports for use in web applications,安裝使用方法如下
安裝 faro-web-sdk
1 | npm i @grafana/faro-web-sdk |
initialize
1 | import { initializeFaro } from '@grafana/faro-web-sdk'; |
url
: Grafana Agent 的位置apiKey
: 對應到 Grafana Agent 的 integrations.app_agent_receiver_configs
下的 server.api_key
設定app
: Web Application 的 meta 資訊,會用於 Grafana Dashboard 上當這樣設定完成後,開啟網頁時,在 network 的地方就會看到 SDK post 相關資訊到設定的 agent 位置
當遇到需要手動推送資訊時,SDK 也有提供對應的 API 接口
手動推送 log
1 | // send a log message |
手動送 Exception
1 | faro.api.pushError(new Error('everything went horribly wrong')); |
手動送 Event
1 | faro.api.pushEvent('navigation', { url: window.location.href }); |
手動送 meaurement
1 | faro.api.pushMeasurement({ |
pause/resume Faro
1 | // pause faro, preventing events from being sent |
Faro SDK 預設會忽略短時間內相同訊息的事件,不會每一筆都往後面送,如果想要改變這行為,可以設定 dedupe: false
(dedupe
: A flag for toggling deduplication filter)
預設是採 batch sending 的模式,每 250 ms 或是每 50 筆送一次,這些數值也可以設定
上一段提到的手動送資訊到後面的 API,都有額外的參數可以設定,細節可以參閱這邊
很多 SDK 的使用細節說明都寫在 faro-core
的地方,README 有此去
預設有提供 faro-react
,其他 framework 如果想要實作類似的效果,可以參考 react 的版本,包含的項目有
以 Angular 來說,應該也可以做到 1~3 點,但我還沒有自己動手實作過,先暫定可以好了
Faro 提供的是 RUM
(Real User Monitoring) 的相關資訊,除了 Grafana,Kibana 和 Sentry 都有提供類似的功能,只是因為自家的 Monitor stack 是 Grafana 為主,所以選擇 Faro 只是為了讓使用的工程師不用在工具中切來切去
前端效能調教水很深,收集到的這些資訊並不會有 Web 在產生畫面的相關資訊,那些需要回到瀏覽器上做分析,背後的 web vita API 可以參考這一個套件 web-vitals
這篇為 網站可靠性工程工作手冊|導入SRE的實用方法
這本書的讀書筆記,線上閱讀版跟翻譯書的連結如下
讀書筆記跟實務上遇到的經驗都會整理一起
參考文件:
]]>這一篇來學習如何寫 Ansible playbook
建立一個空的 playbook 很簡單,只要建立一個 <xxxx>.yml
檔就算是完成一個 playbook 的建立。內容才是重點,主要會有幾個元素
劇本名稱
執行對象: 這會跟 Inventory 設定檔有關
1 | [local] |
[local]
是 inventory group 名稱,也是 hosts 指定的目標,可以依自己的喜好編寫,後面也會有一篇研究如何寫 inventory 檔案任務列表: 會包含一系列要執行的動作,通常會使用 Ansible 所提供的 module 來執行
command
是 Ansible 內建的 module,可以對 hosts 下指令register
也是 Ansible 內建的 module,可以將回傳訊息記錄在變數內,這邊就是會儲存在 result
這一個變數Jinja2
的樣本功能,使用 {{ }}
的方式輸出在 WSL 的環境內,切換到 playbook 檔案所在得資料夾,執行這段指令,需依實際的狀況作些調整,基本指令結構是
1 | ansible-playbook -i <inventory 檔案> <playbook 檔案名> |
範例:
1 | ansible-playbook -i hosts hello_world.yml |
如果設定都正確的話,可預期會得到這樣的執行結果
撰寫 Ansible playbook 本身不難,一旦知道我們想要自動化的行為有什麼,相關流程需要怎麼安排,接下來就是找對應的 Ansible module 或是網路上有的參考範例,最後就是整理在 playbook 劇本內。當然更進階的管理辦法或是 playbook 的檔案結構應該要怎麼規劃,就留給各位去研究了。
Ansible 有提供 ansible-lint
的工具,可檢查 playbook 的格式是否正確,該工具的 GitHub Repo 由此去,Ansible Lint Documentation
ansible
or ansible-inventory
on a control node.很不幸的是 Windows 本身是無法支援 Ansible control node 的功能,只能透過 WSL2 來執行
進入 WSL 後,執行下列指令即可完成安裝 Ansible
1 | sudo apt-get update |
安裝成功後,執行 ansible --version
應可看到類似的訊息
這樣就表示安裝成功。至於其他作業系統的安裝方式,可以參考 Installation Guide
要練習 Ansible 當然也要準備一個可以被測試部署的環境,因為 Docker 是大家的好朋友,所以就準備一個來當 managed node 吧
1 | docker pull chusiang/ansible-managed-node:ubuntu-20.04 |
1 | docker run --name server1 -d -P chusiang/ansible-managed-node:ubuntu-20.04 |
啟動後應可看到 server1
SSH port 綁定的狀態
接下來我們就可寫第一次 Ansible 設定檔及 Inventory 檔
ansible.cfg
的檔案,檔案內容如下1 | [defaults] |
hosts
1 | server1 ansible_ssh_host=127.0.0.1 ansible_ssh_port=32768 ansible_ssh_pass=docker |
ansible_ssh_host
:請設為本機的 IP。ansible_ssh_port
:請設為 docker ps
時取得的 SSH port。ansible_ssh_pass
:因沒有連線用的 SSH 金鑰,故直接使用密碼的方式進行連線,當上述動作完成後,該資料夾應該會看到這兩個檔案,接下來就可以執行第一次 ansible 的指令了
1 | ansible all -m ping |
如果設定正確,應可看到這樣的結果回傳
signals
來試試看是否能成為下一代 change detection 的選擇。想要嘗試的朋友可以安裝 angular cli v16
的版本Signals 不是 Angular team 創造出來的 library,而是引用其他 framework 內所有使用的一個機制,如果沒記錯應該是來自 SolidJS
Signals are the cornerstone of reactivity in Solid. They contain values that change over time; when you change a signal’s value, it automatically updates anything that uses it.
說是這樣子說,Angular Team 是自己實做整個 Signal 機制,相關的程式碼連結我會附在下面
1 | import { Component, signal } from '@angular/core'; |
signal
介面
1 | function signal<T>(initialValue: T, equal?: ValueEqualityFn<T>): SettableSignal<T> |
要取得 signal 物件值的方式很直接,直接當 function 使用即可,接續上面的範例
1 | {{ count() }} |
這樣就可以在 html 上顯示 count 的值了,或許會問不要在 html render 時寫 function call 嗎,會有效能問題,這裡這樣使用是沒有問題的 (之前有聽 angular team 說為什麼不會有問題,但我忘記理由了)
當建立一個 signal 物件後,更新值得方式有三種,set
、update
和 mutate
set: Directly set the signal to a new value, and notify any dependents.
1 | set(value: T): void; |
update: Update the value of the signal based on its current value, and notify any dependents.
1 | update(updateFn: (value: T) => T): void; |
mutate: Update the current value by mutating it in-place, and notify any dependents.
1 | mutate(mutatorFn: (value: T) => void): void; |
1 | import { CommonModule } from '@angular/common'; |
開頭有提到 signals 是一個 reactive library,當然不會只有這種單一值的使用情境,一定會有錯綜複雜的使用情境,這時候會怎麼使用呢
1 | import { Component, signal, computed } from '@angular/core'; |
computed
可以讓我們與其他 signal 作互動結合,當 computed 內的 signal 值改變時,此 computed 結果也會跟著改變,使用上算直覺
1 | export declare function computed<T>(computation: () => T, equal?: ValueEqualityFn<T>): Signal<T>; |
除了 computed,還有一個是 effect
1 | export declare function effect(effectFn: () => void): Effect; |
須留意的是兩者回傳的物件是不一樣的,computed
會回傳一個新的 Signal
物件,但 effect
是回傳一個 Effect
物件,這 Effect 型別的物件可以允許我們停用 effect
,類似 Observable.subscribe 會回傳 subscription 的概念
1 | constructor() { |
這邊要留意的是 effect
宣告的地方跟 inject()
是一樣的,只能在 constructor 宣告,不然會噴錯誤訊息給你看
Signal 的使用方式與 RxJS 其實有很大部分是重疊的,但 RxJS 有很好用的 operators,這時候該怎麼辦呢? 是否有方法能結合兩者。在 GitHub 上面有一個 PR 就是要解決這個問題,Angular team 提供兩個 function,toSignal
和 toObservable
,這過這兩個 function 可以將 Observable 和 Signal 物件做彼此轉換,我是覺得這樣就可保留相當的彈性了,當然也要等實際使用在產品才能知道會有那些坑
[Update] Angular v16.0.0-next.6 實做了 fromObservable
和 fromSignal
兩個方法,想玩的朋友可以更新到新版
附上範例程式
1 | // toObservable |
宣告的位置跟 effect
是一樣的,不然也會噴錯誤訊息給你享用
在這之前還是快速介紹一下 Discord。Discord是一款專為社群設計的免費網路即時通話軟體與數位發行平台,在早期很常被遊戲玩家拿來做線上連線時的通訊軟體,現在是很多產品、社群等都會利用 Discord 來經營社群。
既然是平台,本身又提供 Bot 開發的能力,就要好好的探索一下到底 Bot 在 Discord 能做到什麼事情
在建立第一個 Bot 之前,假設你是純新手沒有開過 Discord 帳號,可以透過這篇說明建立自己的 Discord 帳號。
進入 Developer Portal 並登入應可看到這個畫面
點選右上角的 New Application
會跳出建立 Application 的詢問視窗,輸入你想的名字,打勾 agree Terms of services 後,按下 Create
即完成第一個 Application 的建立
建立完成後在 Applications
的列表上就可看到剛剛建立的 application,點進去會看到等等開發時所需要的資訊,類似 Application ID 和 Endpoint URL 。
因為我們要建立 Bot,所以還要多做一個步驟
完成這步驟後,會看到一些可以設定的項目,其中 Token 會是待會開發時需要的資訊,晚點再回來這邊建立新 Token
Bot 需要取得使用權限及授權範圍,Discord 也很好心的給了建立方式,一樣在 Application 的畫面裡,左邊選單的OAuth2
下的 URL Generator
,這頁面上,我們需要設定幾樣東西
bot
、applications.commands
Send Messages
和 Use Slash Commands
此畫面的最後面會有一個 GENERATED URL
,複製並貼到瀏覽器上,會開啟一個設定畫面,詢問這一個 Bot 想要加到哪一個伺服器中,就跟著步驟一步一步完成即可
雖然官方有提供一個範例程式碼,但我還是想從頭做一次,過程中還是會參考 example code
建立一個 node express 專案,我這邊使用 NX 來幫忙建立 workspace
1 | npx create-nx-workspace --preset=express |
現在我們有一個可以用的 express app 可以運作,但為了讓 Discord Server 可以將訊息打到我們的 App,需要透過 ngrok
的協助
INTERACTIONS ENDPOINT URL
中,當這樣設定完成後,Discord Server 就會將相關的訊息以 webhook 的方式打給我們假設填入的網址是 https://xxxxxx.ngrok.io/interactions
,express 這邊需要實做一個對應的POST
方法
1 | app.post('/interactions', (req, res) => { |
在上一步驟時進行設定儲存時,會出現這個錯誤訊息
要排除這個錯誤,需要實做一個 PING-PONG
的回應,實做程式碼如下
1 | import { InteractionResponseType, InteractionType } from 'discord-interactions'; |
discord-interactions
這時候回去 discord application 頁面按儲存時還是會失敗,而且 express 這邊也會噴 req.body
是 undefined 的錯誤,還少一個驗證 request 的功能
1 | app.use(express.json({ verify: VerifyDiscordRequest(process.env.PUBLIC_KEY) })); |
PUBLIC_KEY
可以從 Discord Application Detail 頁面中取得,放置 .env
檔案內即可1 | import { verifyKey } from 'discord-interactions'; |
這段功能補上去後,Discord Application 設定頁面應可正確的儲存了
上面已經完成最基本與 discord 互動的 Endpoint,接下來就是要是處理 Command 的部分,我們期望是 User 在 Discord 頻道中使用 Bot 來下指令,用一個 test
作為指令行為驗證。使用前要先註冊,這邊有點繁瑣,拆步驟說明
取得 APP_ID: 資訊可以從 Discord Application Detail 頁面上取得
GUILD_ID: 這部分稍微麻煩點
首先開啟 Discord 網頁版並切到有 Bot 的 Channel
網址大概會長這樣
Channels 後面的第一組數字就是我們要的 GUILD_ID 了
詢問 Channel 是否有註冊過 Commands,如果沒有就註冊
1 |
|
Command 宣告
1 | export const TEST_COMMAND = { |
當 express 啟動時執行
1 | const server = app.listen(port, () => { |
這段有實做一個 DiscordRequest
function,內容如下
1 | import fetch from 'node-fetch'; |
需要安裝 node-fetch
套件
1 | npm install node-fetch@^2.6.6 |
DISCORD_TOKEN: 來自 Bot 頁面的 Token
重新啟動 express app,應該可以看到 Command 被成功註冊的訊息,重新啟動一次 app,也可以看到 Command 已經被註冊的訊息。回到 Channel 中輸入 /test 就能看到被註冊的指令了
上一階段把 Command 註冊成功了,express app (Bot Service) 這邊也要實做對應的邏輯
1 | if (type === InteractionType.APPLICATION_COMMAND) { |
執行結果
初期要設定 Bot 在 Discord 上面跑需要一些設定,在寫這篇筆記時,比較會卡住的點是流程面,Discord 在相關的設定上其實還算簡單,第一關打通後,後面會比較順一點。所以稍微複雜的互動行為就留在下一篇筆記了
如果沒有安裝過 Dapr CLI
的,可以先安裝,他可以任我們在開發環境上執行、啟動、管理和除錯 Dapr instances。雖然不是必要但建議開發環境要支援 Docker
步驟如下
初始化 Dapr。 此步驟會安裝最新的 Dapr 二進位檔和容器映射,以設定您的開發環境。
1 | dapr init |
環境多準備了這三個 containers,這時候我們就可以準備來開發第一個 Dapr 應用程式
Dapr 有提供不同語言的 SDK,方便使用者能自然且直覺的與 Dapr 做互動
這裡的範例我使用 .net core console 來練習
建立一個 console 程式
安裝 Dapr.Client
套件
在 program.cs
貼上這段程式碼
1 | using Dapr.Client; |
counter
statecounter
state試著用 dapr
指令來執行程式
1 | dapr run --app-id DaprCounter dotnet run |
會看到程式會跑起來,而且也能看到 Counter 的值被持續增加上去,而且當重啟程式後,也會保留上一次的結果繼續
使用 dapr run
時,--app-id
很重要,state management building block 是使用這個為 prefix 的值,所以第二次執行不是使用同一個 app-id
則會被視為不同的狀態
還記得一開始在 dapr init
時,有啟動幾個 container,其中一個是 redis
,這也是 dapr 儲存狀態的地方,在上一篇也有提到每一個 building block 後面的元件是可以被抽換的,相關設定檔是透過 yaml 來設定,設定檔儲存位置如下
mac/Linux: $HOME/.dapr/components
windows: %USERPROFILE%\.dapr\components
從圖片中可以看到有一個 statesotre.yaml
的檔案,內容會是這樣
1 | apiVersion: dapr.io/v1alpha1 |
const string storeName = "statestore";
scopes
來限定能存取此元件的應用程式 (app-id
)為什麼會回來看 Dapr,最主要的原因是目前的工作,系統都是以微服務的形式跑在自架的 K8s 上,一旦到達一個規模整個管理跟實做上要考慮的事情變的相對複雜,而 Dapr 可以降低這部分的工作並以一致的模式套用在不同的語言跟技術框架上
官網上是這樣子介紹的
APIs for building portable and reliable microservices
Leverage industry best practices and focus on your application’s logic.
從圖片上可以知道 Dapr 是走 sidecar 模式,但好家在他底層是使用 go 實做,執行起來也不會太笨重,並提供簡化不少原系統要處理的東西。而且 Dapr sidecar 之間的溝通是採用 gRPC
的模式來降低整體的負擔,將效能影響降至最低
從這張圖尚可知道,原本的應用程式可透過 HTTP 或 gRPC 的方式與 Dapr 的元件溝通,在這模式下,應用程式間的互動會由 代理人
Dapr 來處理,之後我會說明為什麼我會將其定義成 代理人
的原因
Dapr 提供幾個積木供我們使用 (如下表),讓我們有抽換背後對應的服務 (抽象化)
Building block | Description |
---|---|
State management | Support contextual information for long running stateful services. |
Service invocation | Invoke direct, secure service-to-service calls using platform agnostic protocols and well-known endpoints. |
Publish and subscribe | Implement secure, scalable pub/sub messaging between services. |
Bindings | Trigger code from events raised by external resources with bi-directional communication. |
Observability | Monitor and measure message calls across networked services. |
Secrets | Securely access external secret stores. |
Actors | Encapsulate logic and data in reusable actor objects. |
一開始有提到 Dapr 是使用 sidecar
模式與 application 做互動,所以架構會長的像這樣
Dapr 的執行環境有分 self-hosted
和 container
模式
所以在本機開發環境如果沒有 docker 也還是可以透過 self-hosted
的模式進行開發,不會影響之後的部屬,self-hosted
可透過 Dapr cli 來完成相關的操作 (Dapr CLI installer)
Service Mesh 是另外一個偉大的坑,其負責範圍其實很多,科普一下
服務網格是一個可設定的基礎結構層,內建功能可處理服務對服務通訊、復原、負載平衡和遙測擷取。 它會將這些考慮的責任移出服務,並移入服務網格層。
Dapr 也遵循一樣的模式,所以接下來的問題會是 Dapr 可以取代原本的 service mesh 嗎? 其實 Dapr 可以與原有的 Service Mesh 共存,負責的業務範圍會不太一樣,Dapr 提供系統服務,service mesh 提供服務間的網路溝通
根據閱讀多篇文件瞭解,OIDC 是基於 OAuth 2.0 發展出來的,看起來得先看 OAuth 2.0 是什麼
OAuth 2.0 基本上處理 Authorization 的部分,用來控制授權誰能存取資源,有四個基本元素
其他名詞
authorization server
發出,會在發請求時附加在進去給 resource server
Authorization Code
這算是比較常見的模式,登入畫面會由 authorization server
提供,透過 redirect URI
的方式帶著 authorization code
回到 client
端供後面使用
這流程也是 Keycloak JavaScript adapter 預設行為
Implicit
使用場景是 SPA 或是純前端系統,與 Authorization Code
模式的差異在於 access token
的取得方式,
這模式比較不安全,「透過 URI Fragment 來傳 Access Token ,所以可能會外洩」
Resource Owner Password Credentials
這比較像是過往的 server side 網頁服務
Client Credentials
適用場景: machine-to-machine (M2M) applications
(圖片出自: [筆記] 認識 OAuth 2.0:一次了解各角色、各類型流程的差異)
瞭解基本 OAuth 2.0 後,那 OIDC 又是什麼,一開始提到 OIDC 是基於 OAuth 2.0 發展出來的
先提一下 OAuth 2.0
只有做 Authorization
的部分,並沒有涵蓋Authentication
的部分,這兩者的差異是什麼呢?
(圖片來源: https://openid.net/connect/)
整個的流程大概會是這樣
一些會出現在 OIDC 的名詞
https
scheme and MAY contain port, path, and query parameter components.在 KeyCloak Admin Console 內多新增一個 Client 並把一些設定全部關掉,在最新版的介面裡面已經找不到設定 access type
的介面了,Google 一番後發現只要將所有的 Authentication flow 全部取消掉,就是以前的 Bearer-only 模式
所謂的 Bearer-only 模式: the application only allows bearer token requests
設定完成後可以到同一畫面的右上角取額 setting json 內容
將內容複製起來,等等建立在 Core WebAPI 專案的地方用的到
先新增一個 ASP.NET Core WebAPI 的專案,並安裝 Keycloak.AuthServices.Authentication 套件
將上個步驟的 adapter config 內容新增到 appsettings.json
檔內,這邊是示範,Production 使用時請依正確做法設定
1 | { |
回到 Program.cs
檔案內新增 Authentication 的設定
1 | builder.Services.AddKeycloakAuthentication(configuration, o => |
記得在 app.UseAuthorization()
的上方加入 app.UseAuthentication();
1 | app.UseAuthentication(); |
上述完成設定後,就可以到 API 的地方加上 [Authorize]
的標籤
一旦加上去後,只要要呼叫這個 API 時,就會檢查 request header 內的 authorization 的 Bearer
值是否合法正確
如果從 angular application 呼叫 API 時,通常會撞上 CORS 的問題,這時候就得在 Program.cs
加上 Cors
的相關設定,減少大家 google 的時間,這邊就附上最不嚴謹的設定
1 | builder.Services.AddCors(options => |
Controller 的部分也需要加上 [EnableCors]
的標籤
在 Web 的部分會多判斷處理 Token 過期的問題,如果後臺有設定可自動 Refresh,那麼在呼叫 API 時就會去做 Token 更新的動作,之後才會進行 API 呼叫 (with authorization: Bearer xxxxxx)
]]>Keycloak is an open source identity and access management solution
. 他能提供 SSO 系統服務,一個簡單的驗證機制可以快速被建立出來,更多資訊可以到官網上閱讀,那為什麼要寫這篇筆記,主要是想要瞭解開發時,如何與 Keycloak 串接,所以要在本機上面將該環境給準備出來,以供開發使用。好家在的是在本機電腦上面啟動一套 Keycloak 的動作很簡單,拜 container 技術的進步,只要一行指令就可以完成安裝
1 | docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:19.0.3 start-dev |
admin
/ admin
http://localhost:8080
開啟 http://localhost:8080
會看到這個畫面
進入 Administration Console
會先看到登入畫面,帳號密碼如上面說到的 admin
/ admin
,登入帳後密碼後會看到一堆設定選單,我們的主要目的是要測試開發時能串接 Keycloak 的登入功能,所以會做以下幾件事情
於左上角的下拉選單中,可以看到 Crate Realm
,從這邊進行建立 realm 的動作
什麼是 realm
? realm
是一個 workspace 讓你可以管理 users、applications、roles and groups.
輸入一個名字後按下 Create
即可完成建立,建立完成後在左上角的下拉選單中就可以看到剛剛所建立的 realm
切換到剛剛所新增的 realm
,然後點選 Users
來準備建立第一個 User 帳號
點 Create new user
後,輸入一些基本資訊,按下 Create
完成新增第一個使用者
新增完成後,需要來設定一下登入密碼,在畫面上設定密碼的地方在
Temporary
:使用者於第一次登入時是否要重新設定密碼,預設是 On
表示使用者在第一次登入後是需要變更密碼的驗證剛剛設定的 User 是否能正常使用,可以透過開啟 Keycloak Account Console 的畫面來進行驗證,如果能正確的登入就代表設定正確
上述完成後就可以來試著串看看了,當然首發是 Angular
我覺得這塊是最困惑的地方,一個地方沒設定好,前端就沒法正常使用了
建立 clients
設定 Client ID,之後在 Angular 設定時需要
Capability Config
的頁面先保持預設值
Save
完成新增 Client
這時候會跳到 Client 的詳細頁面,這個頁面需要多設定一些網址資訊
http://localhost:4200/*
http://localhost:4200/*
http://localhost:4200
設定完成後按下 Save
儲存異動
假設已經有一個 Angular 專案
安裝 library
1 | npm install keycloak-angular keycloak-js |
設定 initial config (app.mdoule.ts
)
1 | import { APP_INITIALIZER, NgModule } from '@angular/core'; |
line 12: Keycloak server 位置
line 13: 要存取哪一個 realm
line 14: 使用的 ClientID
line 20: slient-check-sso.html
的內容是
1 | <html> |
line 16 ~ 21: 其他關於驗證的設定項目
當上述設定完成後,基本上整個 angular application 已經跟 keycloak 做好連接了
以下有一個簡單的範例,用來展示 login 前後的操作
app.component.html
1 | <h1>Keycloak Angular Example</h1> |
app.component.ts
1 | import { Component } from '@angular/core'; |
呈現畫面
而 login 後的 User 資訊到底可以取到什麼程度,也是可以從後台做設定的
所安裝的套件也好心的將這兩區塊的功能,範例程式碼都提供出來了,這裡就不多寫,直接附上連結
Keycloak 的設定很多,很多細節需要仔細的研究,但總的來說,功能很強也保有一定的彈性,例如 User Info Storage 的部分可以串接其他的資訊,或是支援其他的 Identity providers 等,開發上的使用也支援很多常見語言,例如 Java、.NET、JavaScript 、Python 等
引用官網的介紹
建立一個 playwright 的專案動作很單純,可以透過 npm init
的方式完成,步驟如下
npm init playwright@latest
等 npm install
結束後即可用 VSCode
開啟該資料夾專案,檔案結構很簡單
1 | playwright.config.ts |
playwright.config.ts
和 tests/
下的東西晚點來看,先來跑一下測試與測試報告
執行測試的指令: npx playwright test
測試報告指令: npx playwright show-report
在 playwright.config.ts
內充滿了滿滿的註解,想要看不懂設定真的有一定的難度,除了測試檔案資料夾或是 timeout 設定都算基本的,測試環境的設定也是在這個設定檔內,相信這個各位開啟檔案後應該知道怎麼處理了
先從預設新增的測試檔案說明起,因為 playwright 提供的 api 功能強大,需要分別研究,現階段先看個感覺
1 | import { test, expect } from '@playwright/test'; |
test
內可已有測試描述即要執行的測試程式page
物件的操作等同操作瀏覽器的一個網頁expect(page).toHaveTitle(/Playwright/);
如果有使用 VSCode,也可以安裝 playwright 的 extension,裝了之後可以讓測試 playwright 更輕鬆
看起來就是簡單好用,更多件介紹由此去
快速掃過官方文件,其實他可以做的不只有 E2E,也還可以做 API Testing,而且不像 cypress.io 一開始就那麼肥大,看起來是一個值得深入研究的工具
]]>