实践中如何处理这种业务方法?


需求描述是这样的:crm系统中。“潜在客户”归档后变为“客户”。这个“归档”需要创建一个“客户”,并删除对应的“潜在客户”。新的“客户”对象的属性值多数是来源于原来的“潜在客户对象”。

这个Archive是一个业务方法,那么它应该写在domain层中还是service(应用)层中?

如果是在domain层中,伪代码如下:
public class PreCustomer
{
public void Archive()
{
Customer customer = new Customer();
customer.xx = xx;
customer.yy = this.yy;

// 其他操作。。。

CustomerDao.Add(cutomer);
PreCustomerDao.Delete(this);
}
}
如果是在service层中,伪代码如下:
public class CustomerService
{
public void Archive(int preCustomerId)
{
PreCustomer preCustomer = ProCustomer.GetById(preCustomerId);
Customer customer = Customer.Create(preCustomer);

// 其他操作。。。

CustomerDao.Add(cutomer);
PreCustomerDao.Delete(preCustomer );
}
}

这两种方式,都能完成任务,看似也差不多。但重要的是Archive是一个业务方法,所以我倾向于放到domain中,既第一种方式。但是放到domain中问题是domain模型被dao污染了,而且这时执行Archive后PreCustomer成了一个悬浮对象了。第二种看似没什么问题,但是这种业务方法到底service层,将导致类似情况对service层不断添加代码,导致service过于厚重,从而退化成事务脚本模式了

想看看大家是怎么理解这个问题了。谢谢

我的建议当然应该是domain,我是通过领域事件来驱动悬浮对象持久化。

或者可以通过dci或mixin将持久化注入给领域对象,而且不会污染领域。

2012-06-21 21:37 "@banq"的内容
或者可以通过DCI或mixin将持久化注入给领域对象,而且不会污染领域。 ...

这种方式的话,需要多出额外的接口,而且只能通过接口去调用,感觉不像是在用领域对象

不好意思,我想到这是对将客户实体转为另外客户实体的过程,类似银行转帐,从一个账户转账到另外一个账户。

这个过程应该是一种服务。

和银行转账不一样吧?转账涉及的是两个对象的金额属性的变化。而这个需求是对象本身的转换。

而且,我觉得转账这个所谓的领域服务设计也是代码重构的结果,转账本身也是账户对象的职责,试想如果转账规则依赖于账户的私有属性时,放在转账服务中的转账实现如何去访问账户的私有属性?所以我任务转账服务(或者说领域服务)应该是代码重构的结果,应该是被领域实体调用的,供领域实体完成职责所需的。是领域实体依赖领域服务,而不是领域服务依赖领域实体,因此,领域服务被上层(应用层)调用是不应该的,不然成了一种更细粒度的应用层代码了。而应用层代码主要是领域模型的外观层。

您是怎么理解的呢?

嗯,行为是属于对象职责,还是跨对象间过程应该是区别它是对象的有主行为还是跨对象的服务的判断标准。

看到需求中提出对旧对象潜在客户进行删除,创建新的客户资料,这好像是从对象边界外对对象的操作,相当于跨两实体对象了。如果归档属于潜在客户职责,但是归档却是灭了自己,能开始或结束一个对象生命周期的行为应该来自于外力。

比如宇宙起源,到底谁创建了宇宙,给与宇宙大爆炸那么大的能量,肯定不是宇宙自己,上帝是一种可能性,呵呵。

当然还要具体结合上下文,如果归档属于客户的一种状态改变,原来是潜在客户,潜在客户是客户的一种特别规格,那么归档相当于客户级别升级,那么归档肯定不是一种领域服务,而是实体的职责。

如果归档是客户在某种场景下扮演某种角色而具备角色职责能力,比如局长有审批职责权力,那么使用dci实现很恰当。

方法行为分三种:
实体基本特征行为,或维护聚合边界内一致性完整性的基本职责行为。
跨实体的领域服务行为。
实体扮演角色具备的角色行为。

对需求中的动作过程活动事件也就是动词的界定应该上面三个范围,我倾向于用四色原型对需求的活动用mi进行一个提炼,然后用相应的领域服务 实体状态或规格去实现它。


如果不是规格升级,而就是两个独立的领域实体。那么应该用领域服务?该如何写呢?

1)domain层:
public class CustomerService
{
public void Archive(PreCustomer preCustomer)
{
Customer customer = new Customer();
customer.xx = xx;
customer.yy = this.yy;

// 其他操作。。。

return customer;
}
}
然后在应用层
Customer customer = CustomerService.Archive(preCustomer);
preCustomerDao.Delete(preCustomer);
customerDao.Add(customer);

2)
domain层的领域服务
public class CustomerService
{
public void Archive(PreCustomer preCustomer)
{
Customer customer = new Customer();
customer.xx = xx;
customer.yy = this.yy;

// 其他操作。。。

preCustomerDao.Delete(preCustomer);
customerDao.Add(customer);
}
}

该用哪种呢?第一种在应用层操作Dao,完成持久化。第二种在领域层中调用dao,完成持久化。

从对象职责来看,持久化其实是领域对象的一个职责,当你创建删除一个对象时,其背后的数据库操作应该有领域模型自己内部搞定,就象我们在word中打一个字或删除一个字,这些字是否存盘应该是word软件内部自动搞定。

我有个疑问,潜在客户和客户,应该都是广义上的客户,区别在于有没有存在交易关系。应该都是一个实体,只是实体的状态不同罢了。我不知道我的理解是否正确?

2012-06-27 11:22 "@snow0613"的内容
应该都是一个实体,只是实体的状态不同罢了。我不知道我的理解是否正确 ...

这是有可能,我前面也提及了,关键我们需要更全面的需求信息来核对。

如果把客户理解成“会员”,那么潜在客户就是CRM里面的“联系人”
显然这是两个不同的领域

因为删除潜在客户还需要更新其他状态,创建客户也必然有很多步骤,所以在domain层直接操作PreCustomerDao应该是不安全的

个人比较倾向于第二种写法,将业务放在service层

sorry,一段时间没来了。

需求可能无法全面说明,但确定的一点是:潜在客户和客户在我这里应该是2个领域实体,所以,才有以上问题。

不用犹豫是否是一个领域实体了(不同状态)。我的问题的前台就是2种实体。

2012-06-27 14:18 "@banq"的内容
这是有可能,我前面也提及了,关键我们需要更全面的需求信息来核对。 ...

首先,确实是两个不同的领域实体。

对于您提到的很多步骤以及安全的问题。我不明白什么是不安全?是指不能更详细的控制业务流程吗?

“很多步骤”这个是做选择的标准吗?我理解不管多复杂也应该是domain内部复杂吧?我们仔细想象一下,可能根本没有什么步骤简单的需求,即使现在简单,也不保证未来随着业务发展,精细化,将使步骤逐渐复杂。如果以是否很多步骤作为在哪个层实现的判断依据,那么这个依据在不同阶段将导致不同结果,如果那时我们将代码在层间转移是不是很悲剧?


2012-06-27 20:43 "@gameboyLV"的内容
如果把客户理解成“会员”,那么潜在客户就是CRM里面的“联系人”
显然这是两个不同的领域

因为删除潜在客户还需要更新其他状态,创建客户也必然有很多步骤,所以在domain层直接操作PreCustomerDao应该是不安全的

个人比较倾向 ...

2012-07-08 22:51 "@xxooxx"的内容
“很多步骤”这个是做选择的标准吗?我理解不管多复杂也应该是domain内部复杂吧? ...

当然是,比如网站通行证的注册,需要注册论坛,调用其他第三方系统的API,如果您将这些业务全部封装在USER的DOMAIN里,肯定是不合适的。假如第三方的API变更,那么整个USER的DOMAIN也无法运转了。

放在服务里就好多了,可以随时更换API的调用方式。

===============================

同样的业务也可以适用crm的创建客户,你能保证“创建客户”只是创建crm的user吗?

万一以后业务扩展,crm需要和其他系统对接,那么所有系统都可以发出createuser的消息,领域服务接收到消息之后,调用所有系统的createuser方法,完成用户创建。

场景思维可能更好定位实体位置,实体方法(行为)只会影响自身,交互行为应该放在场景中。

这种思维可以很好跟Actor结合:每个Actor收到消息也就只能改变自身,不能直接对其他Actor进行修改,而相关的消息的组合可以看作场景的剧本。