我打算明天贴一些自己目前正在用DCI架构编写的代码实例,望大家一起讨论和指教。

我打算明天贴一些自己目前正在用DCI架构编写的代码实例,届时希望大家能参与一起讨论,高人多多指教。
项目所属领域:基金领域
业务:申购基金、赎回基金、转换基金。
--------------------------------------------------------------------------------------
PPT:Fund(基金)
代码如下:


/// <summary>
/// 基金
/// </summary>
public class Fund : IAggregateRoot {

/// <summary>
/// 基金代码
/// </summary>
/// <param name="fundCode"></param>
public Fund(string fundCode) {
this.Code = fundCode;
}

/// <summary>
/// 基金代码
/// </summary>
public string Code {
get;
set;
}

/// <summary>
/// 基金名称
/// </summary>
public string Name {
get;
set;
}

/// <summary>
/// 基金类型
/// </summary>
public FundType Type {
get;
set;
}

/// <summary>
/// 基金种类(A类、B类、C类)
/// </summary>
public FundClass Class {
get;
set;
}

/// <summary>
/// 基金所属公司
/// </summary>
public FundCompany Company {
get;
set;
}

/// <summary>
/// 基金成立日期
/// </summary>
public DateTime SetupDate {
get;
set;
}
}

固有属性FundType(基金类型)
代码如下:

/// <summary>
/// 基金类型
/// </summary>
public class FundType : IValueObject {

public FundType(int fundTypeCode, string fundTypeName) {
this.Code = fundTypeCode;
this.Name = fundTypeName;
}

/// <summary>
/// 基金类型代码
/// </summary>
public int Code {
get;
set;
}

/// <summary>
/// 基金类型名称
/// </summary>
public string Name {
get;
set;
}
}

固有属性FundClass(基金种类)
代码如下:

/// <summary>
/// 基金种类
/// </summary>
public enum FundClass {
/// <summary>
/// A类基金
/// </summary>
A = 1,
/// <summary>
/// B类基金
/// </summary>
B = 2,
/// <summary>
/// C类基金
/// </summary>
C = 3
}

固有属性FundCompany(基金所属公司)
代码如下:

/// <summary>
/// 基金所属公司
/// </summary>
public class FundCompany : IValueObject {

public FundCompany(string fundCompanyId, string fundCompanyName) {
this.Id = fundCompanyId;
this.Name = fundCompanyName;
}

/// <summary>
/// 基金所属公司编号/代码
/// </summary>
public string Id {
get;
set;
}

/// <summary>
/// 基金所属公司编号名称
/// </summary>
public string Name {
get;
set;
}
}

[该贴被achilleswar于2011-05-12 08:38修改过]

好了,基金要参与申购业务了。
首先请出SubscribeFund("申购的基金"角色)
代码如下:


/// <summary>
/// 申购的基金
/// </summary>
public class SubscribeFund : IRole {

private Fund _fund;

public SubscribeFund(Fund fund) {
this._fund = fund;
}

public string Code {
get {
return this._fund.Code;
}
}

/// <summary>
/// 基金不存在
/// </summary>
public bool NotExists {
get {
return this._fund == null;
}
}

/// <summary>
/// 封闭式、创新型封闭式、理财产品、QDII和首发基金不允许购买
/// </summary>
public bool CannotBuy {
get {
bool isCannotBuyFundType = false;
switch (this._fund.Type.Code) {
case 6:
isCannotBuyFundType = true;
break;
case 7:
isCannotBuyFundType = true;
break;
case 9:
isCannotBuyFundType = true;
break;
case 10:
isCannotBuyFundType = true;
break;
}
bool isUnSetuppedFund = (this._fund.SetupDate == DateTime.MinValue);
return isCannotBuyFundType || isUnSetuppedFund;
}
}

/// <summary>
/// 基金是否处于封闭期或暂停申购
/// </summary>
public bool SubscribeClosed {
get {
string fundCode = this._fund.Code;
return FundService.IsSubscribeClosed(fundCode);
}
}
}

再请出SubscribeUser ("申购的用户"角色)
代码如下:

/// <summary>
/// 申购的用户
/// </summary>
public class SubscribeUser : IRole {

private User _user;

public SubscribeUser() {

}

public SubscribeUser(User user) {
this._user = user;
}

/// <summary>
/// 用户不存在
/// </summary>
public bool NotExists {
get {
return this._user == null;
}
}

/// <summary>
/// 用户被禁赛
/// </summary>
public bool Suspended {
get {
return this._user.IsActive == false;
}
}

/// <summary>
/// 选手允许购买的基金
/// </summary>
public IList<SubscribeFund> CanBuyFunds {
get {
return new List<SubscribeFund>();
}
}

/// <summary>
/// 用户是否不允许购买基金
/// </summary>
/// <param name="fund"></param>
/// <returns></returns>
public bool CannotBuyFund(SubscribeFund fund) {
return !(this.CanBuyFunds.Count == 0 || this.CanBuyFunds.Contains(fund));
}

/// <summary>
/// 用户账户余额不足
/// </summary>
/// <param name="amount"></param>
/// <returns></returns>
public bool InsufficientBalance(decimal amount) {
return this._user.Account.Balance < amount;
}
}

接着SubscribeContext("基金申购"场景)粉墨登场
代码如下:

/// <summary>
/// 基金申购场景
/// </summary>
public class SubscribeContext : IContext {

private SubscribeUser _user;
private SubscribeFund _fund;
private decimal _amount;

public SubscribeContext(
SubscribeUser user
,SubscribeFund fund
, decimal amount
) {
this._user = user;
this._fund = fund;
this._amount = amount;
}

public ActionResponse Interact() {
ActionResponse response = new ActionResponse();
//验证基金是否存在
if (this._fund.NotExists) {
return new FailureResponse(
MessageResourceManager.GetMessage(
"ERR_NOT_EXISTS_FUND").Code
, MessageResourceManager.GetMessage(
"ERR_NOT_EXISTS_FUND").Text
);
}
//封闭式、创新型封闭式、理财产品、QDII和首发基金不允许购买
if (this._fund.CannotBuy) {
return new FailureResponse(
MessageResourceManager.GetMessage(
"ERR_CAN_NOT_BUY_FUND").Code
, MessageResourceManager.GetMessage(
"ERR_CAN_NOT_BUY_FUND").Text
);
}
//验证基金是否处于封闭期或暂停申购
if (this._fund.SubscribeClosed) {
return new FailureResponse(
MessageResourceManager.GetMessage(
"ERR_SUBSCRIBE_CLOSED_FUND").Code
, MessageResourceManager.GetMessage(
"ERR_SUBSCRIBE_CLOSED_FUND").Text
);
}
//验证用户是否允许购买此基金
if (this._user.CannotBuyFund(this._fund)) {
return new FailureResponse(
MessageResourceManager.GetMessage(
"ERR_CAN_NOT_BUY_THIS_FUND_USER").Code
, MessageResourceManager.GetMessage(
"ERR_CAN_NOT_BUY_THIS_FUND_USER").Text
);
}
//验证用户帐户余额是否不足
if (this._user.InsufficientBalance(this._amount)) {
return new FailureResponse(
MessageResourceManager.GetMessage(
"ERR_INSUFFICIENT_BALANCE").Code
, MessageResourceManager.GetMessage(
"ERR_INSUFFICIENT_BALANCE").Text
);
}
//验证交易金额
SubscribeAmountValidationContext subscribeAmountValidationContext = new SubscribeAmountValidationContext(this._amount);
SubscribeAmountValidator subscribeAmountValidator = ValidatorFactory.CreateSubscribeAmountValidator(subscribeAmountValidationContext);
ValidationResult amountValidationResult = subscribeAmountValidator.Validate();
if (amountValidationResult.InValid) {
return new FailureResponse(
amountValidationResult.StatusCode
, amountValidationResult.StatusText
);
}
//验证资产配置
AssetValidationContext assetValidationContext = new AssetValidationContext(
new SubscribeUser()
, this._fund
, this._amount
);
AssetValidator assetValidator = ValidatorFactory.CreateAssetValidator(assetValidationContext);
ValidationResult assetValidationResult = assetValidator.Validate();
if (assetValidationResult.InValid) {
return new FailureResponse(
assetValidationResult.StatusCode
, assetValidationResult.StatusText
);
}
return response;
}
}

[该贴被achilleswar于2011-05-12 08:52修改过]

domain service层构建一个互动的环境


/// <summary>
/// 基金申购服务
/// </summary>
public class SubscribeService {

/// <summary>
/// 申购基金
/// </summary>
/// <param name="userId"></param>
/// <param name="fundCode"></param>
/// <param name="amount"></param>
/// <param name="remark"></param>
/// <returns></returns>
public virtual ActionResponse Subscribe(
int userId
, string fundCode
, decimal amount
, string remark
) {
ActionResponse response = new ActionResponse();
IUserRepository userRepository = RepositoryFactory.CreateUserRepository();
User user = userRepository.GetUser(userId);
Fund fund = FundFactory.BuildFund(fundCode);
IContext context = new SubscribeContext(
new SubscribeUser(user)
, new SubscribeFund(fund)
, amount
);
response = context.Interact();
return response;
}
}

这里部分代码先略,如ActionResponse,这是一个响应

忘了贴上PPT用户了,在此补上
PPT:User(用户)
代码如下:


/// <summary>
/// 用户
/// </summary>
public class User {

/// <summary>
/// 用户编号
/// </summary>
public int Id {
get;
set;
}

/// <summary>
/// 用户名称
/// </summary>
public string Name {
get;
set;
}

/// <summary>
/// 用户帐户
/// </summary>
public Account Account {
get;
set;
}

/// <summary>
/// 用户所属组
/// </summary>
public Team Team {
get;
set;
}

/// <summary>
/// 用户状态
/// </summary>
public bool IsActive {
get;
set;
}

/// <summary>
/// 注册日期
/// </summary>
public DateTime RegisterDate {
get;
set;
}
}

用户的固有属性Account(账户)
代码如下

/// <summary>
/// 用户帐户
/// </summary>
public class Account : IValueObject {

/// <summary>
/// 用户总资产
/// </summary>
public decimal TotalAsset {
get;
set;
}

/// <summary>
/// 账户余额
/// </summary>
public decimal Balance {
get;
set;
}
}

用户的固有属性Team(组)
代码如下:

/// <summary>
/// 组
/// </summary>
public class Team : IValueObject {

/// <summary>
/// 小组代码
/// </summary>
public int Code {
get;
set;
}

/// <summary>
/// 小组名称
/// </summary>
public string Name {
get;
set;
}

/// <summary>
/// 本组初始本金
/// </summary>
public decimal Principal {
get;
set;
}
}

[该贴被achilleswar于2011-05-12 09:20修改过]

不错,其实你的申购用户subscribeuser 已经是一个角色,可能你没有发觉。

2011年05月12日 16:28 "@banq"的内容
申购用户subscribeuser 已经是一个角色,可能你没有发觉 ...

是的,在这里是把她(SubscribeUser)当作一个角色的,对应PPT User。
关注跟参与进来讨论的道友比较少,我觉得有必要明天再简单介绍一下这些业务逻辑,因为我认为这个案例很适合用DCI架构来做,再者明天再上剩下的两个业务中的“赎回”用例代码,敬请关注和指导。
[该贴被achilleswar于2011-05-12 17:13修改过]

楼主用了 service内部调用了context ,这个service是个环境。
是否采用这个形式,还是直接调用 context? Banq大哥说说看。

我的感觉是否下面的架构:

应用层 -> 场景 -> 领域 ?? 各位讨论讨论。

楼主提交的代码和我目前自己做的一个项目中极其相似,也是有
表现层-->粗粒度service-->Context-->entity,valueobject,service

不过我的场景里没有再进行角色的,而是场景中直接指导领域模型完成业务逻辑,我之所以没有抽取出角色是因为感觉有时候没有必要,因为逻辑如果不是很复杂,再抽象出角色,有点过度设计。

ps:另外 @liontseng 哥们说的问题也是我在思考的问题,到底有没有必要通过服务去封转场景呢?目前来说服务是静态的,无状态的,但是场景是有状态的,动态的,场景一般来说是每个请求过来创建出常见对象,然后完成业务逻辑,场景对象生命周期就结束了,而封装成服务可以更好的实现远程调用,比如一个分布式系统中,如果不通过服务封转场景的话,客户端代码可能就需要自己去创建场景,而场景的创建往往比较复杂(因为场景有很多不变量要满足,场景一当创建好了以后,就可以直接进行业务逻辑操作了,我目前的做法是场景专门的工厂对象来保证场景的不变量)。
因此如果业务操作逻辑需要进行远程调用的话,我建议通过服务暴漏粗粒度的接口给客户端使用,客户端不用关心太多细节,但是如果是本系统内部调用的话,场景可以直接使用。

也希望大家参与讨论讨论。

2011年05月13日 22:31 "@xmuzyu"的内容
表现层-->粗粒度service-->Context-->entity,valueobject,service ...

是的,实际做项目可以采取这种过渡式的架构,实际上,在理论上我在想,如果把场景前移到客户端也未必不是一种好方式,就象我们剧团下乡演出一样,因为我们现在已经可以使用DCI将演出场景的道具准备妥当,到时现场一搭建组装就可以。

为什么SOA把服务留在服务器端呢?因为技术限制,限制于Java这些静态型将类作为对象语言限制,不能将场景需要的角色行为 实体进行分离,必须吧角色行为静态地写到类中,作为其方法,而DCI可以是无方法角色,以及一些简单的领域对象,在客户端现场通过场景进行组装。

如果是这样,SOA真的可以死了,一枪毙命。哈哈,只是畅想。

2011年05月14日 08:37 "@banq"的内容
如果把场景前移到客户端也未必不是一种好方式 ...

没错,基于api平台的思路就是这个。我在企业内部也采用过类似的思路。面向整个企业我只能开发一个通用的平台板,但有些用户的需求太定制化了,我实在无法把它放到我的平台中,所以就干脆只提供些基本服务,让用户自己开发客户端了。

2011年05月14日 08:37 "@banq"的内容
实际上,在理论上我在想,如果把场景前移到客户端也未必不是一种好方式 ...

呵呵,恩,认同,这我之前发过一个帖子http://xmuzyq.iteye.com/blog/975675,我们可以把场景打成客户端jar的形式丢到具体的客户端服务器去使用,目前有项目也采用这种方式,因为运算逻辑复杂,如果放在中心服务器端,中心服务器端会成为瓶颈,而前端的应用服务器压力小,所以将逻辑从中心节点转移到各个客户端节点,这点其实也使得系统伸缩性更好一点。当场景打包成jar放到客户度调用,只不过场景中可能需要调用其它的远程接口,而这种远程接口多数情况下是用来获取数据的。

2011年05月14日 10:24 "@xmuzyu"的内容
我们可以把场景打成客户端jar的形式丢到具体的客户端服务器去使用,目前有项目也采用这种方式, ...

这是一种方式,更优雅的可以通过消息或事件来驱动。

2011年05月12日 16:28 "@banq"的内容
其实你的申购用户subscribeuser 已经是一个角色,可能你没有发觉。 ...

啊,如梦初醒啊,其实User就已经是个角色了,其实不应该包含Team和Account,因为这两个特征是参与到某些场景中才应有的。感谢感谢。