在开始DDD之前,你需要了解DDD的一些基础知识,聚合(AggregateRoot)、实体(Entity)、值对象(ValueObject),工厂(Factory),仓储(Repository)和领域服务(DomainService)。在这里值对象有区别于C#的值类型,请不要将两者混淆,一开始我也范了这个错误。聚合并不是设计的越大越好,相反的我们应该尽量为聚合划分最小范围。

从一个简单的例子开始。用户注册,三层结构的做法基本上就这样

public class User
{
public string Id { get; set; }
public string LoginId { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Cellphone { get; set; }
...
} public class UserService
{
private readonly UserDao _userDao;
public void Register(User user)
{
_userDao.Save(user);
}
} public class UserController
{
private readonly UserService _userService;
public UserController(UserService userService)
{
this._userService = userService;
} public void Register(FormCollection form)
{
User user = new User();
user.LoginId = form.Get("LoginId");
user.Password = form.Get("Password");
user.Name = form.Get("Name");
user.Email = form.Get("Email");
user.Cellphone = form.Get("Cellphone");
... _userService.Register(user);
}
}

上面的代码就是UI传什么数据过来就给User相应的属性赋值,这种做法往往是凭我们的经验及对这种业务的普遍性做出的设计。他存在一点问题,用户注册到底需要哪些信息并不明确,这些具体业务我们并不知道。那么领域专家提出来了,为了简化用户注册流程,只需要新用户提供登录名,密码及邮箱就行了,且用户名不能修改。

OK。那么UI的代码就会变成

public class UserController
{
public void Register(FormCollection form)
{
User user = new User();
user.LoginId = form.Get("LoginId");
user.Password = form.Get("Password");
user.Email = form.Get("Email"); _userService.Register(user);
}
}

似乎问题解决了。但仔细想想以上代码基本上没有什么业务,也没有规则,更像是添加一条数据记录,而且要命的是用户名不能修改根本没办法体现。

如果用了DDD后那么会有怎样的变化呢?

public class User
{
public User(string loginId, string password, string email)
{
this.LoginId = loginId;
this.Password = password;
this.Email = email;
} public string Id { get; private set; }
public string LoginId { get; private set; }
public string Password { get; private set; }
public string Name { get; private set; }
public string Email { get; private set; }
public string Cellphone { get; private set; }
...
} public interface IUserRepository
{
void Add(User user); void Remove(User user);
} public class UserController
{
private readonly IUserRepository _userRepository;
public UserController(IUserRepository userRepository)
{
this._userRepository = userRepository;
} public void Register(FormCollection form)
{
User user = new User(form.Get("LoginId"), form.Get("Password"), form.Get("Email")); _userRepository.Add(user);
}
}

第一步应该将模型的描述属性定义为值对象,所有状态的变化都是通过业务行为来改变。这样也就很自然的看到一个新用户档案从无到有的过程,即new一个User,需要提供登录名,密码及邮箱且用户名不能修改,这也完全吻合领域专家的要求,有点领域驱动要你怎么做的意思了吧。

接下来领域专家又提出来了,密码不能采用明文,需要加密,用什么加密方式还不确定。我靠,这还要怎么写代码。其实还是可以继续设计,抽象出一个加密规则的接口

public interface IEncryptionService
{
string Encrypt(string password);
}

至此好像new一个user变得有些复杂了,首先构造函数不能解决密码加密的问题,怎么办?那就是通过Factory

public class UserFactory
{
private readonly IEncryptionService _encryptionService;
public UserFactory(IEncryptionService encryptionService)
{
this._encryptionService = encryptionService;
} public User Create(string loginId, string password, string email)
{
return new User(loginId, _encryptionService.Encrypt(password), email);
}
} public class UserController
{
private readonly IUserRepository _userRepository;
private readonly UserFactory _userFactory;
public UserController(IUserRepository userRepository, IEncrypt encrypt)
{
this._userRepository = userRepository;
this._userFactory = new UserFactory(encrypt);
} public void Register(FormCollection form)
{
User user = _userFactory.Create(form.Get("LoginId"), form.Get("Password"), form.Get("Email")); _userRepository.Add(user);
}
}

这样就领域中封装了业务规则,在前端人员根本不需要感知密码是怎么加密的这些业务,而且代码也是简单的。

接下来领域专家又提出了一个规则就是用户名必须唯一。那么问题又来了,工厂构造出来的user并不能保证用户名是唯一的,如果让这些业务在ui端做判断,事必增加前端开发人员的工作量,同时按照DDD的原则是不应该将具体业务暴露出来的。怎么办,那就要引用Domain Service了。简单修改下代码

public interface IUserRepository
{
void Add(User user); bool IsLoginIdExist(string loginId);
} public class DomainService
{
private readonly IUserRepository _userRepository;
public DomainService(IUserRepository userRepository)
{
this._userRepository = userRepository;
} public void Register(User user)
{
if (_userRepository.IsLoginIdExist(user.LoginId)) {
throw new Exception("用户名已存在");
} _userRepository.Add(user);
}
} public class UserController
{
private readonly UserFactory _userFactory;
private readonly DomainService _domainService;
public UserController(IUserRepository userRepository, IEncrypt encrypt)
{
this._domainService = new DomainService(userRepository);
this._userFactory = new UserFactory(encrypt);
} public void Register(FormCollection form)
{
User user = _userFactory.Create(form.Get("LoginId"), form.Get("Password"), form.Get("Email")); _domainService.Register(user);
}
}

第一篇就写这么多吧,基本上DDD的相关知识都涉及了一点。接下来将慢慢丰富此例子。

如何开始DDD的更多相关文章

  1. 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑

    阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...

  2. 如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成

    阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是 ...

  3. 如何一步一步用DDD设计一个电商网站(十)—— 一个完整的购物车

     阅读目录 前言 回顾 梳理 实现 结语 一.前言 之前的文章中已经涉及到了购买商品加入购物车,购物车内购物项的金额计算等功能.本篇准备把剩下的购物车的基本概念一次处理完. 二.回顾 在动手之前我对之 ...

  4. 如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念

    一.前言     DDD(领域驱动设计)的一些介绍网上资料很多,这里就不继续描述了.自己使用领域驱动设计摸滚打爬也有2年多的时间,出于对知识的总结和分享,也是对自我理解的一个公开检验,介于博客园这个平 ...

  5. 如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文

    阅读目录 前言 明确业务细节 建模 实现 结语 一.前言 上一篇我们已经确立的购买上下文和销售上下文的交互方式,传送门在此:http://www.cnblogs.com/Zachary-Fan/p/D ...

  6. 如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文

    阅读目录 前言 如何在一个项目中实现多个上下文的业务 售价上下文与购买上下文的集成 结语 一.前言 前几篇已经实现了一个最简单的购买过程,这次开始往这个过程中增加一些东西.比如促销.会员价等,在我们的 ...

  7. 如何一步一步用DDD设计一个电商网站(五)—— 停下脚步,重新出发

    阅读目录 前言 单元测试 纠正错误,重新出发 结语 一.前言 实际编码已经写了2篇了,在这过程中非常感谢有听到观点不同的声音,借着这个契机,今天这篇就把大家提出的建议一个个的过一遍,重新整理,重新出发 ...

  8. 如何一步一步用DDD设计一个电商网站(四)—— 把商品卖给用户

    阅读目录 前言 怎么卖 领域服务的使用 回到现实 结语 一.前言 上篇中我们讲述了“把商品卖给用户”中的商品和用户的初步设计.现在把剩余的“卖”这个动作给做了.这里提醒一下,正常情况下,我们的每一步业 ...

  9. 如何一步一步用DDD设计一个电商网站(三)—— 初涉核心域

    一.前言 结合我们本次系列的第一篇博文中提到的上下文映射图(传送门:如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念),得知我们这个电商网站的核心域就是销售子域.因为电子商务是以信息网络 ...

  10. 如何一步一步用DDD设计一个电商网站(二)—— 项目架构

    阅读目录 前言 六边形架构 终于开始建项目了 DDD中的3个臭皮匠 CQRS(Command Query Responsibility Segregation) 结语 一.前言 上一篇我们讲了DDD的 ...

随机推荐

  1. redis5 集群迁移方案

    Redis5 集群迁移方案 一.KEY优化 1.按原来要求进行优化与大KEY分拆. 二.现Redis 集群缩容(对业务无影响) 主节点按要求合并至3个主节点. 业务配置为3主4从 删除没有槽的主节点与 ...

  2. 亿级 ELK 日志平台构建部署实践

    本篇主要讲工作中的真实经历,我们怎么打造亿级日志平台,同时手把手教大家建立起这样一套亿级 ELK 系统.日志平台具体发展历程可以参考上篇 「从 ELK 到 EFK 演进」 废话不多说,老司机们座好了, ...

  3. CentOS7使用ZFS文件系统

    默认情况下,CentOS7并没有含ZFS支持的文件和,需要进行更新和安装第三方库. Step 1:安装第三方库和更新系统 [root@localhost ~]# rpm -Uvh http://www ...

  4. spring mvc mybatis shiro构建cms系统ios android

    开发语言: java.ios.android 部署平台: linux.window jdk版本:JDK1.7以上版本 开发工具: eclipse.idea等 服务器中间件:Tomcat 6.7.Jbo ...

  5. web安全之机器学习入门——1.环境搭建

    前置知识 算法和数据的辩证关系:算法和数据是机器学习解决实际问题不可或缺的两大因素.早期机器学习十分依赖特征提取,随着发展,人们发现通过增加训练数据量,让机器从大量基础特征中可以自动关联出潜在关系,自 ...

  6. delphi三层结构常出现的问题和解决方案

    以下问题出现原因有可能多个,暂时将我遇见的记录下来,以后有新的在陆续更新上去,有网友愿意的话也可以共同测试一下. 一,无法更新定位行.一些值可能已在最后一次读取已更改. 错误出现前提: 1, 录数据时 ...

  7. Spring 框架下 (增 删 改 )基本操作

    //applicationContext.xml 配置文件 <?xml version="1.0" encoding="UTF-8"?><be ...

  8. ABP框架系列之十六:(Dapper-Integration-Dapper集成)

    Introduction Dapper is an object-relational mapper (ORM) for .NET. Abp.Dapper package simply integra ...

  9. python repr()和str()

    两者功能差不多,都是把对象转为字符串表示形式,但是也有区别,repr()之后再eval()可以转为原型,但str()只能保证大多数,不能100% 其中主要的 差别在与 字符串对象本身,比如 a = ' ...

  10. h5移动网页唤起App

    最近这个困惑了很久,不断的有一些坑,目前还有疑问关于iOS唤起无效时会出现弹框的问题,这个最后再说 1.首先可能需要判断当前浏览器的来源(目前开发的App还没有上架,所以针对腾讯出品的大家广为人知的微 ...