在拆分业务库的时候,我们面临着如许的题目:业务之间的关系是较为复杂的,怎样去拆分业务库,才是较为公道的呢?一开始我们准备根据外卖业务核心流程:页面→商家→下单,去拆分外卖业务。但是随着外卖子频道业务的快速发展,子频道业务也建立了本身的研发团队,在页面、商家、下单等环节,也开始建立本身的页面。如果我们仍然按照外卖下单的流程去拆分库,那在同一个库之间,就会有外卖团队和外卖子频道团队共同开发的情况,如许职责界限很不清晰,在实际的开发过程中,肯定会出现理不清的情况。
我们都知道软件工程领域有所谓的康威定律:
Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations. - Melvin Conway(1967)
翻译成中文的大概意思是:设计系统的组织,其产生的设计等同于组织之内、组织之间的沟通结构。
在康威定理的指导下:我们认为技术架构应该反映出团队的组织结构,同时,组织结构的变迁,也应该导致技术架构的演进。美团外卖平台下包罗外卖业务和垂直品类业务,对于在我们团队中已经有了组织结构,优先组织结构,去拆出独立的业务库,方便子业务库的同学内部沟通协作,减少他们跨组织沟通的成本。同时,我们将负责外卖业务的大团队,再进一步细化成页面小组、商家小组和订单小组,由这些小组的同学去在外卖业务下完成更细维度的外卖子业务库拆分。根据组织结构划分的业务库,自然的存在业务界限,每个同学都会按照本身业务的目标去继承完善本身的业务库。如许的拆库对内是高内聚,对外是低耦合的,有用的低落了内外沟通协作的成本。
工程内代码隔离
在实现工程隔离之后,我们发现工程内部的代码还是可以相互引用的。工程内部如果也不能实现代码的隔离,那么工程内部的界限就是含糊的。我们希望工程内至少能够实现页面级别的代码隔离,由于Activity是构成一个App的页面单元,围绕这个Activity,通常会有大量的代码及资源文件,我们希望这些代码和资源文件是被会合管理的。
通常我们想到的做法是以module工程为单位的相互隔离,但在module是相对比较重的一个约束,难道每个Activity都要建一个module吗?如许代码结构会变得很复杂,而且针对一些大的业务体,又会形成巨大化的module。
那我们又想到规范代码,用包名去人为约定,但靠包名约束的代码,界限含糊,时不时的告急需求,就把包名约定打破了,而且资源文件的摆放也是任意的,迁移成本高。
那怎么去解决工程内部的界限题目呢?《微信的模块化架构重构实践》一文中提到了一个重要的概念p(pins)工程,p工程可谓是工程内约束代码界限的重要法宝。通过在Gradle里面设置sourceSets,就可以改变工程内的代码结构目录,完成代码的隔离,设置示例:
sourceSets {
main {
def dirs = [‘p_widget’, ‘p_theme’,
‘p_shop’, ‘p_shopcart’,
‘p_submit_order’,‘p_multperson’,‘p_again_order’,
‘p_location’, ‘p_log’,‘p_ugc’,‘p_im’,‘p_share’]
dirs.each { dir ->
java.srcDir(“src/ d i r / j a v a " ) r e s . s r c D i r ( " s r c / dir/java") res.srcDir("src/ dir/java")res.srcDir("src/dir/res”)
}
}
} 效果如图所示:
p工程满足了工程内代码隔离的需求,但是别忘了,我们每个模块在外卖两个终端上(外卖App&美团App)上大概存在差异,如果能在模块内部实现两端差异,我们的目标才算告竣。基于上述考虑,我们想到了利用Gradle提供的productFlavors来实现两端的差异化。为此,我们需要界说两个flavor:wm和mt。
productFlavors {
wm {}
mt {}
}
但是,如许生成的p工程是并列的,也就是说,各个p工程中所有的差异化代码都需要被存放在这两个flavor对应的SourceSet下,这岂不是跟模块间代码隔离的理念相违背?理想的结构是在p工程内部进行flavor划分,由p工程内部包容差异化,继承改成Gradle脚本如下:
productFlavors {
wm {}
mt {}
}
sourceSets {
def dirs = [‘p_restaurant’, ‘p_goods_detail’, ‘p_comment’, ‘p_compose_order’,
‘p_shopping_cart’, ‘p_base’, ‘p_product_set’]
main {
manifest.srcFile ‘src/p_restaurant/main/AndroidManifest.xml’
dirs.each { dir ->
java.srcDir(“src/ d i r / m a i n / j a v a " ) r e s . s r c D i r ( " s r c / {dir}/main/java") res.srcDir("src/ dir/main/java")res.srcDir("src/{dir}/main/res”)
}
}
wm {
dirs.each { dir ->
java.srcDir(“src/ d i r / w m / j a v a " ) r e s . s r c D i r ( " s r c / {dir}/wm/java") res.srcDir("src/ dir/wm/java")res.srcDir("src/{dir}/wm/res”)
}
}
mt {
dirs.each { dir ->
java.srcDir(“src/ d i r / m t / j a v a " ) r e s . s r c D i r ( " s r c / {dir}/mt/java") res.srcDir("src/ dir/mt/java")res.srcDir("src/{dir}/mt/res”)
}
}
} 最终工程结构变成如下: