在進入主題之前建議先行閱讀「【程式語言 - Go】來認識Google開發的程式語言…」,初步認識一下Go語言是什麼? 容不容易學習? 才能夠更快的體會此篇章的目的。
當我們在進行軟體開發時,常常會需要有背後的資料庫系統來儲存我們的資料,而資料庫系統也會隨著時代的演進,進行大幅度的更新,那在這樣的時空背景之下,我們究竟要如何設計才能對於未來升級時較無痛的改動呢? 因為我們都知道一但程式架構需要大幅度的改動時就是開發人員噩夢的開始了…,為了我們美好的未來,只好認真學習一下軟體的設計模式了,避免沒日沒夜的工作消磨掉應有的生活品質。
雖然我們也可以使用別人開發好的Library來補上這一塊,我們只要注意版本的差異即可,But…理想的狀況之下,作者願意持續維護該套件,甚至是有著官方的支持,才能有持續更新的版本,否則一但失去了維護的狀況之下,要嘛就是自己接手過來修改,不然就是另外尋找替代方案來進行替換,但這些都是成本啊,要用什麼方法,就好好的審慎評估了,並不代表說自己設計一定最好,通常事情都是這樣的,有正就有反,沒有最好,只有最適合的一種策略。
那我們也知道平常使用資料庫時,無非就是新增、修改、刪除、查詢這四大方法為一個基礎,而後才會衍生各式各樣的統計啊…等的進階應用,那麼假設我們的系統非常的單純,只需要設計資料庫的基本方法時,我們可以這樣做…
那在進入主題之前不免俗的在傳教一下什麼是「工廠方法」,所謂工廠方法我們就想像成汽車工廠,一台汽車基本上要能夠「驅動」,因此「驅動」是所有車型最基本需要的實作方式,那汽車工廠只要設計好「驅動」的介面規格即可,至於子公司要生產什麼車都沒關係,只要將「驅動」的功能給時做出來就可以了。
這樣有什麼好處呢? 嗯….,一開始確實感受不到什麼好處,甚至設計時不斷懷疑自己是不是大砲打小鳥的感覺,但當有一天資料庫的API大改版時,我們就會慶幸當初做了這樣的設計,除了讓改動幅度傷害降到最低之外,測試的時候也能夠根據資料庫的版本切換不同的生產工廠,怎麼做呢? 就讓我們繼續的學習以下的實作細節。
這邊會以Elasticsearch資料庫搜尋引擎為例來進行示範,我們不需要包山包海的實作,只取其中的_search
、GET document
、_update
...等部份的API進行即可。
其實原理很簡單,假設我們今天要實現5.x跟7.x的步驟如下:
- 將使用到的ES API定義成介面。
- 基底類別實做上述的介面,並延伸實做一些各版本共用的方法。
- Es5繼承自基底類別。
- Es7繼承自基底類別。
- 開發者使用
elasticsearch.New(endpoint)
的時候, 就先判斷版本,然後告知工廠我要版本5或7的ES產線即可。
實做部份
- 假設我們只會使用到Get及Update。
// default.go
// EsInterface 定義我們會使用到的es方法
type EsInterface interface {
Get(esIndex, esType, esID string) (*Response, error)
Update(esIndex, esType, esID string, data interface{}) (*Response, error)
}
- 基底類別實做Interface
// default.go
// EsDefault 基底類別,其他版本可以繼承自此
type EsDefault struct {
EsInterface
endpoint string
}
// Get get documnent
func (e *EsDefault) Get(esIndex, esType, esID string) (*Response, error) {
// 這邊實做預設的API, 假設以v2當基底,我們就實做v2的API, 可以用套件或者單純的http request來實做Elasticsearch的Get
return nil, nil
}
// Update document
func (e *EsDefault) Update(esIndex, esType, esID string, body interface{}) (*Response, error) {
// 這邊實做預設的API, 假設以v2當基底,我們就實做v2的API, 可以用套件或者單純的http request來實做Elasticsearch的Update
return nil, nil
}
- Es7繼承自基底類別
// es7.go
// Es7 v7.x
type Es7 struct {
*EsDefault
}
// Get get documnent
func (e *Es7) Get(esIndex, esType, esID string) (*Response, error) {
// 這裡實做ES7的API。
return &response, nil
}
// Update update document
func (e *Es7) Update(esIndex, esType, esID string, body interface{}) (*Response, error) {
// 這裡實做ES7的API。
return &response, nil
}
- 最後用版本 + 工廠方法來產生相對應的類別
// default.go
// New elasticsearch interface
func New(endpoint string) (EsInterface, error) {
es, err := factory(endpoint)
if err != nil {
return nil, err
}
return es, nil
}
// version 取得es版本
func version(endpoint string) (uint64, error) {
resp, err := http.Get(endpoint)
if err != nil {
return semver.SpecVersion.Major, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
var verInfo *VerInfo
json.Unmarshal(body, &verInfo)
v, err := semver.Make(verInfo.Version.Number)
if err != nil {
return semver.SpecVersion.Major, err
}
return v.Major, nil
}
// factory 工廠方法,根據版本來產生相對應的Es類別
func factory(endpoint string) (EsInterface, error) {
v, err := version(endpoint)
if err != nil {
return nil, err
}
glog.Infof("Elasticsearch version: %d", v)
esDefault := &EsDefault{endpoint: endpoint}
var instance EsInterface
switch v {
case 7:
instance = &Es7{EsDefault: esDefault}
default:
instance = esDefault
}
return instance, nil
}
結語
其實這樣的概念不只用於軟體設計,各產業也都非常的適合使用,透過一開始的彈性設計規劃,雖然初期成本高且無實質效益,但難保有一天業績蒸蒸日上,產線也需要隨之擴大時,如果沒有規劃好的話很容易打掉重練,浪費時間之外也浪費了許多成本在做重複的事情,當我們想要永續發展一個事業體時不妨好好的思考一下是不是一開始就設計好產線,未來只要稍做調整就能夠再變出一個新的產品產線,來擴大營收,永續發展…。
如果有任何問題或錯誤的話不妨底下留言,讓我們互相學習,共同成長,一起將知識內化成為我們最堅實的後盾與最尖銳的武器,迎向快速發展的知識時代。
喜歡撰寫文章的你,不妨來了解一下:
Web3.0時代下為創作者、閱讀者打造的專屬共贏平台 — 為什麼要加入?
歡迎加入一起練習寫作,賺取知識!
留言
張貼留言