1. 程式人生 > >如何開發高度可定制的產品

如何開發高度可定制的產品

center 數據 擴展點 屬性 數據模型 測試 結果 耗時 添加

原文:how-to-develop-a-highly-customizable-product

翻譯:CUBA China

CUBA-Platform: https://cuba-platform.com

CUBA-China:http://cuba-platform.cn

歡迎轉載, 轉載請註明來源。



本文探討了幾種開發高度可定制項目的方案,包括CUBA平臺。

做為開發人員肯定聽客戶說過:“你們的產品還不錯,但是還有些細節需要完善”, 然後收到一份有數百個需求的“待完善細節”清單。 做為項目經理肯定也跟團隊說過:"公司即將拿到一個大單,但是需要咱們先。。。。。。" ,結果往往變成開發人員頭疼不已地去滿足客戶的各項願望清單。

那麽,如何既能滿足客戶的需求,又能使核心產品遠離客戶潛在的危險想法? 以特定技術方式設計的產品,如何既支持更多功能插件又能持續保證最高性能? 為解決方案提供可靠和出色的支持,需要面對多大的挑戰?

隨著商業世界對定制產品的需求越來越強烈,軟件開發行業發展出了許多通用方法來滿足客戶的定制需求。下面我們會介紹一些典型的方案,如果您對這些方案已經很熟悉,那麽歡迎您直接向下滾動到擴展方案段落,了解我們如何以更有效的方式解決這些具有挑戰性的問題


"全家桶"方案(All In One


最直接,最顯而易見的解決方案就是在一個核心產品裏實現所有的功能,然後為了滿足不同客戶的需求,再給各個功能加個

這個方案的主要優點就是,對於某些類型的產品適用,這樣的產品通常涵蓋該行業所有業務需求所以無需大量定制

這種方法的先天局限性隱藏在無需大量定制的假設中。通常,產品開發始於這一設想,但經過多次交付後,就會意識到客戶定制化需求的真實規模。然後就是進退兩難的局面:拒絕定制開發則可能失去客戶,繼續在核心產品上添加定制功能則導致源碼成為一個垃圾箱,核心產品代碼包含了多數用(戶根本用不到的功能、臃腫、難以維護。

這種情況下你會如何選擇?顯然,不管怎麽選都不是成功之道

總結:只有在你確定產品不會出現很多的個性化需求時,

“All in One”方案才是合適的選擇。 否則,你只能在產品的可維護性、可控性與客戶滿意度之間二選一。 對此,我們引用Jerry Garcia說過的一句話做出評價:Constantly choosing the lesser of two evils is still choosing evil(兩害相權取其輕也是害)


如果確實有重量級的定制需求“必須”交付,又不能使用大而全的方案,另一種直截了當的方式就是使用代碼分支 - 創建一個新的代碼分支,然後在這個分支上獨立修改。

分支方案與All in One比較,最大的優點是可以隨心所欲的做定制化開發,不用擔心一個分支的代碼修改影響其它客戶。 使用不同的分支來滿足不同客戶的特定需求,避免了在同一代碼庫中混合所有功能。

然而隨著產品發展,這種方案將會面對另一種“死法”。 因為大多數bug修復,產品改進,新功能都應用到核心產品分支上,而定制化開發在客戶分支上,因此,需要頻繁地合並分支以使所有定制分支與核心產品保持同步。 原始產品代碼沒有受到客戶分支的影響的話合並還算是一個簡單的操作,否則的話代碼合並會變得非常耗時並且無法避免地,回歸測試時肯定有bug。

如果只需要少量客戶分支,這個方案還是有效的。 然而,隨著交付的增加,頻繁地代碼合並將會成為開發人員的噩夢。

總結:分支方案無疑非常靈活和直接 - 產品的任何部分都可以修改。 然而,費力的部分發生在交付之後,並且隨著時間的推移會越來越費力,並且客戶分支太多的話就更加難以管理。


EAV:實體 - 屬性 - 值模型 entity–attribute–value model


EAV,即實體 - 屬性 - 值模型(又名對象 - 屬性 - 值(object-attribute-value)模型,垂直數據庫模型或開放模式)是眾所周知且廣泛使用的數據模型。 EAV支持動態實體屬性,通常與標準關系模型並行使用。

從產品化的角度來看,使用EAV的主要優點是您可以“按原樣”交付產品,然後通過在運行時添加所需的屬性來調整數據模型,從而保持源代碼的清潔。

但這個方案也有缺陷:

適用範圍有限 - EAV模型受限於僅允許向實體添加屬性,然後根據預先編寫的邏輯將其自動嵌入到UI中。

額外的數據庫服務器負載 - 垂直數據庫設計經常成為企業應用程序的瓶頸,因為企業應用程序通常有大量實體,實體也可能具有很多屬性。

最後,企業級應用往往需要成熟的報表引擎,EAV的“垂直”數據庫結構會給報表引擎開發增加大量復雜度。

總結:實體 - 屬性 - 值模型在某些情況下具有很大的價值,例如通過增加額外的不會在業務邏輯中明確使用的信息數據來實現靈活性。換句話說,EAV是對標準的關系模型和插件架構的一個很好的補充。


插件架構方案


插件架構是最流行和最強大的方法之一 - 定制功能邏輯被發布為單獨的制件,稱為插件。這種方案需要在產品源代碼中定義“定制點”(也稱為擴展點),應用程序在定制點檢查是否有插件要覆蓋開箱功能 插件的一個變體是外部腳本:所需功能采用外部腳本實現並在外部存儲,通過預定義的“定制點”控制調用外部腳本實現插件功能。

使用插件方案,產品代碼保持一直“幹凈”, 不會被不同的客戶化影響。研發團隊“按原樣”交付核心產品,定制化功能做在插件或者腳本中。另一個優點是軟件升級更新更易於管理,產品和插件的完全分離使得它們都可以彼此獨立地更新而互不影響。

當然,這個方案也有限制:主要是不可能完全預測客戶將來會提出哪些定制要求,只能先猜測應該嵌入“定制點”的位置。也許可以在各處都添加定制點以防萬一,但結果就是代碼可讀性差、難以調試,技術支持復雜度提高。

總結:如果“定制點”易於預測,插件架構方案確實有效,需要註意的就是“定制點”以外無法添加定制功能。


擴展方案


我們的企業級應用開發平臺CUBA采取了一種獨特的方法。CUBA是一個具有實踐性、開發人員驅動演進的平臺。 基於我們對現有產品的豐富經驗,在定制化開發方面,我們提出了兩個終極要求:

客戶定制化代碼應與核心產品代碼完全分離。

產品代碼的每個部分都應該支持可修改定制。

最終,通過CUBA的“擴展”機制,我們不但滿足了以上需求而且做到了更多。


CUBA擴展


擴展是一個獨立的CUBA項目(lib庫),它繼承了父項目(比如說核心產品)的所有功能。開發人員能夠在擴展裏開發新的功能而且不會影響到父項目,更厲害的是由於使用了Open Inheritance模式和CUBA的特別機制,開發人員可以在擴展裏重寫父項目的任何部分。總之,CUBA擴展就是開發團隊實現本文開頭提到的數百個“小細節”的地方。

實際上,每個CUBA項目都是CUBA平臺本身的擴展 - 因此任何平臺功能都可以被重寫。我們自己也采用這種方法從核心平臺中分離出一些開箱即用的功能(全文搜索,報表,圖表等)。如果您在項目中需要它們,就把它們添加為父項目 - 就像多重繼承!

用同樣的方式還可以構建多級擴展結構。聽上去有點復雜但是很實用。舉一個現成的例子:Sherlock - 是Haulmont的出租車全周期管理解決方案,支持出租車業務從預訂、派單到計費的方方面面。該解決方案滿足了客戶許多不同的業務,其中相當一部分與地域相關。例如,所有英國出租車公司都有相同的法律規定,但其中許多不適用於美國,反之亦然。顯然,我們不希望在核心產品中實施所有這些規定,原因是:

這是“特定運營區域”的功能

不同國家當地的法規可能對出租車車隊運營產生完全不同的影響

一些客戶根本不需要類似的法律規定

因此,我們這樣組織多級擴展結構:

核心產品包含出租車業務的通用功能

第一級定制實現區域定制化功能

第二級定制涵蓋特定客戶的定制需求(如果有的話)

技術分享圖片

顯而易見,通過使用擴展,既不需要客戶分支也不需要把客戶需求都集成到核心產品中,代碼還清晰可控。 看上去好得難以置信,接下來看看它是如何實現的:


向現有實體添加新屬性(實體定制)


假設Product產品中有一個User實體,它包含兩個字段:login和password:

技術分享圖片

然後根據客戶要求需要向上述實體添加“家庭住址”字段,我們就需要在"擴展"中擴展User實體:

技術分享圖片

上述代碼中除了@Extends之外的所有註解都是常見的JPA註解。 @Extends註解是CUBA引擎的一部分,它全局地將User實體替換為ExtUser,包括替換掉原Product產品裏的User實體。

使用@Extends屬性,我們在平臺裏強制做以下事情:

1. 始終創建“最新子類"的實體

User user = metadata.create(User.class); //ExtUser 實體將創建

2. JPQL執行之前做轉換,以便它們始終返回“最新子類”的集合

select u from product$User u where u.name = :name //返回ExtUsers集合

3. 始終在關聯實體中使用“最新子類”實體

userSession.getUser(); //返回 ExtUser 類型

換句話說,如果聲明了擴展實體,整個解決方案(核心產品和擴展)中的基礎實體都會被擴展實體替換。


  界面定制


以上我們通過添加地址屬性擴展了用戶實體,現在要反映到用戶界面中。 原始產品(Product)界面聲明是這樣的:

技術分享圖片

如圖所示,CUBA界面描述使用普通XML語法。 雖然我們可以簡單地在擴展中重寫整個界面的XML描述(先復制粘貼原XML的大部分內容),但是如果將來原界面某些內容發生變化就得手動將這些更改復制到擴展界面。為了避免這種情況,CUBA引入了界面繼承機制,在擴展中只需要描述對界面的更改即可:

技術分享圖片

如圖所示,使用extends屬性定義要繼承的界面,然後描述要更改的界面元素。

看看結果:

                技術分享圖片

修改業務邏輯


CUBA平臺使用Spring Framework實現業務邏輯,Spring Framework構成了平臺基礎架構的核心部分。

舉例,有一個bean用來做價格計算:


技術分享圖片


要重寫價格計算邏輯,只需要簡單的兩步:

首先,擴展(繼承)核心產品中的類並重寫相應的方法:

技術分享圖片

其次,使用原有的bean標識符在Spring配置中註冊新類:

技術分享圖片

然後對PriceCalculator的註入將始終返回擴展類實例,所以新邏輯將在整個產品範圍內生效。


  在擴展中升級核心產品版本


隨著核心產品的發展和新版本的發布,您很可能會打算將擴展升級到最新的核心產品版本。 這個過程也很簡單:

   1. 在擴展中指定核心產品的新版本號

   2. 重新構建擴展:

如果擴展是基於產品API的穩定部分構建的,則可以直接運行擴展。

如果核心產品API進行了一些重大修改,並且這些修改與擴展中實現的定制化開發重復了,則有必要在擴展中支持新的核心產品API。

大多數情況下,產品API在更新時不會發生顯著的變化,尤其是在小版本中。 但即使出現API“大爆炸”,產品通常會保持至少兩個未來版本的向下兼容性,舊的實現標記為“已棄用”,允許將所有擴展遷移到最新的API。


總結

現在我們以表格形式對以上方案做一個簡短的總結:

                 技術分享圖片                                                            

顯而易見,擴展方案很強大,但它不支持運行時定制(動態定制)。為了彌補這方面,CUBA也支持Entity-Attribute-Value模型和Plugin/Scripting方案。

鏈接

CUBA-Platform 官方網站 : https://www.cuba-platform.com

CUBA China 官方網站 : http://cuba-platform.cn

技術分享圖片技術分享圖片


如何開發高度可定制的產品