平台化三部曲之二模塊化開發 - Google Guice 平台模塊化開發的果汁
該文章來自阿裏巴巴技術協會(ATA)精選集
在前文《從Eclipse平台看交易平台化》中,主要探討平台的擴展機製。 本文將繼續探討平台化開發中另一個重要方麵: 模塊機製。在阿裏係統開發中,大家都有自己的模塊化開發方式。比如目前交易中的TMF框架(Trade Module Framwork)也是從模塊化開發解決業務隔離擴展。 Detail 2.0平台化項目中定義了一套自己的模塊化方式。本文想通過介紹Guice Module的模塊機製,介紹一種簡單強大的模塊開發解決方案,Guice在java開源軟件中被廣泛使用,也證明他的生命力。 希望給有開發需求的開發者可以直接拿來主義,在開發中提高效率。
Guice是什麼
Guice(讀作Juice)是Google開發的一套注入框架,目前最新版本是4.0Beta。注入的好處是將使用者與實現者、提供者分離,降低代碼間的依賴,提高模塊化程度。
Google的java開源產品,一般都具有良好的口碑,簡潔而強悍,比如guava類庫。 本文會隻涉及Guice的DI基本功能,更主要是介紹利用Guice來組織平台的模塊開發,在這點,Guice提供了非常好的解決方式,個人想通過這篇文章給需要模塊化開發的同學推薦Guice的Module解決方案。
Guice的基本DI功能
在java開發方麵,Spring是大部分係統采用的基本框架,我們利用Spring的IOC,DI等功能組織代碼。 Spring的成功和優勢無可置否,但是Google的Guice提供了更輕量級,更簡潔高效的DI框架,並且更高程度抽象出模塊的概念,為代碼開發提供了強大的工具。 他的基本功能可以通過Guice wiki中了解,
摘抄一些優點介紹:
- IoC中Bean的注釋:其實實現細節很是讓人不得不佩服,因此,很多的其它框架也開發模仿;
- 通過“prodivers”和“modules”實現編程配置:這相對於其它語言的實現方式而言,顯得更加的優美,至少認人覺得是一種比較實際可能的方法;
- 快速的“prototype”場景:可以通過CGLib快速的構建對象,這點讓我很激動。Guice的出現讓我們看到了其實prototype的bean和動態創建的bean其實也可以很容易的管理;
- Modules:module可以將應用程序分割成幾大塊,或是將應用程序組件化,尤其是對於大型的應用程序;
- Type safety:類型安全,它能夠對構造函數、屬性、方法(包含任意個參數的任意方法,而不僅僅是setter方法)進行注入;
- 快速啟動;
- 簡單、強大、快速的學習曲線;
- Guice的思想在一定程度上積極的影響著Spring和WebBeans;
- Guice的頭Bob Lee(https://crazybob.org/)不愧為IoC大師;
Guice DI基本原理: 創建一個GuiceInjector(一種DI依賴注入的實現方式)。 Injector將會使用它的配置完的模塊來定位所有請求的依賴,並以一種拓撲順序來為我們創建出這些實例
如果在新項目中,也建議用Guice作為DI解決方案。
Guice的模塊概念
Guice定義Module這個接口用來確定各個接口和他對應的實現的綁定。
通過bind("interface").to("implementation")的方式來完成接口實現的組裝。目前很多的開源框架都采用Guice進行模塊化開發,最典型的例子是**ElasticSearch**這個著名的分布式搜索引擎。 本文將大量通過ES的模塊化開發實踐來說明Guice是模塊開發的利器。 除了ES外,Apache Shiro, Apache Shindig等也也采用了Guice,越來越多的開源實開始將Spring DI換成Guice。
ok,從頭開始,所有的Guice模塊都實現一個通用接口Module, 這個接口定義很簡單,注釋裏也跟精確的解釋了模塊的定義和功能:
A module contributes configuration information, typically interface bindings, which will be used to create an {@link Injector}. A Guice-based application is ultimately composed of little more than a set of {@code Module}s and some bootstrapping code.
/**
* A module contributes configuration information, typically interface
* bindings, which will be used to create an {@link Injector}. A Guice-based
* application is ultimately composed of little more than a set of
* {@code Module}s and some bootstrapping code.
* <p/>
* <p>Your Module classes can use a more streamlined syntax by extending
* {@link AbstractModule} rather than implementing this interface directly.
* <p/>
* <p>In addition to the bindings configured via {@link #configure}, bindings
* will be created for all methods annotated with {@literal @}{@link Provides}.
* Use scope and binding annotations on these methods to configure the
* bindings.
*/
public interface Module {
/**
* Contributes bindings and other configurations for this module to {@code binder}.
* <p/>
* <p><strong>Do not invoke this method directly</strong> to install submodules. Instead use
* {@link Binder#install(Module)}, which ensures that {@link Provides provider methods} are
* discovered.
*/
void configure(Binder binder);
}
在configure方法裏,我們將接口的具體實現通過模塊綁定起來。 當這個模塊被使用時,他定義的接口實現就會被調用。
同時Guide提供了Module接口的一個抽象類AbstractModule,提供了一些模塊綁定的便利支持,同時推薦開發者的模塊都繼承這個抽象類。
Guice模塊開發實踐
對於平台來說,將係統的功能通過模塊來定義功能的邊界,模塊之間相對獨立。模塊作為平台功能的組成的基本形式,通過預定義配置和運行時配置來完成平台的組裝。
模塊的具體實現將利用Guice的Module功能,完成配置信息和綁定各接口的特定實現。平台啟動後,通過各種途徑將相關模塊整合起來,構建Guice Injector, 來完成將模塊定義的實通過依賴注入的方式,快速構建起來,從而創建一個複合的模塊係統。
給大家一個直觀的印象,看看下麵這個Elasticsearch的模塊圖:
點這裏可以看到完整的圖。 可以看到具體模塊的組織。 可以看到這麼一個複雜的分布式搜索引擎實現通過guice的模塊化處理,有了一個清晰的結構。
下麵我們將跟深入模塊實現的細節和一些實踐,用來更好的開發我們的業務平台。
Guice組織多業務可擴展平台開發
對於阿裏內部常見的多業務平台來說,模塊可能會分為係統模塊,領域模塊,業務模塊這三個類型。
- 係統模塊是用來建設平台本身需要的功能組織,比如平台的日誌模塊,監控模塊等。
- 領域模塊是某個領域的功能組件,比如交易領域裏的物流模塊,優惠模塊等。
- 業務模塊是業務方業務功能的實現,比如航旅開發出機票查詢模塊。
這樣的模塊化組織設計,為我們係統的分層,降低耦合度也非常有好處。
模塊元數據(Metadata)
我們可以給模塊定義元數據,用來描述模塊的基本信息,從而可以方便平台對模塊的管理和監控。
ModuleMetaData:{name(名稱), type(類型), setting(配置)}
名稱是模塊的基本命名,類型可以是模塊命名空間,setting保存模塊的更多配置信息。
模塊命名空間
多個模塊基於他們在源代碼樹或者代碼中的調用情況被放進了不同的命名空間中。不同的側麵,諸如插件,監控,環境設置服務是分割的功能實體,他們之間沒有模塊層麵的依賴。模塊可能已經太過龐大或者難以複合使用的將會被進一步分割成嵌套命名空間中的稍小的模塊。
如果把模塊係統類比成文件係統,模塊是一個文件,那麼命名空間就像是文件夾,相關的模塊通過命名空間歸類管理。命名空間和模塊形成一個樹狀結構。 通過命名空間+模塊名可以唯一的定位到一個模塊。
這樣平台的功能和結構便可以通過: {Namespace:Module Name: Bound Class}清晰管理起來。
模塊擴展和替換
大多數模塊僅僅會提供一個或者多個類或接口,但是某些模塊可以產生它們需要的新模塊。這些模塊通常依賴當前配置或輸入參數動態產生。這樣我們便可能通過插件寫出替代或者擴展平台的內置功能,然後通過一定的條件來開啟和配置這些插件。
比如我們有個ScriptModule模塊提供腳本功能,平台預先定義了Groovy和Python兩種腳本,平台可以根據配置或運行時根據輸入參數告訴平台動態采用某個Groovy或者Python,同時也可以使自定義腳本,開發平台的插件提供新的腳本模塊,並通過注冊到平台來滿足平台對腳本功能的擴展。
這種類似ScriptModule的模塊我們把他定義為可擴展模塊,模塊維護一個模塊內功能的注冊擴展機製,新的script實現可以通過該模塊registerScript方法注冊。平台在運行時,根據配置或者輸入參數,動態決定某個腳本被調用。
public class ScriptModule extends AbstractModule {
private final Map<String, Class<? extends NativeScriptFactory>> scripts = Maps.newHashMap();
public void registerScript(String name, Class<? extends NativeScriptFactory> script) {
scripts.put(name, script);
}
@Override
protected void configure() {
MapBinder<String, NativeScriptFactory> scriptsBinder
= MapBinder.newMapBinder(binder(), String.class, NativeScriptFactory.class);
for (Map.Entry<String, Class<? extends NativeScriptFactory>> entry : scripts.entrySet()) {
scriptsBinder.addBinding(entry.getKey()).to(entry.getValue()).asEagerSingleton();
}
}
}
需要更複雜靈活實現的話,可以在上麵的的基礎上跟進一步擴展,維護一個子模塊的注冊擴展機製,首先定義一個ScriptsModule:
public class ScriptsModule extends AbstractModule {
private final Settings settings;
private Map<String, Class<? extends Module>> scriptTypes = Maps.newHashMap();
public ScriptsModule(Settings settings) {
this.settings = settings;
}
/**
* Registers a custom script type name against a module.
*
* @param type The type
* @param module The module
*/
public void registerScript(String type, Class<? extends Module> module) {
scriptTypes.put(type, module);
}
@Override
protected void configure() {
bind(ScriptsTypesRegistry.class).toInstance(new ScriptsTypesRegistry(ImmutableMap.copyOf(scriptTypes)));
}
}
然後我們提供具體的ScriptModule實現,比如通過配置文件來配置新的腳本,也可以開發一個插件將新的CustomSciptModule注冊進去。從而跟靈活的擴展功能。
從這裏大家可以看到,**我們可以利用這種可擴展模塊的注冊機製,實現對多業務的支持**:
比如交易平台提供了優惠類型模塊, 每個業務可以把自己的優惠實現模塊注冊進去,平台會識別出業務類型後,自動找到對應業務的優惠實現,完成業務對優惠功能的定製需求。
{
"ticket":"TicketDiscountModule",//電影票優惠模塊
"flight":"FilghtDiscountModule",//機票優惠模塊
...
}
模塊構建使用
在了解模塊的功能和擴展方式後,平台需要構建和使用模塊。
平台啟動時,首先你的啟動類裏,可以指定需要加載的模塊,這個可以通過ModulesBuilder這個類來快速完成:
ModulesBuilder modules = new ModulesBuilder();
modules.add(new PluginsModule(settings, pluginsService));
modules.add(new EnvironmentModule(environment));
…
injector = modules.createInjector();
模塊加載完成後通過創建Guice injector, 就可以完成所有依賴實現的組裝。
這是,你就可以通過
client = injector.getInstance(Client.class);
得到需要的實現,完成相應的邏輯。
ModuleBuider有些很酷的特性,如果一個Module同時實現了SpawnModules接口,
public interface SpawnModules {
Iterable<? extends Module> spawnModules();
}
以為這這個模塊具有創建子模塊的能力, 那麼ModulesBuilder在加入這種類型的模塊是,會遞歸的創建出所有的子模塊。
public ModulesBuilder add(Module module) {
modules.add(module);
if (module instanceof SpawnModules) {
Iterable<? extends Module> spawned = ((SpawnModules) module).spawnModules();
for (Module spawn : spawned) {
add(spawn);//這是一個遞歸調用
}
}
return this;
}
總結
模塊化是對複雜業務平台分而治之的方式,通過模塊化開發,可以使我們的平台建設更高效更靈活。 Guice的Module功能再開源軟件中被廣泛使用,在本人自己過去使用經曆中,發現確實是個模塊化的利器,希望更多的人了解使用。
最後更新:2017-04-01 13:37:10