利用Docker开启持续交付之路

持续交付即Continuous Delivery,简称CD,随着DevOps的流行正越来越被传统企业所重视。持续交付讲求以短周期、小细粒度,自动化的方式频繁的交付软件,在这个过程中要求开发、测试、用户体验等角色紧密合作,快速收集反馈,从而不断改善软件质量并减少浪费。然而,在我所接触的传统企业中,对于持续交付实践的实施都还非常初级,坦白说,大部分还停留的手工生成发布包,手工替换文件进行部署的阶段,这样做无疑缺乏管理且容易出错。如果究其原因,我想主要是因为构建一个可实际运行且适合企业自身环境的持续发布流程并不简单。然而,Docker作为轻量级的基于容器的解决方案,它对系统侵入性低,容易移植,天生就适合做自动化部署,这些特性非常有助于降低构建持续交付流程的复杂度。本文将通过一个实际案例分享我们在一个真实项目中就如何使用Docker构建持续发布流程的经验总结,这些实践也许不是最先进的,但确是非常实际和符合当时环境的。

项目背景

我们的客户来自物流行业,由于近几年业务的飞速发展,其老的门户网站对于日常访问和订单查询还勉强可以支撑,但每当遇到像双十一这样访问量成倍增长的情况就很难招架了。因此,客户希望我们帮助他们开发一个全新的门户网站。

新网站采用了动静分离的策略,使用Java语言,基于REST架构,并结合CMS系统。简单来说,可以把它看成是时下非常典型的一个基于Java的Web应用,它具体包含如下几个部分:

  • 基于Jersey的动态服务(处理客户端的动态请求)
  • 二次开发的OpenCMS系统,用于静态导出站点
  • 基于js的前端应用并可以打包成为一个OpenCMS支持的站点
  • 后台任务处理服务(用于处理实时性要求不高的任务,如:邮件发送等)

以下是系统的逻辑软件架构图:

01

面临的挑战以及为什么选择Docker

在设计持续交付流程的过程中,客户有一个非常合理的需求:是否可以在测试环境中尽量模拟真实软件架构(例如:模拟静态服务器的水平扩展),以便尽早发现潜在问题?基于这个需求,可以尝试将多台机器划分不同的职责并将相应服务按照职责进行部署。然而,我们遇到的第一个挑战是:硬件资源严重不足尽管客户非常积极的配合,但无奈于企业内部层层的审批制度。经过两个星期的努力,我们很艰难的申请到了两台四核CPU加8G内存的物理机(如果申请虚拟机可能还要等一段时间),同时还获得了一个Oracle数据库实例。因此,最终我们的任务就变为把所有服务外加持续集成服务器(Jenkins)全部部署在这两台机器上,并且,还要模拟出这些服务真的像是分别运行在不同职责的机器上并进行交互。如果采用传统的部署方式,要在两台机器上完成这么多服务的部署是非常困难的,需要小心的调整和修改各个服务以及中间件的配置,而且还面临着一旦出错就有可能耗费大量时间排错甚至需要重装系统的风险。第二个挑战是:企业内部对UAT(与产品环境配置一致,只是数据不同)和产品环境管控严格,我们无法访问,也就无法自动化。这就意味着,整个持续发布流程不仅要支持自动化部署,同时也要允许下载独立发布包进行手工部署。

最终,我们选择了Docker解决上述两个挑战,主要原因如下:

  • Docker是容器,容器和容器之间相互隔离互不影响,利用这个特性就可以非常容易在一台机器上模拟出多台机器的效果
  • Docker对操作系统的侵入性很低,因其使用LXC虚拟化技术(Linux内核从2.6.24开始支持),所以在大部分Linux发行版下不需要安装额外的软件就可运行。那么,安装一台机器也就变为安装Linux操作系统并安装Docker,接着它就可以服役了
  • Docker容器可重复运,且Docker本身提供了多种途径分享容器,例如:通过export/import或者save/load命令以文件的形式分享,也可以通过将容器提交至私有Registry进行分享,另外,别忘了还有Docker Hub

下图是我们利用Docker设计的持续发布流程:

02

图中,我们专门设计了一个环节用于生成唯一发布包,它打包所有War/Jar、数据库迁移脚本以及配置信息。因此,无论是手工部署还是利用Docker容器自动化部署,我们都使用相同的发布包,这样做也满足持续交付的单一制品原则(Single Source Of Truth,Single Artifact)。

Docker与持续集成

持续集成(以下简称CI)可以说是当前软件开发的标准配置,重复使用率极高。而将CI与Docker结合后,会为CI的灵活性带来显著的提升。由于我们项目中使用Jenkins,下面会以Jenkins与Dcoker结合为例进行说明。

1.创建Jenkins容器

相比于直接把Jenkins安装到主机上,我们选择把它做为Docker容器单独使用,这样就省去了每次安装Jenkins本身及其依赖的过程,真正做到了拿来就可以使用。

Jenkins容器使创建一个全新的CI变的非常简单,只需一行命令就可完成:

docker run -d -p 9090:8080 ——name jenkins jenkins:1.576

该命令启动Jenkins容器并将容器内部8080端口重定向到主机9090端口,此时访问:主机IP:9090,就可以得到一个正在运行的Jenkins服务了。

为了降低升级和维护的成本,可将构建Jenkins容器的所有操作写入Dockerfile并用版本工具管理,如若需要升级Jenkins,只要重新build一次Dockerfile:

FROM ubuntu
ADD sources.list /etc/apt/sources.list
RUN apt-get update && apt-get install -y -q wget
RUN wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | apt-key add -
ADD jenkins.list /etc/apt/sources.list.d/
RUN apt-get update
RUN apt-get install -y -q jenkins
ENV JENKINS_HOME /var/lib/jenkins/
EXPOSE 8080
CMD ["java", "-jar", "/usr/share/jenkins/jenkins.war"]

每次build时标注一个新的tag:

docker build -t jenkins:1.578 —rm .

另外,建议使用Docker volume功能将外部目录挂载到JENKINS_HOME目录(Jenkins会将安装的插件等文件存放在这个目录),这样保证了升级Jenkins容器后已安装的插件都还存在。例如:将主机/usr/local/jenkins/home目录挂载到容器内部/var/lib/jenkins:

docker run -d -p 9090:8080 -v /usr/local/jenkins/home:/var/lib/jenkins ——name jenkins jenkins:1.578

2. 使用Docker容器作为Jenkins容器的Slave

在使用Jenkins容器时,我们有一个原则:不要在容器内部存放任何和项目相关的数据。因为运行中的容器不一定是稳定的,而Docker本身也可能有Bug,如果把项目数据存放在容器中,一旦出了问题,就有丢掉所有数据的风险。因此,我们建议Jenkins容器仅负责提供Jenkins服务而不负责构建,而是把构建工作代理给其他Docker容器做。

例如,为了构建Java项目,需要创建一个包含JDK及其构建工具的容器。依然使用Dockerfile构建该容器,以下是示例代码(可根据项目实际需要安装其他工具,比如:Gradle等):

FROM ubuntu
RUN apt-get update && apt-get install -y -q openssh-server openjdk-7-jdk
RUN mkdir -p /var/run/sshd
RUN echo 'root:change' |chpasswd
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]

在这里安装openssh-server的原因是Jenkins需要使用ssh的方式访问和操作Slave,因此,ssh应作为每一个Slave必须安装的服务。运行该容器:

docker run -d -P —name java java:1.7

其中,-P是让Docker为容器内部的22端口自动分配重定向到主机的端口,这时如果执行命令:

docker ps
804b1d9e4202       java:1.7           /usr/sbin/sshd -D     6 minutes ago       Up 6 minutes       0.0.0.0:49153->22/tcp   java

端口22被重定向到了49153端口。这样,Jenkins就可以通过ssh直接操作该容器了(在Jenkins的Manage Nodes中配置该Slave)。

有了包含构建Java项目的Slave容器后,我们依然要遵循容器中不能存放项目相关数据的原则。此时,又需要借助volume:

docker run -d -v /usr/local/jenkins/workspace:/usr/local/jenkins -P —name java java:1.7

这样,我们在Jenkins Slave中配置的Job、Workspace以及下载的源码都会被放置到主机目录/usr/local/jenkins/workspace下,最终达成了不在容器中放置任何项目数据的目标。

通过上面的实践,我们成功的将一个Docker容器配置成了Jenkins的Slave。相比直接将Jenkins安装到主机上的方式,Jenkins容器的解决方案带来了明显的好处:

  • 重用更加简单,只需一行命令就可获得CI的服务;
  • 升级和维护也变的容易,只需要重新构建Jenkins容器即可;
  • 灵活配置Slave的能力,并可根据企业内部需要预先定制具有不同能力的Slave,比如:可以创建出具有构建Ruby On Rails能力的Slave,可以创建出具有构建NodeJS能力的Slave。当Jenkisn需要具备某种能力的Slave时,只需要docker run将该容器启动,并配置为Slave,Jenkins就立刻拥有了构建该应用的能力。

如果一个组织内部项目繁多且技术栈复杂,那么采用Jenkins结合Docker的方案会简化很多配置工作,同时也带来了相率的提升。

Docker与自动化部署

说到自动化部署,通常不仅仅代表以自动化的方式把某个应用放置在它应该在的位置,这只是基本功能,除此之外它还有更为重要的意义:

  • 以快速且低成本的部署方式验证应用是否在目标环境中可运行(通常有TEST/UAT/PROD等环境);
  • 以不同的自动化部署策略满足业务需求(例如:蓝绿部署);
  • 降低了运维的成本并促使开发和运维人员以端到端的方式思考软件开发(DevOps)。

在我们的案例中,由于上述挑战二的存在,导致无法将UAT乃至产品环境的部署全部自动化。回想客户希望验证软件架构的需求,我们的策略是:尽量使测试环境靠近产品环境。

  1. 标准化Docker镜像

很多企业内部都存在一套叫做标准化的规范,在这套规范中定义了开发中所使用的语言、工具的版本信息等等,这样做可以统一开发环境并降低运维团队负担。在我们的项目上,依据客户提供的标准化规范,我们创建了一系列容器并把它们按照不同的职能进行了分组,如下图:

03

图中,我们把Docker镜像分为三层:基础镜像层、服务镜像层以及应用镜像层,下层镜像的构建依赖上层镜像,越靠上层的镜像越稳定越不容易变。

基础镜像层

  • 负责配置最基本的、所有镜像都需要的软件及服务,例如上文提到的openssh-server

服务镜像层

  • 负责构建符合企业标准化规范的镜像,这一层很像SaaS

应用镜像层

  • 和应用程序直接相关,CI的产出物

分层后, 由于上层镜像已经提供了应用所需要的全部软件和服务,因此可以显著加快应用层镜像构建的速度。曾经有人担心如果在CI中构建镜像会不会太慢?经过这样的分层就可以解决这个问题。

在Dockerfile中使用FROM命令可以帮助构建分层镜像。例如:依据标准化规范,客户的产品环境运行RHEL6.3,因此在测试环境中,我们选择了centos6.3来作为所有镜像的基础操作系统。这里给出从构建base镜像到Java镜像的方法。首先是定义base镜像的Dockerfile:

FROM centos
# 可以在这里定义使用企业内部自己的源
RUN yum install -y -q unzip openssh-server
RUN ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key
RUN echo 'root:changeme' | chpasswd
RUN sed -i "s/#UsePrivilegeSeparation.*/UsePrivilegeSeparation no/g" /etc/ssh/sshd_config \
&& sed -i "s/UsePAM.*/UsePAM no/g" /etc/ssh/sshd_config
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]

接着,构建服务层基础镜像Java,依据客户的标准化规范,Java的版本为:jdk-6u38-linux-x64:

FROM base
ADD jdk-6u38-linux-x64-rpm.bin /var/local/
RUN chmod +x /var/local/jdk-6u38-linux-x64-rpm.bin
RUN yes | /var/local/jdk-6u38-linux-x64-rpm.bin &>/dev/null
ENV JAVA_HOME /usr/java/jdk1.6.0_38
RUN rm -rf var/local/*.bin
CMD ["/usr/sbin/sshd", "-D"]

如果再需要构建JBoss镜像,就只需要将JBoss安装到Java镜像即可:

FROM java
ADD jboss-4.3-201307.zip /app/
RUN unzip /app/jboss-4.3-201307.zip -d /app/ &>/dev/null && rm -rf /app/jboss-4.3-201307.zip
ENV JBOSS_HOME /app/jboss/jboss-as
EXPOSE 8080
CMD ["/app/jboss/jboss-as/bin/run.sh", "-b", "0.0.0.0"]

这样,所有使用JBoss的应用程序都保证了使用与标准化规范定义一致的Java版本以及JBoss版本,从而使测试环境靠近了产品环境。

  1. 更好的组织自动化发布脚本

为了更好的组织自动化发布脚本,版本化控制是必须的。我们在项目中单独创建了一个目录:deploy,在这个目录下存放所有与发布相关的文件,包括:用于自动化发布的脚本(shell),用于构建镜像的Dockerfile,与环境相关的配置文件等等,其目录结构是:

├── README.md
├── artifacts   # war/jar,数据库迁移脚本等
├── bin         # shell脚本,用于自动化构建镜像和部署
├── images       # 所有镜像的Dockerfile
├── regions     # 环境相关的配置信息,我们只包含本地环境及测试环境
└── roles       # 角色化部署脚本,会本bin中脚本调用

这样,当需要向某一台机器上安装java和jboss镜像时,只需要这样一条命令:

bin/install.sh images -p 10.1.2.15 java jboss

而在部署的过程中,我们采用了角色化部署的方式,在roles目录下,它是这样的:

├── nginx
│   └── deploy.sh
├── opencms
│   └── deploy.sh
├── service-backend
│   └── deploy.sh
├── service-web
│   └── deploy.sh
└── utils.sh

这里我们定义了四种角色:nginx,opencms,service-backend以及service-web。每个角色下都有自己的发布脚本。例如:当需要发布service-web时,可以执行命令:

bin/deploy.sh -e test -p 10.1.2.15 service-web

该脚本会加载由-e指定的test环境的配置信息,并将service-web部署至IP地址为10.1.2.15的机器上,而最终,bin/deploy.sh会调用每个角色下的deploy.sh脚本。

角色化后,使部署变的更为清晰明了,而每个角色单独的deploy脚本更有利于划分责任避免和其他角色的干扰。

  1. 构建本地虚拟化环境

通常在聊到自动化部署脚本时,大家都乐于说这些脚本如何简化工作增加效率,但是,其编写过程通常都是痛苦和耗时,需要把脚本放在相应的环境中反复执行来验证是否工作正常。这就是我为什么建议最好首先构建一个本地虚拟化环境,有了它,就可以在自己的机器上反复测试而不受网络和环境的影响。

Vagrant(http://www.vagrantup.com/)是很好的本地虚拟化工具,和Docker结合可以很容易的在本地搭建起与测试环境几乎相同的环境。以我们的项目为例,可以使用Vagrant模拟两台机器,以下是Vagrantfile示例:

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.define "server1", primary: true do |server1|
server1.vm.box = "raring-docker"
server1.vm.network :private_network, ip: "10.1.2.15"
end
config.vm.define "server2" do |server2|
server2.vm.box = "raring-docker"
server2.vm.network :private_network, ip: "10.1.2.16"
end
end

由于部署脚本通常采用SSH当方式连接,所以,完全可以把这两台虚拟机看做是网络中两台机器,调用部署脚本验证是否正确。限于篇幅,这里就不多说了。

4 构建企业内部的Docker Registry

上文提到了诸多分层镜像,如何管理这些镜像?如何更好的分享?答案就是使用Docker Registry。Docker Registry是一个镜像仓库,它允许你向Registry中提交(push)镜像同时又可以从中下载(pull)。

构建本地的Registry非常简单,执行下面的命令:

docker run -p 5000:5000 registry

更多关于如何使用Registry可以参考地址:https://github.com/docker/docker-registry

当搭建好Registry后,就可以向它push你的镜像了,例如:需要将base镜像提交至Registry:

docker push your_registry_ip:5000/base:centos

而提交Java和JBoss也相似:

docker push your_registry_ip:5000/java:1.6
docker push your_registry_ip:5000/jboss:4.3

使用下面的方式下载镜像:

docker pull your_registry_ip:5000/jboss:4.3

总结

本文总结我们在实际案例中使用Docker一些实践,它给我们的印象就是非常灵活,几乎是一个多面手,给整个流程带来了极大的灵活性和扩展性,并且也展现了极好的性能,符合它天生就为部署而生的特质。

Share

实战:持续交付中的业务分析

在需要频繁交付、不断收集用户反馈、拥抱变化、追求业务敏捷的项目中,软件的开发和交付是迭代式进行的。在这样的项目团队中,BA(业务分析师)通常需要在一个开发迭代开始之前完成该迭代开发任务的分析。但在特殊情况下,从收集客户需求到将功能细节传达给开发团队的周期会缩短到一至两天。BA可以用于思考和分析的时间远远少于可以预先做出所有设计的瀑布式项目。

那么在这样的敏捷项目中,BA如何能够适应这种交付模式,完成高质量的业务分析,协同团队为客户交付高价值的软件呢?

项目背景

ABC公司是一家知名的国际性会计师事务所,业务规模庞大,分支机构遍布全球170多个国家。

ThoughtWorks受邀对其“全球派遣服务(International Assignment Service)”业务部门提供IT解决方案,以及软件系统的开发。该系统包括收集其客户的全球派遣雇员的报税数据,以及管理ABC公司税务咨询师对这些数据的进行审核、汇算和出具报告的业务流程;逐步替换其目前已远远不能满足业务和性能需求的遗留系统。

该系统主要有两类用户,一类是ABC公司客户方被派往不同国家工作的雇员(以下简称Mary),这些雇员使用该系统填入报税需要的数据。另一类用户是ABC公司的税务咨询师(以下简称Kim),负责审核、处理Mary提交的数据。

BA在该项目中面临的主要挑战

  • 该项目为分布式开发,ABC公司的决策方在美国,而ThoughtWorks的开发团队在中国,沟通反馈周期有时较长。
  • 由于ABC公司对用户体验的重视,需要频繁交付软件,以便收集用户反馈并及时调整解决方案和后续开发计划。这大大缩短了从收集需求、开始分析到进入开发的周期,增加了分析中出现缺陷的风险。
  • 当开发过程中发现问题时,无法马上与客户取得沟通,开发进度可能会受到影响。

识别业务价值

业务分析的重要性在于首先做正确的事情。理解客户的业务,关注需求背后的价值可以帮助项目团队在软件的设计方面做出正确的选择。

而我们面临的困难是,客户提出的需求,往往都是直接的软件功能,而不是需要解决的业务问题。如果BA只专注于针对客户需要的功能进行系统分析,就丧失了帮助客户优化解决方案以及改进业务流程的机会。

如何寻找业务价值?

以敏捷开发方法中的用户故事为例,找出客户要解决的业务问题的一个简单办法是,用以下方式概括每个用户故事的内容:

As…(角色),I want to…(完成什么样的功能),So that…(解决什么问题,带来什么价值)

“So that…”说明了该故事的业务价值,即要解决的业务问题。准确的寻找业务价值将有利于我们设计出最适合的“I want to”,这很可能优于客户直接提出的功能要求。

需要注意的是,不要把解决方案或功能当成该用户故事的价值。以ABC公司业务系统中的一个用户故事为例,BA对该需求业务价值的了解程度将直接影响到解决方案的优劣。

作为(As…) 我想要(I want to…) 以便(So that…) 是否阐明了价值?
Mary 即时浏览我的行程统计数据 了解我在各个国家或地区停留的时间以及从事的活动
Mary 即时浏览我的行程统计数据 我可以迅速地检查我所输入的在各国家或地区停留时间及从事活动的数据是否正确(以保证我可以依照法律要求提交准确的报税数据)

在该用户故事的两种不同表述中,由于第一种表述只说明了需要的功能,没有说明业务价值,在功能设计时,我们可能会将“行程统计数据”的内容设计的过于详细而造成浪费,使用户不明白此功能的意图。而第二种表述的业务目标就非常明确,可以帮助我们更加容易地设计出适合的解决方案。

此外,BA在了解客户的业务问题时,最好请客户提供一些真实案例/场景来证实其观点并加深自己的理解。

避免分析错误

在实际工作中,我们发现有以下两个方面的分析工作容易被BA忽略,而做出错误的决定。

    1. 客户要求实现某些现有业务流程或遗留系统的功能

例如,客户需求的功能,是当前遗留系统中已经使用多年、且未收到过任何抱怨的功能。所以客户和BA往往认为这个功能是合理的,忽略了深入的分析和思考。而这种思考不全面而做出的决定可能会与可以预见的新功能产生冲突。

在ABC公司的遗留系统中,用来收集报税数据的问卷内容是通过excel表来维护的,而Mary在前台也是通过下载excel问卷,填写完毕后再上传。

在新开发的系统中,问卷被改为在线方式,并辅助以其他必要功能提升Mary的用户体验和满意度。但由于客户方的员工都是财务背景出身,非常喜欢使用excel表,而之前用excel表维护问卷内容也被证明是非常有效的,所以客户坚持在新系统中延用这种方式。经过仔细的分析,我们发现在针对提高Mary用户体验的新功能上线后,使用excel表维护问卷内容将大大增加维护的工作量及错误率,而这与项目的相关目标背道而驰。ThoughtWorks在列举了问题的细节后,说服客户采用了新的解决方案。

    1. 客户要求利用新的IT系统改变当前的业务流程

客户发现目前的业务流程有不合理的地方,希望在新的IT系统里直接改变这些流程。如果不经过仔细的分析,这种做法可能会很危险,业务流程的盲目改变可能会对一部分用户造成麻烦,为客户实施该软件形成强大阻力。那么了解清楚目前这些流程存在的价值和原因事关重要,从而可以帮助我们为客户提供科学的、逐步优化其流程的IT解决方案。

在ABC公司的业务流程中,Kim和Mary之间的一些交流是通过邮件来完成的。这里存在两个业务风险:1)Kim和Mary交流的重要信息被散落在各自的邮件里,系统无法记录,在遇到法律问题时,难以划分责任;2)Kim和Mary可能会使用邮件发送一些保密性较强的内容,如果发错,后果不堪设想。

在开发新系统时,客户要求我们增加了一个消息功能,使Kim和Mary之间的交流可以方便地在系统内部完成。该功能上线后,很好地化解了这两个业务风险,同时收到了Mary这类用户的良好反馈。然而这对该会计师事务所在某些国家分支机构里的Kim这类用户的工作却带来了不小的影响。由于之前使用邮件系统,Kim可以将Mary的邮件转发给相关的同事,并利用邮件丰富的功能进行结果的跟踪。而新上线的消息功能达不到邮件的所有要求,所以增加了他们的工作难度。此外,由于Mary对这个功能的青睐,发送消息的数量远远超过了在使用遗留系统时发送邮件的数量,超过了客户想提高Mary的满意度而在短期内所能承受的代价。

在遇到以上问题时,我们与客户一同分析,提出了折中的解决方案,花费了较少的代价将消息系统和客户的邮件进行集成,同时帮助客户制定了对此项业务流程改进和配套IT解决方案的蓝图。

理清需求优先级

在频繁上线的项目中,其中一个重要的实践是确定需求的优先级,使得重要的功能能够先被开发出来投入使用以便及时收集用户反馈。一般的做法是要求客户排好需求优先级,然后与项目相关成员一同制订迭代开发和上线计划。但由于客户决策方所处角色以及思维角度的局限性,对优先级的评定可能存在盲目。建议BA参照以下价值维度帮助客户对优先级进行评定。

从客户价值维度分析需求优先级

需求价值维度分析图

价值维度 说明
愿景目标 该功能点是否契合项目的愿景和业务目标?与项目目标的契合程度越高者优先级越高
时间限制 客户的业务是否有一定的时间表?如果该功能点必须在某时间点前投入使用,则该需求必须被排入相应时间的发布计划中
市场卖点 该功能点是否是吸引特定目标用户的卖点?如果客户的资金存在问题或者需要市场的快速认可,则可以考虑将该需求列为高优先级
有无替代方案 该功能点有无方便的替代方案?如果有简单易行的替代方案,则该需求的优先级较低
客户内部政治因素 该功能点是否存在客户内部的政治因素?例如某功能只对小部分用户提供价值,但会决定客户内部某个重要组织对这个项目的投资和评价,则可以考虑将该需求列为高优先级
投资收益 该功能点的开发成本和客户所能获得的收益是否匹配?例如客户某工作流程浪费了一个小组人员大量时间,但对其他部门或工作环节无影响。如果开发相应的软件功能造成的投入大于客户在一定时期内可以节省的资金,则该需求的优先级较低
技术依赖性 其他需求是否依赖于该功能点?如果依赖于这个功能点的需求优先级高,那么该功能的优先级应更高

技术风险对优先级的影响

除了来自客户方面的决定因素,我们还应考虑技术实现方面的影响。如果一些技术风险较高的功能可以先进入开发阶段,则问题会尽早地被暴露。开发人员在项目早期解决这些问题会有利于开发成本的节约。所以除以上客户价值维度外,应再参考以下矩阵来权衡需求的优先级。

需求优先级矩阵

客户价值维度和需求优先级矩阵并不是优先级高低的计算器,而是与客户以及团队沟通交流的工具。不同项目的影响维度也会有所不同。由于各项因素的复杂性,客户价值维度和技术风险因素需综合考虑,不可以权重来计算。BA可以与客户对以上因素的内容达成一致,使得客户在评定需求优先级时可以快速、准确地做出判断。同时,通过对价值维度的分析,我们将有机会清晰地了解到功能优先级高或低的原因,以便我们能够准确地编制项目开发和上线计划,并合理地划分用户故事范围。

借助价值维度分析,管理客户期望值

有些客户的决策人可能会依据自己的喜好划分优先级,这对于项目能够按目标成功交付造成一定的风险。此外,客户在功能的设计和验收阶段也容易对单个功能追求完美,造成额外工作量,增加项目范围。而这部分额外工作可能并不合理或者价值较低。长期如此,团队在开发过程中将逐渐偏离项目目标。如果能借助优先级维度对这些额外需求进行分析,则可以提供更有说服力的依据,帮助客户做出正确决定,达成BA和项目经理对客户期望值的有效管理,从而降低交付风险。

发挥团队其他成员在业务分析中的作用

在频繁交付的项目中,如果BA独自承担业务分析工作,难免会出现疏漏。ThoughtWorks曾与ABC公司的IT部门合作完成其业务系统的一些集成工作。在合作过程中发现,ABC公司IT部门的开发人员在业务分析中参与度很低,由此造成了如下问题:

  1. BA需要写大量需求文档,故从需求分析到软件交付的周期较长
  2. 设计缺陷的发现滞后
  3. 在需要频繁交付的情况下,解决方案质量较差,方案优化能力较弱

而ThoughtWorks的开发人员由于在业务分析中的参与度较高,则有效地避免了以上问题。

开发人员如何参与分析

开发人员是软件功能的实现人员,对方案的实现工作量有较准确的估计。在明确项目目标或业务问题后,BA如果能够和开发人员一同分析解决方案,将更有效地为客户找到兼顾成本和效果的方案。

在收集到客户需求后,BA可根据业务价值对需求进行分析,判断客户提出的功能或解决方案是否能很好地满足该业务价值或要解决的业务问题;或者按照自己的理解设计出满足该业务价值的功能或解决方案。

完成上述工作之后,BA应与开发人员就需求和业务价值进行充分沟通,验证功能实现的可行性,同时积极探寻更优方法。如果开发人员提出符合业务价值的不同方案,BA则可以要求开发人员提供一些关于开发工作量、方案优劣、技术风险方面的比较数据,从而帮助自己有效地与客户沟通并挑选最佳方案。甚至可以根据分析结果帮助客户调整该需求的优先级。对于技术难度和风险较高的功能点,建议邀请资深开发人员参与讨论。

与开发人员沟通中遇到的挑战与解决方法

由于上述方法需要与开发人员大量沟通,有些BA在应用以上实践时也遇到了以下挑战。

    1. 开发人员缺少参与业务分析的热情

在ThoughtWorks,大多数开发人员都喜欢积极思考、主动为业务分析提供帮助,大大减少了需求分析上的漏洞。然而在ABC公司的IT部门中,开发人员很少主动为业务分析出谋划策,尤其是团队中资历较浅的成员,甚至不愿意参与解决方案的讨论。团队成员的优势没有得到充分发挥,开发人员只管按需求埋头苦干,结果功能和解决方案的问题往往在测试或者验收阶段才暴露出来,不可避免地造成了浪费。

站在开发人员成长的角度,从ThoughtWorks实践来看,积极地理解业务、思考解决方案能够更快地提高技术能力。故此BA可以找出一些实际案例,协同项目经理与团队各成员进行沟通,鼓励大家积极参与业务分析,逐步形成开发人员与BA协作的良好氛围。

    1. 开发人员容易就客户需求或解决方案产生争论

开发人员在积极参于分析的过程中,有时会对软件功能的价值吹毛求疵,在细节上与BA产生较多争论,使BA在应付开发人员的问题以及与客户求证答案之间疲于奔命。

解决此类问题,可采取以下方法:

  • BA在收集需求时,尽可能充分地了解客户要解决的业务问题,以便能够快速回答开发人员的问题
  • 面对开发人员对解决方案的质疑时,应保持良好的心态,清楚地了解开发人员顾虑的问题和原因
  • 如果自己掌握的信息确实不能证明现行方案的合理性时,协同开发人员,找到更优方案并与现行方案进行优缺点比较
  • 将新旧方案与客户沟通,则可快速帮助客户做出判断

不要忽略测试人员在业务分析中的贡献

由于测试人员所处角度和对细节的关注,往往可以发现一些功能细节的设计漏洞。所以在用户故事进入开发前,BA与质量保证人员对相关业务价值进行充分沟通,会在功能进入开发之前为BA创造更正设计缺陷的机会。

做为质量保证人员,如果充分了解功能背后的业务价值,相对于只了解功能需求,将可以写出更加完善的测试用例,提高测试覆盖率。这会为交付高质量的软件把好最后一道关。

结语

业务分析是困难的,特别是我们面对未知领域的时候。如果只是简单地按照客户的具体需求进行软件开发,那么我们交付给客户的价值将非常有限。然而识别业务价值、帮助客户分析需求优先级、保障团队协作,将有效提升团队对软件的设计能力,解决客户真正的业务问题,交付更大价值。

作为一名业务分析人员,当您在尝试以上实践时,可能会发现自己对客户业务的理解变得更加深刻。在与客户的沟通中,也能够更加容易地提出有价值的问题以及建议,从而提升客户对项目团队的信任,为成功交付项目打下良好基础。

*注:“客户价值维度”的概念由ThoughtWorks咨询师李光磊提出。在此对李光磊表示感谢。


Share