使用qi4j实现DCI架构

我曾经DCI架构是什么?在一文中提到Qi4j框架实现DCI架构比较好,dzone今天就有一篇文章专门谈Implementing DCI in Qi4j。

DCI是一种新的构建面向对象应用的方法途径,DCI:Data数据,Context场景,Interaction交互。该文谈了如何使用DCI构建一个RESTAPI。

Roles

DCI核心思想是:对象不是由单个类如POJO组成的,而是使用Roles角色来组合组装功能,再具体一点,当我们在这里说“对象Object”,是指DDD中的实体,而不是值对象或其他。在带有保存数据的企业架构中,实体将被划分为很多Roles,每个Roles有各自的职责目标,这点可以使用Qi4j的Mixin完成(AOP),或者Scala的Traits,否则很难以实现。

Data

DCI的数据代表实体对象中的数据,数据是进行私有Mixin(Privatemixin)混合的,不可以被实体对象以外对象访问,Privatemixin是一个重要概念,可以在不使用private语法的情况下,让状态成为对象的私有属性。

Context

Context掌握了在一个交互场景中角色与一个特定对象实例的关系映射。COntext可以使用POJO或Qi4j的TransientComposites实现,后者功能很强大,体现了面向组合的架构特点。

Interactions

交互是作为场景实现的方法,寻找场景中相关的角色,然后激活调用其领域方法,重要一点是,交互的方法应该对应于用户界面表现层的Action方法,也就是MVC中控制器的方法。如果你改变了你的界面,就改变相应的场景和交互。

该文结合了作者项目,演示了DCI的使用情况,这个项目只有三个简单领域模型:Project,User,和Task,Task是一种可分配的角色,而Project和User是一种分配者的角色,User处于一种被分配的角色,这样,我们可以有一个简单场景叫InboxContext,有一个方法(或者叫交互):assign(Assignable):分配(可分配)。

Project有一个带有Task的InBox,那是我们要分配给用户的Task。但是它既可以被一个User拥有,也可以被一个特定Project拥有,这样就需要角色职责分配,更多职责发现和切分见:对象的责任与职责

切分的目标是达成这样:"assignment"不和这样场景绑定:"查询一个用户的tasks列表,他们其中一个能够分配给这个用户",而应该是:"查询可分配者的分配Assignments列表,选择其中一个分配给分配者"。这两者区别就是,与具体用户解耦,从对象职责行为这个新角度来考虑场景,交互和场景与具体类解耦,只和角色有关系,我们就可以在角色这个层次来思考设计。

这种方式可以让程序员侧重角色这个抽象层面来考虑,然后着重了解职责“分配”是如何工作的,而不必着眼于用户,因为用户有用户名和密码有个人简历等等很多细节资料;如果我们以后使用Calculation替代Task,使用ClusterNode替代User,这样我们就不必修改这个系统,可以复用它了,唯一修改的就是对象和角色的映射,如果熟悉RBAC基于角色权限设计原理的话,应该对这种从角色和职责角度考虑方式的认同。

该文还使用代码表达了如何实现交互,如下代码:

InteractionContext map = new
 InteractionContext();
RootContext context = assembler.objectBuilderFactory().newObjectBuilder( RootContext.class
 ).use(stack ).newInstance();
InboxContext inboxContext = context.user( userId ).inbox();

为当前场景创造了RootContext,这是所有场景的根场景,从根场景能够创建子场景InboxContext,其中userId用来寻找特定的User,如果使用REST实现,这个userId就使用URL实现,比如:"/administrator/inbox".

为了实现前面"查询可分配者的分配Assignments列表,选择其中一个分配给分配者",代码如下:

class
 AssignmentsMixin
     implements Assignments
 {
     @This   //自动注入Data


     AssignmentsData data;

     
//选择其中一个分配给分配者


     public
 void
 assignTo( Assignable assignable, Assignee assignee )
     {
         assignable.assignTo( assignee );
         data.assignments().add( assignable );
     }

     
//查询可分配者的分配Assignments列表


     public
 Iterable<Assignable> assignments()
     {
         return
 data.assignments();
     }
 }

我们如果实现将任务Task分配给某个用户,就使用如下代码:

inboxContext.assignTo( task );

更多代码可见原文有下载。

最后,该文总结了这样做的优点:

以上代码可能多了些,但是能够完成代码的可读性,代码的可维护性,易于改变拓展性,如果你使用普通POJO方法,把所有职责放在一个类中,好像很简单了,一旦这个软件项目发展到一定程度,就难以拓展维护。

此外还有优点是可以重用复用,能够避免贫血模型,在目前所谓主流架构Spring或EJB之中,你为了避免将所有行为方法放入一个大类中,将数据放在实体中,将行为分开放到服务Service中(见请问一下这样分层对不对),这实际破坏了封装,就是MF指责的失血模型,问题摆在那里,但是一直没有得到解决, DCI架构解决了这个问题。

和xmuzyu讨论了以Jive Jdon案例说明对象职责和SOLID原则应用一文中的“阅读次数”到底应不应该属于帖子这个对象的属性,其实这个问题存在很多案例中,“阅读次数”可以说不是帖子的固有属性,帖子这个对象离开“阅读次数”这个属性不是不能存在,探究“阅读次数”这个属性和固有属性比如帖子的名称等是有区别的,属于一种场景属性,也就是说只有在阅读这个场景下才会发生的属性。

DCI架构本质:DCI:对象的Data数据,对象使用的Context场景,对象的Interaction交互行为,我们知道,对象有数据属性和方法行为,以前我们是封装在一个对象中,为什么要封装在一个对象中?因为这个对象在某个需求用例场景中被使用时需要这些属性和方法行为,注意了,这里面有一个关键点,就是对象被使用,以前我们进行面向对象设计,是遵循一种静态原则,因为这个对象被使用需要这些属性和行为,所以,我们在编码时将这些属性和行为写在这个类中。

这个逻辑过程是不对的,那是因为过去程序语言平台落后,导致了我们这种思维逻辑,现在是的思维逻辑是:对象被使用时需要的属性和行为不必一定要在编写代码时写入,而是在运行时再注入或MiXIN混合进去。

这就是DCI架构的本质。

我们还是以“阅读次数”这个案例分析,在以JiveJdon案例说明对象职责和SOLID原则应用一文我们讨论焦点集中在“阅读次数”到底应不应该属于帖子这个对象,其实这个角度有问题了,如果按照DCI架构和对象角色职责这个架构来考虑,应该这样:

我们除去具体事物如用户和帖子,而是从角色来分析这个场景,就像上贴中存在“分配者”和“被分配者”一样,这里存在两个角色“阅读者”和“被阅读者”,而场景Context是和“阅读者”有关的一个对象,那么一个帖子被阅读的建模描述如下:

第一步:根据用户创建一个阅读场景对象:

ReadContextreadcontext=RootContext.create(userId)

第二步:由阅读场景对象来执行交互行为阅读:

readcontext.view(readed);

其中readed被阅读者就是帖子。

所以,按照DCI架构来说,阅读这个行为应该属于Context场景这个行为,只有在这个场景下才会发生阅读这个行为。

如果不按照DCI架构来分析,我们会倾向把阅读这个行为放到“帖子”这个对象中,有人担心,以后再有“顶”这个行为,那么顶的结果数据也要放到“帖子”这个对象中,这里有一个误区,“帖子”不是一个类,不是说所有场景属性结果都放如帖子这个一个类中,帖子只是一个对象群中根实体,我们可以根据不同场景创建不同实体类或值对象,都从属于“帖子”这个根实体,组成一个边界和子领域。

当然,如果有Qi4j或AOP的Mixin来支持,我们也可以使用楼上这种DCI架构方式。

但是个人认为,上面AssignmentsMixin类实际是和业务无关的器具技术类,如果语言本身提供AssignmentsMixin这个混合机制,就不再需要了。

另外,DCI架构中对于我们普通的POJO技术,也就是没有Mixin支持的环境中,最大的借鉴就是引入Context这个场景对象,D和I以前都有,就是对象的数据和方法,通过Context这个对象引入,使的我们的软件更加贴近需求分析中用例场景,四色原型可以说是DCI架构的前言。

Context这个对象其实和角色动作职责有关,如果你不是管理者,你就不可能进入管理这个场景,角色是场景的前置条件,交互动作是场景的必然结果,很符合DBC设计原则。

这应该是面向对象设计领域新的革命思维。

DCI架构的核心是Context,场景是角色参与具体业务活动的表现,从用例图可以看出,如下图:

这张图是典型的需求用例图,这是通往软件的第一步,通常我们从用例图到软件建模,有一个转换,那是因为过去软件技术不行,只能做妥协转换,而根据DCI架构,我们可以在软件中直接实现用例需求。

DCI架构的场景有时很象SOA中的服务,服务确实为对象被使用提供了场景,所以,很多场景是在提供服务时发生的。但是必须注意到,交互行为属于场景这个范围内,你不能忽视场景这个边界,而直接将交互行为方法写到服务中,这就造成了服务类过大,回到面向过程编程了。见这个有关Spring下的分层讨论

原文:http://www.jdon.com/jivejdon/thread/38266#23127442

相关推荐