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

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

  1. public class User
  2. {
  3. public string Id { get; set; }
  4. public string LoginId { get; set; }
  5. public string Password { get; set; }
  6. public string Name { get; set; }
  7. public string Email { get; set; }
  8. public string Cellphone { get; set; }
  9. ...
  10. }
  11.  
  12. public class UserService
  13. {
  14. private readonly UserDao _userDao;
  15. public void Register(User user)
  16. {
  17. _userDao.Save(user);
  18. }
  19. }
  20.  
  21. public class UserController
  22. {
  23. private readonly UserService _userService;
  24. public UserController(UserService userService)
  25. {
  26. this._userService = userService;
  27. }
  28.  
  29. public void Register(FormCollection form)
  30. {
  31. User user = new User();
  32. user.LoginId = form.Get("LoginId");
  33. user.Password = form.Get("Password");
  34. user.Name = form.Get("Name");
  35. user.Email = form.Get("Email");
  36. user.Cellphone = form.Get("Cellphone");
  37. ...
  38.  
  39. _userService.Register(user);
  40. }
  41. }

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

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

  1. public class UserController
  2. {
  3. public void Register(FormCollection form)
  4. {
  5. User user = new User();
  6. user.LoginId = form.Get("LoginId");
  7. user.Password = form.Get("Password");
  8. user.Email = form.Get("Email");
  9.  
  10. _userService.Register(user);
  11. }
  12. }

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

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

  1. public class User
  2. {
  3. public User(string loginId, string password, string email)
  4. {
  5. this.LoginId = loginId;
  6. this.Password = password;
  7. this.Email = email;
  8. }
  9.  
  10. public string Id { get; private set; }
  11. public string LoginId { get; private set; }
  12. public string Password { get; private set; }
  13. public string Name { get; private set; }
  14. public string Email { get; private set; }
  15. public string Cellphone { get; private set; }
  16. ...
  17. }
  18.  
  19. public interface IUserRepository
  20. {
  21. void Add(User user);
  22.  
  23. void Remove(User user);
  24. }
  25.  
  26. public class UserController
  27. {
  28. private readonly IUserRepository _userRepository;
  29. public UserController(IUserRepository userRepository)
  30. {
  31. this._userRepository = userRepository;
  32. }
  33.  
  34. public void Register(FormCollection form)
  35. {
  36. User user = new User(form.Get("LoginId"), form.Get("Password"), form.Get("Email"));
  37.  
  38. _userRepository.Add(user);
  39. }
  40. }

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

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

  1. public interface IEncryptionService
  2. {
  3. string Encrypt(string password);
  4. }

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

  1. public class UserFactory
  2. {
  3. private readonly IEncryptionService _encryptionService;
  4. public UserFactory(IEncryptionService encryptionService)
  5. {
  6. this._encryptionService = encryptionService;
  7. }
  8.  
  9. public User Create(string loginId, string password, string email)
  10. {
  11. return new User(loginId, _encryptionService.Encrypt(password), email);
  12. }
  13. }
  14.  
  15. public class UserController
  16. {
  17. private readonly IUserRepository _userRepository;
  18. private readonly UserFactory _userFactory;
  19. public UserController(IUserRepository userRepository, IEncrypt encrypt)
  20. {
  21. this._userRepository = userRepository;
  22. this._userFactory = new UserFactory(encrypt);
  23. }
  24.  
  25. public void Register(FormCollection form)
  26. {
  27. User user = _userFactory.Create(form.Get("LoginId"), form.Get("Password"), form.Get("Email"));
  28.  
  29. _userRepository.Add(user);
  30. }
  31. }

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

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

  1. public interface IUserRepository
  2. {
  3. void Add(User user);
  4.  
  5. bool IsLoginIdExist(string loginId);
  6. }
  7.  
  8. public class DomainService
  9. {
  10. private readonly IUserRepository _userRepository;
  11. public DomainService(IUserRepository userRepository)
  12. {
  13. this._userRepository = userRepository;
  14. }
  15.  
  16. public void Register(User user)
  17. {
  18. if (_userRepository.IsLoginIdExist(user.LoginId)) {
  19. throw new Exception("用户名已存在");
  20. }
  21.  
  22. _userRepository.Add(user);
  23. }
  24. }
  25.  
  26. public class UserController
  27. {
  28. private readonly UserFactory _userFactory;
  29. private readonly DomainService _domainService;
  30. public UserController(IUserRepository userRepository, IEncrypt encrypt)
  31. {
  32. this._domainService = new DomainService(userRepository);
  33. this._userFactory = new UserFactory(encrypt);
  34. }
  35.  
  36. public void Register(FormCollection form)
  37. {
  38. User user = _userFactory.Create(form.Get("LoginId"), form.Get("Password"), form.Get("Email"));
  39.  
  40. _domainService.Register(user);
  41. }
  42. }

第一篇就写这么多吧,基本上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. WD Elements 与 time machine

    备份是很重要的问题. 之前买了一个 WD Elements,想要格式化成 HFS 的格式. 不然不能被 Time Machine 使用. 但是用磁盘工具不能成功,因为 EFI 分区的问题. 参考下面网 ...

  2. 基于python的selenium自动化测试环境搭建

    Windows下的环境搭建: 1.安装python2.7.152.cmd里敲pip install selenium3.安装firefox47.geckodriver11(并将geckodriver. ...

  3. 容器101:Docker基础

    Docker如此受欢迎的一个原因是它提供了“一次开发,随处运行”的承诺.Docker提供了一种将应用程序及其运行时依赖性打包到单个容器中的简单方法.它还提供了一个运行时抽象,使容器能够跨不同版本的Li ...

  4. [leetcode]39. Combination Sum组合之和

    Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), fin ...

  5. 第一个用IDEA写的程序——“前言中不允许有内容”

    "前言中不允许有内容" 这是用IDEA写的第一个程序-- 它出现了一些问题 让人很难过 希望有人可以帮助解答,谢谢 程序是这样子的 运行完是这样显示的

  6. etcd-v2第一集

    网站:https://github.com/coreos/etcd 一些观点:https://yq.aliyun.com/articles/11035 1.etcd是键值存储仓库,配置共享和服务发现2 ...

  7. IP、TCP、DNS协议

    ·······················································IP协议························· 位于网络层,作用是把数据包传送 ...

  8. font-smoothing使用后字体看起来会更清晰舒服

    CSS3里面加入了一个“-webkit-font-smoothing”属性. 这个属性可以使页面上的字体抗锯齿,使用后字体看起来会更清晰舒服. 加上之后就顿时感觉页面小清晰了. 淘宝也在用哦! 它有三 ...

  9. Maven依赖及范围

    一.依赖范围(scope): 共5种,compile (编译).test (测试).runtime (运行时).provided.system compile:编译依赖范围,在编译,测试,运行时都需要 ...

  10. sqoop加载mysql数据库

    ./bin/sqoop list-databases --connect jdbc:mysql://node-001:3306/ --username root --password hadoop