行为驱动开发(BDD)如何与领域驱动设计(DDD)结合?

行为驱动开发(Behaviour Driven-Development)与测试驱动开发(TDD)两者都强调敏捷迭代,BDD使用“用户故事”来描述需求,然后开发人员将这些故事带入具体应用,通过不断迭代添加入真正的业务本质,也就是说,在BDD中,领域模型是通过开发迭代过程不断取自于于用户故事,而一般人理解的DDD是指一个成熟的领域模型,而不是一个在不断发展中的领域模型。来自Stackoverflow的提问
How does Behaviour Driven Development (BDD) work with Domain Driven Design (DDD) - Stack Overflow试图解开其中的问题。

BDD的定义:Given在某种场景下 When发生了事件 Then导致了什么结果(简称Given, When, Then)。

(banq注:我曾经把四色原型总结为:某个角色对某个事情做了某个动作,导致了什么结果。这两种描述几乎相同,四色原型侧重动作活动,BDD侧重事件,事件和动作其实非常类似。)

在StackOverflow这篇回答中,首先肯定BDD并不是一次性产生成熟的领域模型,BDD是从三个角度来看待需求: 知道的the known, 能够知道的the knowable 和不知道的 the unknowable.

对于“知道的”,因为比较简单,大家都知道,就无需各种场景描述,比如谈到日志,大家都能明白日志是什么,怎么做日志。

对于“能够知道的”,BDD擅长于此,通过用户故事来表达,并且以Given, When, Then方式来表达,这实际是使用scenarios场景来表达领域模型(banq注:比较类似DCI分析)

对于“不知道的”,我们通过BDD这样敏捷迭代不断挖掘深入,能够不断发现深层次的领域模型,隐式模型显式化。(见复杂模型的循环化)

当我们结合BDD和DDD时,我们使用BDD来实现领域模型中和场景有关的那些部分。当然在一个大型项目中,如果我们已经有一些成熟大的领域模型,我们也可以使用BDD的Feature Injection,特征是提供了一种让用户能够使用程序系统提供能力的途径,一般这是由UI界面工程师来完成,所谓能力实际是一种服务,服务必须通过界面通过类似MVC方式来实现,这样用户操作界面才能实现我们提供给它的服务能力,当然这个界面可以是网页也可能是移动页面。

(banq注:我经常在一些网站网页上使用的按钮功能,转到其手机页面上就找不到了,比如新浪微博有那么多种客户端,但是更新都不是同步的,造成客户端使用体验完全不同,这实际没有重视Feature一种体现)

BDD是从TDD发展过来的,也属于DDD中一种描述业务的无处不在的统一语言,它的描述格式是:

As a [Role]
I want [Feature]
so that [benefit]

用中文的意思来理解,我认为是:作为某个角色,我需要某些功能或权利,这样能得到相应利益。 正如职责驱动开发中奖职责责任作为分析突破口一样,这里好像是从这个方面作为切入点分析的。

这样一种描述方式能够帮助我们从用户故事中不断寻找到那种传递业务价值核心的信息。因为大部分我们的客户总是这样问:嗯,我希望有这样的功能....你看这样做可以吗?( . . . I want [some feature] so that [I just do, ok?].)

从以上用户故事中,我们能发现如果软件能够正确实施用户这些行为,我们可以将它们作为系统的测试方式和验收标准。

那么如何能保证软件正确实施用户这些需求行为呢?对于简单容易明白的,我们能一下子能知道掌握,但是复杂一点的怎么办?我们可以使用一种模板来套用截取。

这个模板是:
Given some initial context (the givens),
When an event occurs,
then ensure some outcomes.

给出某个场景,但事件发生时,将有什么结果发生。

我们以取款机ATM来举例这个模板的使用,假设用户故事是这样:
As a customer,
I want to withdraw cash from an ATM,
so that I don’t have to wait in line at the bank.
作为一个客户,我想从ATM机中提取现金,这样(好处结果是),我不用在银行排队等候。

那么我们是如何知道我们已经成功传递了这个用户故事呢?对于这个故事的理解我们需要考虑几种场景情况:该客户账户可能是一个信用卡账户,有可能存在余款不够需要透支,而透支有存在透支额的问题,取款多少或多于或少于透支额等等多个可能情况。

我们使用given-when-then模板变成如下:
+Scenario场景 1: Account is in credit+ 账户是信用卡
Given the account is in credit 给出账户是信用卡
And the card is valid 并且卡是有效的
And the dispenser contains cash 并且ATM机有现金
When the customer requests cash 当客户请求取出现金时
Then ensure the account is debited 那么确保账户余额被扣除
And ensure cash is dispensed 并且确保现金被吐出
And ensure the card is returned 并且确保信用卡能退还。

+Scenario场景 2: Account is overdrawn past the overdraft limit+
账户已经透支。
Given the account is overdrawn 给出账户已经透支场景
And the card is valid 并且这个卡是有效的
When the customer requests cash 但客户请求取出现金时
Then ensure a rejection message is displayed 那么确保拒绝信息显示
And ensure cash is not dispensed 并且确保现金不会吐出
And ensure the card is returned 并且确保信用卡能退还。

对于上面两种不同场景,我们发现相同的点:事件 给出given 结果。

也就是说,given-when-then模板能够充分表达这几种不同场景发生的情况。

场景的组成部分 the givens(给出), event(事件), and outcomes(结果) – 是一种细粒度能够直接在代码反映用户故事的方式。这样就可以直接写出其相应的Java类:

表达Givens(给出):


public class AccountIsInCredit implements Given {
public void setup(World world) {
...
}
}
public class CardIsValid implements Given {
public void setup(World world) {
...
}
}

event事件对应的代码:


public class CustomerRequestsCash implements Event {
public void occurIn(World world) {
...
}
}

对于结果,通过类似Junit的测试框架JBehave将这些组合在一起执行,将输出结果“world,”,它是保存在你对象中某个地方,然后传递到每个给出场景中,JBehave然后通过事件激活"occurIn"方法,也就是真正执行场景的行为,最后,将控制传递给我们故事中已经定义的任何成果。

Feature Injection功能特征注入是BDD另外一个主要内容。

BDD将软件分析设计开发分为如下几个阶段:
Vision视角
Goal目标
Capability能力
Featrue功能特征
Story故事
Scenario场景
Code

作为投资人,其战略眼光是赚钱 节约钱 保护钱;经营者的目标是能够活下去,系统分析师必须有是让用户通过系统能够完成业务并获利能力,而界面设计师的特点是用户界面组件能够代表其相应的功能,开发者则是不断迭代反馈的故事,对于场景来说,所有人员都要以一个具体案例让系统按照用户希望那样运行起来,到了编码阶段,由程序员将想法实现。


[该贴被banq于2012-08-03 15:58修改过]


下面谈谈复杂领域的Cynefin模型,如下图:

将业务分为四个领域:Simple简单 Complicated极其复杂 Complex比较复杂和Chaotic混沌。

如果业务领域只存在简单和极其复杂,我们可以设定目标下决心克服它们,尽管任务非常艰巨,但是这些复杂Complicated还是可以预料的,因为我们知道它艰巨。在这种情况下瀑布式开发也许用得上。

但是事实情况除了上面两种,还有Complex和混沌,所谓Complex是指那种无法预期的变化,比如用户突然修改需求,这是你无法控制,一旦修改撤销等等带来复杂和混乱。

混沌是类似你的房子着火等等紧急事故情况。混乱的是,在你发布的软件之日你的系统出现错误发生当机,这时你必须放下一切,立即修复。

我个人理解,Cynefin模型其实将世界划分为已知和未知两个边界,无论目标有多远,只要是已知其方向,也就是能够确定了方向,迟早会克服,只要坚持就可以。而这个世界更多是存在很多未知部分,与其说人人在上帝面前平等,不如说人人在未来面前平等,更确切地说:人人在未知因素面前平等,不管你是圣贤 还是白痴。

[该贴被banq于2012-08-03 16:21修改过]


如果按照DDD方式开发,领域层的类将有比贫血模型更多的方法,这些方法可能需要调用数据库。
1、如果数据库事务在这些方法内处理,显然无法进行AOP,存在重复性代码不说,还难以保证事务的一致性(可能存在一个业务调用这类多个方法);如果在业务层处理事务,如何保证这些方法不被误调用?如果误调用出错还好说,但这些方法实际不会出错,但会造成数据库连接未关闭。
2、如何区分这类与数据库交互的方法和不与数据库交互的方法?因为不与数据库交互的方法相对是比较安全的。
3、大多数方法实际上只是对存储方法的简单调用,估计大于80%;大多数程序员很难分清哪些代码应该在业务层,哪些在领域层,估计也大于80%;那么,这种架构真正有价值的地方不超过10%,为了这10%,有必要去做100%的工作么?

1、聚合根拥有更新自身内部的方法本身就是一致性的表现,怎么难以保证事务一致性了?误调用是指啥?
2、···这要如何说,领域中不关数据库的事吧。
3、那是自己认为的吧···
“大多数方法实际上只是对存储方法的简单调用,估计大于80%”这句话是面向数据库思维的表现,而我认为“大多数方法实际上是业务逻辑的表达”。数据库用来持久数据的,不是用来逻辑运算的。
“大多数程序员很难分清哪些代码应该在业务层,哪些在领域层,估计也大于80%”若果你的环境是这样,请把那20%挑出来再用DDD。
“这种架构真正有价值的地方不超过10%”相当佩服这种推理··科学大把定理老百姓都不懂,即使是科学家也有各种分支,然后说站在一方说另一方“某定理的价值不超过10%”····吐槽不能···再说若果这10%主导整个系统的逻辑,即使不100%,我也会投入80%、90%精神。

我觉得你对DDD的了解还不够深

我个人觉得在软件架构的时候不能为了DDD而去DDD,DDD能获得很多的优点:1.能够获得处理复杂的业务逻辑的能力。2.能够很好的发挥面向对象的优势。。等等。但是它也有一些不足:1.难于掌握,要掌握DDD必须有一定的功底才行。2.如果没有数据库映射工具的话,数据的持久也很麻烦 3.会使你的系统变的复杂
如果是一些相对业务逻辑不复杂的系统或者说你设计的类和你的数据库表可以很好的对应,没有必要绝对的DDD,可以有很多折中的办法。比如活动记录 或者采用表模块这样的设计我个人觉得更适合

首先DDD是有利于分析和设计的,回答一下问题
1.领域层的类,我认为是你说的是根或者实体,他们不直接数据和事务打交道,而是通过Repository来保存实体的状态,至于怎么实现,可以用数据库,或者其它的方式,至于事务,一般是在应用层处理,所以Domain不关心底层的实现,专注于领域,如果你需要在领域层处理事务,可能是实体方法的粒度太大,需要重构。

2.DDD的清晰的分层,你所说的与数据库的交互都是Infrastructure层的职责,至于用JPA或者其它看需求

3.大多数方法只是对存储方法的调用,我个人认识是领域分析不到位,或者这个项目不适合用DDD,领域分析是从领域的角度去分析问题,比如支付系统有Order,可以向Order添加Item,可以支付。这些都是领域分析,这些不依赖其它层,这部分是在需求不变的前提下是相对稳定。

关于BDD,不知道JBehave如何