标签
| 架构 敏捷 咨询
作者
| 张逸
本文通过我在咨询工作中的真实案例讲解了
如何将敏捷开发的Inception与可视化咨询手段结合
。2014初,我作为咨询师为客户提供咨询服务,负责一个新项目的敏捷咨询、架构以及开发编码工作,距今已有四年时间,文中内容已经不再敏感。个人认为,这次咨询的一些方法与经验有值得借鉴之处,因而分享给大家,以资参考。
破冰之旅
我们要开始的项目是重写原有的一个版本管理系统,将其一部分内容从主控板中剥离出来,并将原有的C实现改为Java实现。项目的目标和范围相对确定,但架构方案还有待进一步确认。此外,团队成员只有C语言背景,没有任何Java语言的背景知识,也不了解面向对象编程。针对此情况,我决定对项目做一个Inception。下图是我确定的Inception计划:
我们召集相关干系人(包括支持软件产品部部长,部门的大项目经理,版本管理人员,项目组所有成员)参与了Inception的Kick Off会议。会议中,我们一起梳理了项目的目标与范围。整个咨询项目的目标为:
在确定项目的目标与范围后,我开展了破冰之旅,以热气球的形式展现团队的动力与阻力:
部门领导对这个项目非常重视,希望树立一个推行敏捷的模型,获得落地经验,并将这些经验分享和推行到整个部门。因此,部门领导给与了很大的支持。不过,挑战也非常大。
首先,团队成员完全不了解Java,也没有任何OO知识。其次,团队成员也不了解敏捷。同时,还需要承担相对紧张的交付压力。项目的特殊性决定了部门领导对项目高质量的重视。整个项目的主要功能点并不多,但涉及到的场景非常多,情形也比较复杂。同时,设计的系统必须具备良好的可扩展性和低缺陷率。在交付项目的同时,咨询师还需要培养团队成员,带领团队成员在技术和敏捷实践上获得提高。
需求的梳理
针对需求,在Inception阶段,我着重进行对Master Story的识别与拆分。由于该系统的主要工作是对原有系统进行重写,除了极个别的新需求外,需求功能与范围都是明确无误的。但考虑到我们需要在Inception阶段确定MVP(最小可用产品),从而获得发布计划与迭代计划,重新梳理需求是有必要的。而且,这个梳理过程要求整个团队的成员都参与,这也是一个极佳的通过协作明确需求的机会。
我在梳理需求时,
首先还是从调用者(用户角色)的角度出发
。这相当于Use Case中的Actor。从这个视角去分析需求功能,可以更容易帮助我们去识别需求的价值,并找到系统的边界。该项目的情况比较特殊,从使用者角度看,仅有外场的用服作为参与者;但调用或触发功能的角色却不仅限于用服,一些参与的系统也可以视为Actor,这其中包括:SCS、DBS和VMP Timer。
项目主要的功能就是对版本规格包的管理(包括升级、回退、删除等),因而有必要识别规格包的类型。可以分为:BBU、RRU和固件,而且BBU和RRU还包含了补丁规格包。补丁规格包又分为冷补丁和热补丁。这些规格包可能组合。同时,不同的产品制式包含的规格包也有所不同。
站在Actor的角度,我们梳理出了如下图所示的Master Story列表,并以用例图展示:
我们可以将这些用例视为系统的一级功能或者Feature,它有助于我们建立对整个系统的全局感官。但这样的分解太粗犷,因此还需要继续细化。我使用了卡片来帮助我们表现功能之间的层次关系。同时,根据规格包的类型,又以表格的方式展现各种规格包之间的异同。例如下图就是关于版本升级功能的需求分解:
通过这样的需求分析活动,团队成员基本上就系统的功能达成了一致意见。现在,我们就可以把最高层次的Feature卡排列在白板上,根据大家对系统的认识来确定优先级,并基于功能完备性选择可以放在同一个MVP的Feature卡:
为了避免需求的缺失,并确定这些功能与网管系统能够对应,我们还做了Requirement Map,如下图所示:
我们将整个项目周期划分为四个MVP。我们一致认为版本的升级是整个系统最重要也是最主要的功能,它的价值最高,可能存在的风险也最高,完全有理由放在迭代的最前面。最初,我们并没有考虑将“查询”功能放在最高优先级,相较于“升级”和“上电”,甚至于“回退”,它的优先级都有所不如。但当我提出如何在每个发布阶段进行演示时,大家才认识到如果不尽快实现查询功能,可能会使得我们发布的最小版本并不可用。我们当然也可以考虑命令式脚本进行查询,但这样的开发成本相较于开发UI的查询功能而言,并没有太大的优势。
虽然所有规格包的升级功能在重要性方面都要高于“回退”以及“全同步”。但由于升级功能的工作量最大,所有规格包的升级功能开发完毕大约会占用超过1/2的时间。从最小可用的角度来看,我们选择了整体功能的完备性。因此将固件与补丁包的升级放到了更其次的MVP 3。而且我们认识到,一旦开发完BBU和RRU规格包的升级,对于固件与补丁包而言,实现就变得简单了。
我们之所以考虑将“全同步”与“预上电”功能放到了MVP 2,与它们的重要程度(从重要程度讲,它们要低于升级与回退)无关,而在于对它们的开发需要与其他团队协作。若能将这部分功能的迭代周期提前,可以便于我们更好地与其他团队进行协作,同时,帮助我们提前发现风险。
在分析需求时,团队还统一了主要的领域术语,并确定了各个领域对象之间的关系。我用各种颜色的即时贴来展现(并非采用四色建模),这其中,绿色即时贴代表受控板,白色即时贴为主控板,即CC。NodeB为基站,有时候也称为网元,为保持一致,我们决定统一为NodeB。我们还识别出Small NodeB的概念。对于Small NodeB而言,BBU和RRU是在一块单板上的:
RAID系统分析
在梳理了VPM的需求后,我在Inception阶段开展了对系统架构的分析。由于这个系统自身并不复杂,但牵涉到太多的系统,且系统与系统之间的通信采用了各种方式,且系统的稳定性和性能的要求都较高,因而需要对整个系统的风险进行充分地识别。为此,我引入了RAID(Risk, Assumption, Issue, Dependency)分析,如下图所示:
进行RAID分析要比单纯的问题分析更为收敛,因为它明确了头脑风暴的范围及其类别。
识别风险
通常而言,对风险的识别可以引导我们对系统质量属性的思考,利益相关者可以充分表达对这些属性的担心,从而驱动我们去寻找解决方案。
稳定性
在这次RAID分析中,网管系统的负责人明确提出了对稳定性的担忧。由于之前的版本管理系统驻留在主控板CC中,网管系统(Java平台)与主控板以及受控板之间的通信较少。在将版本管理系统的部分功能转移到Java平台之后,形成NVUM专有模块并由网管系统发起调用。这就导致网管与主控板之间的通信可能会增多。从过往的系统看,这种通信的稳定性欠佳。基于这一问题,我们在后续的架构设计中对此进行了深入分析,决定在主控板一端设计粗粒度的接口,一次性地传递升级需要的信息,并以写文件的方式,将返回的数据传递到NVUM。
可扩展性
风险对扩展性的识别,帮助我们确立了一个架构原则,就是版本规格包的结构不应该影响到主控板的系统。这是因为主控板系统的升级受到的制约最多,我们不希望当产品发生变化时,影响整个版本管理系统。
性能
当选择的基站数量较多时,系统的版本升级过程会变得缓慢。而版本升级必须要求无线基站不能处于shutdown状态,否则会影响业务。因此,升级过程通常会选在凌晨,并且必须在较短时间内完成整个升级工作,故而性能可谓重中之重。目前网管采用并发方式为每个基站分配一个线程进行升级。由于本次系统的设计采用了配置文件的形式,网管担心在启动多个并发线程时同时加载多个配置文件,可能会导致OutOfMemory异常。这个风险的识别及时地为我们敲响了警钟。我们为此安排了技术Spike,并确定确实存在这方面的问题,目前还在解决之中。此外,为了提升性能,主控板与受控板之间的版本下载也需要并发进行,即当一个版本文件下载到主控板时,不必等到所有文件下载完毕,就可以并行地传递给受控板。就这个风险,我们讨论的结果是在新版本中可以支持这一功能,但在旧版本升级到新版本场景下,无法支持。
给出假设
Assumption可以是关键的架构约束,也可以是系统功能性的约定。架构约束既可能是设计的阻力,也可以成为动力。经过讨论,我们基本上确定了两条最为重要的架构约束,包括:
-
系统必须支持双向兼容,即网管一侧的NVUM(Java以及Scala)与主控板一侧的VMP Manager(C语言)互为兼容。例如,倘若NVUM升级为新版本,同样可以管理旧版本的主控板系统;反之亦然。这个约束的提出,要求我们在开发过程中,只要我们的接口已经发布,则不能再修改接口。除修复缺陷外,我们不能删除旧有功能,只能增加新功能。若旧有功能不合适,我们也不能删除,但可以将其置为
@deprecated
标注。
-
版本升级过程中,若前后操作具有依赖关系,则必须保证事务的一致性,要么全部成功,要么全部失败。事实上,这一条也是对质量属性“可靠性”的一个回应。
现有问题
整个RAID的识别都针对技术层面,而非管理层面。因此我们识别的问题也限制在技术范围。
在我们识别出来的问题中,最致命的一个问题是关于NVUM模块的加载。NVUM是一个Jar包(即本项目最主要实现的内容)。在我们现在的设计中,希望的部署方式是在网管系统中动态加载这个Jar包。之所以选择动态加载,而非静态依赖,原因有二:
-
NVUM由我们项目组维护,网管系统则属于另外一个项目,两边的版本计划完全不一致。网管系统为CS系统,被独立地部署到全球多个外场。若采用静态依赖,需要我们将其纳入到网管系统中,但NVUM的版本更新会更频繁,外场不可能因为NVUM一个模块的调整,而付出频繁更新网管系统的代价。
-
网管系统负责监控外场各个基站的设备运转状况。虽然网管系统的重启(耗时数十分钟)并不会影响设备的功能,但却可能在重启过程中,因为未能及时掌控设备状态,而导致无法及时发现问题去解决。这样的事故是必须避免的。换言之,网管系统的重启代价太高,不能经常重启。
目前的设计是由技术部制作规格包,规格包会包含我们的NVUM Jar包。外场人员得到规格包后,导入规格包,此时,网管系统会动态加载NVUM。具体流程如下:
这就需要解决动态加载的问题。网管系统的开发人员认为无法完成Jar包的动态加载。但就这一点,我已经给出明确答复,从技术上是可行的,并且做了一个Demo来演示,方法是通过URLClassLoader实现加载。当然,我们也可以选择OSGI,但我认为这个方案过于重型,成本太高。
但是,动态加载也存在一个问题,即我们需要将NVUM分为interface和impl两个模块,并保证interface的稳定性。规格包中应该只包括impl模块。
另一个方案是采用脚本。我比较倾向于选用Groovy,因为它能很好地与Java集成。我们只需要在Java中调用Groovy提供的GroovyShell,就能直接读取groovy脚本文件;然后调用
run()
方法即可执行脚本。
识别依赖
除了NVUM与网管系统(OMMB、LMT),NVUM与主控板,主控板与受控板之间的依赖外,牵涉到依赖的还有很多。有的属于输入依赖,例如ACS、SCS;有的则属于输出依赖,例如OSS、BSP、TCM、SCS、DBS、COM、BRS等。此外,还有版本制作工具等系统也会受到NVUM的影响。同时,NVUM还需要访问OMMB文件系统,FTP,读取诸多外部文件。通信则可能采用Telnet、SNMP、SSH等多种协议。
这些依赖的识别便于确定本系统对其他系统可能造成的影响,事先识别有利于我们及时做好沟通,同时还需要就一些架构约定以及接口定义达成一致意见。依赖的识别也有利于我们设计系统的物理架构,并考虑系统的部署方式。
架构设计
在经过项目的需求分析与风险问题的识别后,就进入系统的架构设计阶段。我在Inception计划中安排了将近两天的时间,用以
开展“设计工作坊”活动,通过引入可视化的架构设计手段带领整个团队一起开展架构设计。团队参与的形式有助于促进交流与沟通,形成架构知识的共享
。因为只有团队共享了架构知识,并能就架构的思想和原则达成一致,架构才能在软件开发过程中发挥作用。同时,这种活动也是培养团队成员架构设计能力的一种手段。