2012-09-19 13:42 "@tangxuehua"的内容
即发出事件的是role,而利用事件重建的是聚合根;这里的根本问题是,我们重建对象时,重建的是聚合根,而不是“聚合根扮演某个角色后的东西”。 ...

我大概老眼昏花,实在看不懂你的问题,担心搞错了,非常抱歉了,反正我的观点在我其他帖子都已经阐述,统一语言是:场景 事件和状态。

当一个对象存在时,它就扮演某个角色做某个交互事情;如果这个对象不存在需要被创建,就由它的母亲去扮演角色把它创建生出来。因为看不懂你的问题,所以,只能胡乱喊几句,太深入的问题需要几天沉淀才能想清楚,留给我们彼此多点时间,也请其他道友帮助回答。谢谢。
[该贴被banq于2012-09-19 13:59修改过]

问题是这样的对不对:
某个人借助与外星人实现时空穿梭,等他想回去的时候,发现回不去了,因为外星人不见了,哈哈
[该贴被clonalman于2012-09-19 14:21修改过]

clonalman 很幽默,你这么一说,我大概明白它的意思,其实一般聚合根有两个,一个对外,一个对内,就象正局长一般盯着上级部门,而局单位内部事宜都是由几个副局长分管了。

比如DDD书籍中Car和发动机两个都是聚合根,发动机是对内维护内部团结一致的副局长角色一样的聚合根,而Car则是一个集合概念,类似正局长角色,负责对外,可以认为是一个框架,Car确实是一个组合体。

所以,一般重建聚合根时,我倾向于先构建一个框架聚合根Car,然后由Car负责加载重建其内部一个个元素组件。

jivejdon论坛聚合根也是由Thread这个组合体和RootMessage这两个聚合根,因此加载一个帖子时,首先加载一个Thread,其他部分加载看情况而定,如果是要看帖子内部内容,届时显示时会加载与内容显示相关的部件;而如果只是想看看很多Thread列表,从外部看Thread,那届时就会加载Thread与外部特征有关的数据,这样实现起来本论坛有的道友感觉比较快的原因之一。


[该贴被banq于2012-09-19 14:36修改过]

通过文字有时确实表达不清楚了,还是写段简单的代码来说明吧:


//表示一个人,一个Model,也是一个聚合根
public class Person : AggregateRoot
{
public string Name { get; set; }
//姓名
public int Age { get; set; }
//年龄
public float Score { get; set; }
//考试成绩
}
//学生角色,该角色可以被一个person模型扮演
//在用java,c之类的强类型语言,一般都只能用组合的方式为model赋予角色
//像javascript这样的弱类型语言,才能更自然的做到DCI的将行为注入到model
public class StudentRole
{
private Person _actor;

public StudentRole(Person actor)
{
this._actor = actor;
}

//学生参加考试,一个我假象的角色行为
public void Exam()
{
ApplyEvent(new ExamFinished(this.Id, 80.5));
}

//内部方法,根据事件更改角色扮演者的数据,这里是更改person的成绩状态信息
private void OnExamFinished(ExamFinished evnt)
{
_actor.Score = evnt.Score;
}
}
//这是一个event sourcing中的event,表示考试已完成
public class ExamFinished
{
public Guid StudentId { get; }
//表示哪个学生
public float Score { get; }
//表示考试成绩分数

public ExamFinished(Guid studentId, float score)
{
this.StudentId = studentId;
this.score = score;
}
}


//然后人扮演学生角色参与考试的过程大概可以这样写:
var person = repository.Get<Person>(personId);
//先从仓储取出人
var student = person.ActAs<StudentRole>();
//人扮演学生角色
student.Exam();
//参考考试,实际就是在执行角色的行为

好了,我的问题是:
事件是StudentRole产生的,StudentRole内部组合了一个person实例,
同时产生的ExamFinished事件可以驱动领域的其他部分的响应或者是领域外的
其他东西的响应, 这都没问题;问题是,当我通过以下语句(从仓储获取person模型实例)重建person对象时,
var person = repository.Get<Person>(personId);

因为用到了event sourcing, 那Person类如何会认识ExamFinished这个事件,
这个事件它压根都不知道是什么。难道我要把
private void OnExamFinished(ExamFinished evnt)
这个方法放在Person类里吗?

所以我想搞清楚的是,你通过事件溯源作用的对象是person实例,
还是下面这个扮演了学生角色后的“混合体”:
var student = person.ActAs<StudentRole>();

这个时候的student实际上是一个“混合体”,是一个扮演了StudentRole之后的人。

我觉得,既然产生事件的是这个混合体,那应用事件溯源的也应该是这个混合体。

我不知道有没有把问题描述清楚,呵呵。
[该贴被tangxuehua于2012-09-19 15:26修改过]
[该贴被tangxuehua于2012-09-19 15:28修改过]

2012-09-19 15:18 "@tangxuehua"的内容
难道我要把
private void OnExamFinished(ExamFinished evnt)
这个方法放在Person类里吗? ...

看懂多少回复多少吧,答案是的,一般通过Mixin这种功能,Java中通过动态代理,而Scala通过trait实现混合,这种混合使得实体Person具备了其扮演的角色的一切行为。

当然,通过语言本身提供的魔法混合有些Magic,象做魔术一样,不太容易理解,我采取的是注射。

Class Person{

@inject
private StudentRole studentRole


}

这样,一个实体扮演多少角色一目了然。

[该贴被banq于2012-09-19 16:15修改过]

2012-09-19 15:49 "@banq"的内容
Class Person{

@inject
private StudentRole studentRole


} ...

我更喜欢:

Student不是一个角色,而已与Person并列的实体

class Person {
public string Name { get; set; } //姓名
public int Age { get; set; } //年龄
}

Student与Score都是在特定也业务下的,最好不要放在Person,
哪天Person毕业了,不是Student,Score就是多余的了

class Student
{
public Person Person { get; set }
public float Score { get; set; } //考试成绩
}

class StudentExamService
{
IBusService bus;

public Finish(Student student, float score)
{
//业务:通知其他人(他父母)考试成绩
//架构:Event Sourcing
ExamFinished e = new ExamFinished(student, score);
.....
bus.send(e);

student.Score = score;
}
}

真的没有必要加什么角色,学生就是学生,说成学生角色,只是为了迎合架构的需要而已,非领域的概念
(大道至简,道法自然)

[该贴被clonalman于2012-09-19 16:20修改过]

2012-09-19 16:15 "@clonalman"的内容
Student与Score都是在特定也业务下的,最好不要放在Person,
哪天Person毕业了,不是Student,Score就是多余的了 ...

言之有理啊,但是散落四处的各种角色也不利于代码阅读。

我现在想到,更优雅方式可能是使用元注解:

@StudentRole
Class Person{

}

我不认同学生角色是为迎合架构的需要哦,实际领域中也存在这个角色这个概念啊,只不过原来隐式而已,现在显式出来,显式出来好处很多,会发现灵活度增加。

那么哪天Person毕业了,不是Student,那他会担任新的角色,只要在这个社会上都有角色的人啊,那么当回顾一个人的简历时,我们会发现他担任过的各种角色,这又什么不好呢?所以,我坚持将角色陈列在数据实体中的显式做法,只不过优雅程度不同而已。

2012-09-19 16:23 "@banq"的内容
言之有理啊,但是散落四处的各种角色也不利于代码阅读。 ...

1、如果你把所谓"角色"散落在各处,那只能是你在划分系统,子系统,模块没有做好,
在相关的模块里,它就是一个实实在在的实体,你想把它拿掉门都没有

2、考试模块他就是一个学生,
如果将来他成了一个公司老总,可能出现在公司相关的分析决策当中
系统是有边界的,边界的拓展是会一直出现各种各样的角色的,所谓"角色"是没法枚举完的
把角色事先放Person,也就是上帝才能知道了,
那这样思路设计的架构,会是一个"神仙架构"(我不否能它能实现业务功能)

3、对于一个人的简历回顾,其实际就是上就是对"角色"的查询而已....

[该贴被clonalman于2012-09-19 16:51修改过]

对于考分成绩,这是角色学生参与考试这个行为后的结果,还是不要放在Person中,而是放在结构实体中,比如考试这个实体,考试这个实体实际可能是一个关联组合对象。

Class 考试{

Person person;

Score score;//考试成绩

}

至于clonalman 刚才帖子里谈到,"对于一个人简历是对角色查询”,我知道clonalman其实和我和视角已经不同,我是希望把隐式的通过数据表达的重要领域概念显式化拿到代码上面来实现,而不是被数据掩盖。

有时候,我们发现阅读一个系统代码还不能理解这个系统,必须运行起来结合数据才能理解。设想一下,如果我们让程序员只通过阅读代码,就能大概掌握其运行的架构,这样的系统可读性才很强,这就需要我们做很多隐式转变显式的工作。

2012-09-19 17:00 "@banq"的内容
Class 考试{

Person person;

Score score;//考试成绩

} ...

上面一个模型其表达的含义是这个Person做为学生只靠一次试,只有一个成绩,
如果会靠多次,就需要一个成绩表,就看系统需求边界在哪里

如果你的业务需求上要显示表达所有角色,实现应该是这样的

class Student
{
public Person Person { get; set }
public float Score { get; set; } //考试成绩
}

class Boss
{
public Person Person { get; set }
public Company Company { get; set; }
}

class Person {
public string Name { get; set; } //姓名
public int Age { get; set; } //年龄
public Student Student { get; set; }
public Boss Boss { get; set; }
}

或则

class Role
{

}

class Student:Role
{
public Person Person { get; set }
public float Score { get; set; } //考试成绩
}

class Boss:Role
{
public Person Person { get; set }
public Company Company { get; set; }
}

class Person {
public string Name { get; set; } //姓名
public int Age { get; set; } //年龄
public RoleCollection Roles { get; set; }
}

在领域中,关心它所担任过的所谓"角色"的显式表达

这时候,获得简历就不是通过查询,而是通过Person导航



[该贴被clonalman于2012-09-19 17:21修改过]



上面一个模型其表达的含义是这个Person做为学生只靠一次试,只有一个成绩,
如果会靠多次,就需要一个成绩表,就看系统需求边界在哪里

好像不是吧?

2012-09-19 17:41 "@wee"的内容
上面一个模型其表达的含义是这个Person做为学生只靠一次试,只有一个成绩,如果会靠多次,就需要一个成绩表,就看系统需求边界在哪里


好像不是吧? ...

是这个
class Student
{
public Person Person { get; set }
public float Score { get; set; } //考试成绩
}