網路城邦
上一篇 回創作列表 下一篇   字體:
物件導向設計的程式碼真是噁心
2022/08/15 18:44:38瀏覽772|回應0|推薦5

物件導向設計到處都是。

但能看得爽的,可以容易看出意圖與架構,好上手好維護的物件導向設計,很少見。

 

最近再嘗試在web api架構的程式下,增加那個WebSocket雙向長連結的架構支援,以避免開發聊天應用的後端程式,之前需要一直輪詢是否有新訊息的架構問題。

之前開發Api的後端架構是PHP開發的,商業邏輯與共用的程式碼都整理過了,不想重工,可以靠autoloader需要直接new出來用就好,所以就找個同語言的WebSocket的工具來提供吧。

這個WebSocket的服務概念其實很簡單,接受連線,握手成功後將連線升級成WebSocket的長連結,之後雙方互相監聽對方的訊息派送事件就好。

訊息格式基本上就是自訂,自訂文字還是自訂二進位資料隨你,收到怎樣的訊息該做怎樣對應的處理自己想辦法。

 

所以呢?

找工具,安裝,看範例先搞看看。

就很簡單,前端開個瀏覽器來測試,在控制台下指令建立連線,並在連線網址傳連線參數過去,連上了服務器端回傳訊息給前端,前端收到log下來就能確認機制有完成,連線有成功那樣。

網路上的範例就是前端網頁連線上去後,在連線成功後送一個訊息,服務端收到訊息的處理就是把所有已建立的連線都檢查一次,只要不是送訊息的那個連線就通通轉發收到的訊息。

測試過,也沒問題,可以運作了。

嗯,然後呢?

 

這樣的WebSocket服務根本沒有用啊。

要寫個聊天通訊服務,不可能是對所有人傳訊息那樣。

會有聊天室的定義,同聊天室的人才收到,如此一來也需要登入身分才能管理誰在哪個聊天室,與這個聊天室的訊息應該送給在這個聊天室的在線成員,這樣才有基本的使用情境。

連線WebSocket之前,可以先登入拿到連線身分,之後用那個憑證來建立WebSocket連線,這樣就可以解決身分判斷的問題了。

疑?怎麼抓建立連線時網址後面的問號後的參數值呢?

傻蛋查看程式碼,看了半天也看不懂這個到底是什麼鬼,最後呢,是google找到有神奇的案例,照那個案例的寫法,喔,有抓到參數了。

傻蛋嘗試把範例裡面的連線變數印出來查看,發現那大到不可閱讀的程度,而且只能dump傾印,無法序列化成文字。

傾印的內容在服務端無法一個畫面呈現,最後設法把傾印的內容弄進文字檔,才確實看到那個變數的狀態。

額,這種連線和數據庫連線一樣是無法序列化的,也不是靜態的簡單物件。

印出來的內容看起來會含有完整程式碼,很瞎。

跑第一次,印出來的文字檔大小一百多kb,連兩次時再傾印出來的文字檔又大了幾十倍,慢到一個不行,這到底是啥鬼?

如果真的變數那麼大,那麼吃記憶體,這個服務根本就不可用啊!

不過仔細測試之後,不要傾印,只顯示新連線後當前服務使用的記憶體位元組數,來確認增加連線的記憶體使用量變化,斷線後的記憶體使用量,經小心的測試後,喔,沒問題,一個連線增加的記憶體使用量只有十幾kb,應該只是因為連線無法被轉成字串顯示,所以才會有這樣的誤會。

傻蛋看程式碼範例是用一個特殊的,從沒用過的物件集合資料型態在保存所有連線中的連線。

這玩意兒真的很難用,當需要用用戶id來判斷是否要推送訊息給這個連線,

這個資料型態只能全部翻一遍一個一個判斷,而且還要使用從連線挖出建立連線時的連線參數的處理方式,才能知道這連線到底是誰,是否要推。

真的很難用。

傻蛋想說,真的有必要照範例那樣寫嗎?

於是改用key-value的陣列,連線驗證完成時拿到用戶id,直接拿來當key試看看,不要用範例的那個神奇的物件集合資料型態來保存看看。

結果,可以用,拿到連線後指定某一個連線推送,沒問題喔。

另外再測試看看,改這樣寫的話,是否占用記憶體會不會比較浪費,結果感覺不出來明顯的差異。

看起來連線這東西會被特殊的方式保存並讓人很難log出來查看就是了,但仍然可以用方便的格式來保存這些連線的參照並使用。

所以要實做太久沒有動作的連線要主動踢掉斷線避免浪費資源,也完全可以把相關的連線時間與狀態保存在idkey的那個自訂義變數結構裡面就好。

好喔,看起來應該是大致能用了吧。

 

接下來傻蛋繼續嘗試一些實用的改造,如把訊息先由物件轉字串再轉二進位再丟給對方那樣,而且當訊息太長時先壓縮後再傳,收到壓縮格式就解壓縮再解開來使用。

基本上,可行,除了一個例外,就是php要回傳二進位內容的那段會出現錯誤。

一個奇怪的語法錯誤。

除此之外,前端瀏覽器那邊的測試,無論是傳有轉二進位的資料,還是傳原始的字串,服務端收到的事件資料都是原始的字串,不需要還原二進位格式。

這是怎樣?

看起來用來建WebSocket服務的那個工具,叫Ratchet的,就是不支援二進位的格式吧。

都開源的,有程式碼啊,查看看,能不能改改。

這個真的不好找,之前找如何在建立連線時抓到請求的網址參數時,一開始也是先看看程式碼,後來在一堆物件定義轉來轉去還是看不懂怎抓,後來找到抓取的寫法其實是有點意外好運的。

但既然有明確的錯誤,找到錯誤的位置,然後設法調整一下看看能不能支援二進位格式,畢竟據說效能比較好。

結果找半天,挖勒,快找到的時候才發現那個開源專案在核心處理是引用另一個開源專案的。

無言。

所以先算了,不能轉二進位就算了,文字就文字吧。

 

第三個碰到的困難是,如果不是前端WebSocket連線的請求,而是獨立的api請求,那能不能在某個api事件時推送特定訊息給某些連線中的用戶呢?

這個WebSocket的服務是獨立的進程,和api是不同也不互通的。

或許不是不可能,但應該不容易,而且解決方案可能和作業系統環境還有關係,總之是個麻煩。

想想看啊。

最後呢,是讓api服務需要通知WebSocket服務推訊息時馬上連線過去送完命令後再斷線,這樣會比較簡單。

測試一下,再裝一個library來方便建WebSocket連線,確認可以在api事件指定推送訊息給某些連線中用戶,可行。

 

至於這個WebSocket架構的主要程式邏輯呢,就在於前後端收到事件訊息的解析與分派上。

最簡單的設計是訊息一律是個json物件,必須有個eventcodekey,然後設計每個事件應該對應的資料屬性格式。

收到指定事件時呼叫對應的function處理即可,可以固定一個地方集中寫死就好,也可以把實際執行functionclassmethod直接放設定檔,直接動態呼叫原有的ServiceModel的方法就好。

需要注意一下這不是一個請求配對一個回應,可以不回應,也可以推送給其他指定的在線用戶,這和原本的Api介面不可能完全共用,也需要回傳具體的事件的代碼讓收到推送訊息的客戶端知道這是哪個事件與該如何回應處理。

動態一點的呼叫方式是方便代碼組織得比較乾淨好看而已,但那容易有安全上的洞,其實沒有比較厲害,自己衡量吧。

WebSocket的訊息介面就是如此的粗暴簡單,我傳訊息給你,你傳訊息給我,訊息格式自己設計自己實現,要設計聊天室留言後端直接下訂單呼叫外部api都行,流程自已要想清楚就是了,思慮不周設計有漏洞,就自求多福吧。

如果想用WebSocket的方式取代原本的apirequest/response也是可以的,只是前端的處理架構要整個改掉,效能好一點點但架構複雜度提高,不見得划算,就先不研究了。

 

所以啊,要用聊天室介面來替代傳統的表單頁面的設計應該是可行的。

中小型不是大平台的應用,這樣的架構基本上就夠用了,apiWebSocket服務和前端框架串接是可以實現想要的使用者介面的。

如果需要大平台高併發,則需要將服務變成集群化可以複製多個節點那樣,沒明確需求前就先不搞了,光把環境弄出來就要燒點錢了,沒必要。

 

是啊,其實這種需求,理論上應該有更成熟的可用的產品可以直接用。

但那就不是開源找一找兜一兜可以免費拿來用的了。

如果不照實際需求情境去想一下,嘗試一下,也不會發現那些範例程式是如此拉垮無用喔。

而傻蛋的這些嘗試,都是出於否些具體目的需求而發生的,實際上解決後,也沒有什麼高深與厲害的,反而是程式土土醜醜的,但確實能滿足自己的需要。

如果好好整理並寫些引導註解文件,開發一個web api服務和WebSocket服務並不是什麼高深的技術,隨便來個邏輯還行的人都能上手學會吧?

其實就是沒啥了不起的,不用多聰明,只要一步一步學,有一段一段完成,是個人都能學會吧。

這個過程裡面,好像沒有任何物件導向的設計思考喔,需要什麼就怎麼設計,那些物件很多只是把一些功能分類打包起來的分類而已。

大多數情況根本不需要抽基類,不需要設計界面,甚至不需要是物件,之所以還是宣告為物件常常只是為了方便使用autoloader方便執行時按需動態載入而已。

盡量都直接用就好,使用的細節盡量封裝起來,容易懂容易用就好,不然就是自找麻煩。

而傻蛋引用的那些開源Library,用起來是還算方便,至少不用什麼都自己想辦法搞出來,但讀起代碼來呢,好個物件導向,那可是好個陡峭學習曲線呢。

( 不分類不分類 )
回應 推薦文章 列印 加入我的文摘
上一篇 回創作列表 下一篇

引用
引用網址:https://classic-blog.udn.com/article/trackback.jsp?uid=pondin&aid=176860132