上一篇针对用户注册案例简单介绍了如何使用 DDD,接下来我将继续针对这个例子做一下补充。先将User模型丰富起来,因为目前看上去他和贫血模型还没有啥大的区别。

首先还是由领域专家来说明业务,他提出了用户注册成功后需要完善个人信息,这些信息包括姓名、生日、手机号。还需要用户提供一些联系信息,如地址,邮编等。那么我们就可以根据业务定义方法了。昨天netfocus兄指正了loginid所产生的歧义,表示认同,所以今天一并修改了一下。

public class AddressInfo
{
public AddressInfo(string province, string city, string address, string postcode)
{
this.Province = province;
this.City = city;
this.Address = address;
this.Postcode = postcode;
} public string Province { get; private set; }
public string City { get; private set; }
public string Address { get; private set; }
public string Postcode { get; private set; }
} public class User
{
public User(string name, string password, string email)
{
this.Name = name;
this.Password = password;
this.Email = email;
} public string Id { get; private set; }
public string Name { get; private set; }
public string Password { get; private set; }
public string RealName { get; private set; }
public string Email { get; private set; }
public string Cellphone { get; private set; }
public string Birthday { get; private set; }
public AddressInfo Address { get; private set; } public void UpdateBasicInfo(string realName, string birthday, string cellphone)
{
this.RealName = realName;
this.Birthday = birthday;
this.Cellphone = cellphone;
} public void UpdateAddress(AddressInfo address)
{
this.Address = address;
}
}

那么前端的代码也很简单

public class UserController
{
private readonly IUserRepository _userRepository;
public void SetProfile(FormCollection form)
{
var user = _userRepository.Get(form.Get("id")); user.UpdateBasicInfo(form.Get("name"), form.Get("birthday"), form.Get("cellphone"));
} public void SetAddress(FormCollection form)
{
var user = _userRepository.Get(form.Get("id")); var address = new AddressInfo(form.Get("province"), form.Get("city"),
form.Get("address"), form.Get("postcode")); user.UpdateAddress(address);
}
}

以上的代码很好理解,只是设计了一个AddressInfo的值对象。

接下来将演示一下用户登录验证和修改密码。一般的做法:

public interface IUserRepository
{
User GetByName(string loginId);
} public class UserController
{
private readonly IUserRepository _userRepository;
public UserController(IUserRepository userRepository)
{
this._userRepository = userRepository;
} public void Logon(FormCollection form)
{
User user = _userRepository.GetByName(form.Get("LoginId"));
if (user == null)
throw new Exception("loginId", "账号不存在。");
if (user.Password != form.Get("Password"))
throw new Exception("password", "密码不正确。"); FormsAuthentication.SetAuthCookie(user.Name, createPersistentCookie);
}
}

请注意上述代码比较密码是错误的方式,因为上一篇说明了密码是加过密的。所以要修改一下,首先要注入IEncryptionService,那么就会这样判断

if (user.Password != _encryptionService.Encrypt(form.Get("Password")))

这样会有什么问题呢。目前IEncryptionService的接口相对还比较简单,如果IEncryptionService提供了针对不同业务的好多加密接口,那么前端人员就需要详细了解IEncryptionService的api,增加了复杂度。再对User封装一个方法,然后对Contoller代码再稍做修改

public class User
{
public bool VerifyPassword(string password, IEncryptionService encryptionService)
{
return this.Pasword == encryptionService.Encrypt(password);
}
} public class UserController
{
public void Logon(FormCollection form)
{
User user = _userRepository.GetByName(form.Get("LoginId"));
if (user == null)
throw new Exception("loginId", "账号不存在。");
if (user.VerifyPassword(form.Get("Password"), _encryptionService))
throw new Exception("password", "密码不正确。"); FormsAuthentication.SetAuthCookie(user.Name, createPersistentCookie);
}
}

这样具体密码采用了什么加密接口就不用关心了,将此规则封闭在了domain内,还有一个主要目的是为了修改密码时能够复用。也许你并不认同这种做法,好像也没啥变化,当然也没关系,解决问题就行,我只想表达聚合可以封装哪些方法。
再仔细考虑我觉得上述代码表达的业务还是比较多,首先要查询该登录名的用户是否存在,再去验证密码,如果需求再有其他规则,如禁用的用户不能登录,具有时效性的用户过期了也不能登录等等,这样是不是越来越复杂,前端开发人员需要掌握的业务知识就会越来越多,所以最好将此业务封装在领域内,ui端只需要传入登录名和密码。
说了这么多,User本身是无法做到这一点的,那么还是要将这些业务规则写在上一篇提到过的DomainService

public class DomainService
{
private readonly IUserRepository _userRepository;
private readonly IEncryptionService _encryptionService;
public DomainService(IUserRepository userRepository, IEncryptionService encryptionService)
{
this._userRepository = userRepository;
this._encryptionService = encryptionService;
} public User Authenticate(string loginId, string password)
{
var user = _userRepository.GetByName(loginId);
if (user == null)
throw new Exception("loginId", "账号不存在。");
if (!user.VerifyPassword(password, _encryptionService))
throw new Exception("password", "密码不正确。"); return user;
}
} public class UserController
{
public void Logon(FormCollection form)
{
try {
User user = _domainService.Authenticate(form.Get("LoginId"), form.Get("Password"));
FormsAuthentication.SetAuthCookie(user.Name, createPersistentCookie);
}
catch (Exception) {
throw;
}
}
}

这样是不是更好一点呢?
接下来再来说修改密码。直接上代码吧

public class User
{
public void ChangePassword(string oldPwd, string newPwd, IEncryptionService encryptionService)
{
if (!this.VerifyPassword(oldPwd, encryptionService))
throw new Exception("旧密码输入不正确。"); this.Pasword = encryptionService.Encrypt(newPwd);
}
} public class UserController
{
public void ModifyPassword(FormCollection form)
{
try {
User user = _userRepository.GetByName(User.Identity.Name);
user.ChangePassword(form.Get("oldPwd"), form.Get("newPwd"), _encryptionService);
_userRepository.Update(user);
}
catch (Exception) { throw;
}
}
}

好吧,这到这里吧,希望对你有帮助。下一篇再继续讨论丰富一下用户注册的过程,引入事件驱动。

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

  1. 用CQRS+ES实现DDD

    用CQRS+ES实现DDD 这篇文章应该算是对前三篇的一个补充,在写之前说个题外话,有园友评论这是在用三层架构在写DDD,我的个人理解DDD是一种设计思想,跟具体用什么架构应该没有什么关系,DDD也需 ...

  2. 开始DDD

    如何开始DDD(完) 连续写了两篇文章,这一篇我想是序的完结篇了.结合用户注册的例子再将他简单丰富一下.在这里只添加一个简单需求,就是用户注册成功后给用户发一封邮件.补充一下之前的代码 public ...

  3. DDD中的Unitwork与DomainEvent如何相容?(续)

    上篇中说到了面临的问题(传送门:DDD设计中的Unitwork与DomainEvent如何相容?),和当时实现的一个解决方案.在实际使用了几天后,有了新的思路,和@trunks 兄提出的观点类似.下面 ...

  4. DDD 领域驱动设计-三个问题思考实体和值对象(续)

    上一篇:DDD 领域驱动设计-三个问题思考实体和值对象 说实话,整理现在这一篇博文的想法,在上一篇发布出来的时候就有了,但到现在才动起笔来,而且写之前又反复读了上一篇博文的内容及评论,然后去收集资料, ...

  5. DDD~基础设施层~续

    回到目录 在之前写的DDD~基础设施层文章中,提到了UnitOfWork,它里面有一些方法,但经过项目证明,不应该有Save和IsExplicitSubmit,而这个工作单元只起到了数据上下文统一的作 ...

  6. 基于DDD的现代ASP.NET开发框架--ABP系列文章总目录

    ABP相关岗位招聘:给热爱.NET新技术和ABP框架的朋友带来一个高薪的工作机会 ABP交流会录像视频:ABP架构设计交流群-7月18日上海线下交流会的内容分享(有高清录像视频的链接) 代码自动生成: ...

  7. 我的“第一次”,就这样没了:DDD(领域驱动设计)理论结合实践

    写在前面 插一句:本人超爱落网-<平凡的世界>这一期,分享给大家. 阅读目录: 关于DDD 前期分析 框架搭建 代码实现 开源-发布 后记 第一次听你,清风吹送,田野短笛:第一次看你,半弯 ...

  8. 我也来说说DDD~大话目录

    回到占占推荐博客索引 DDD之前没有接触过,但一但有了接触就一发不可收拾,他会带去进入一个全新的世界! DDD不是新技术,而是新思想,新模式,是软件开发领域的一次突破,它更接近于业务,对于业务的改动它 ...

  9. DDD 领域驱动设计-“臆想”中的实体和值对象

    其他博文: DDD 领域驱动设计-三个问题思考实体和值对象 DDD 领域驱动设计-三个问题思考实体和值对象(续) 以下内容属于博主"臆想",如有不当,请别当真. 扯淡开始: 诺兰的 ...

随机推荐

  1. 【VBA】杨辉三角

    Private Sub Workbook_Open() Dim loopA As Integer Dim loopB As Integer Dim loopNum As Integer Dim top ...

  2. Django REST framework的分页

    DRF分页组件 为什么要使用分页 我们数据表中可能会有成千上万条数据,当我们访问某张表的所有数据时,我们不太可能需要一次把所有的数据都展示出来,因为数据量很大,对服务端的内存压力比较大还有就是网络传输 ...

  3. AC自动机解题记录

    1.HDU 2222 Keywords Search 模板题 #include <bits/stdc++.h> #define fir first #define sec second # ...

  4. 项目总结19:layui实现表格渲染、表格搜索、数据获取

    项目总结19:layui实现表格渲染.表格搜索.数据获取 1-参考资料:https://www.layui.com/demo/table/reload.html 2-本次总结的是layui的表格功能, ...

  5. SpringMVC不能引入静态资源

    SpringMVC不能引入静态资源 问题:部署项目后程序加载或用浏览器访问时出现类似的警告, 2011-01-19 10:52:51,646 WARN [org.springframework.web ...

  6. 3.Redis高级功能

    3.Redis高级功能3.1 慢查询分析3.1.1 慢查询的两个配置参数3.1.2 最佳实践3.1.3 单线程架构3.2 Redis Shell3.2.1 redis-cli 详解3.2.2 redi ...

  7. ping内网一台虚拟机延时很大(hyper-v虚拟机)的解决办法

    问题现象: ping 内网一台虚拟机延时很大,不稳定,造成业务系统响应慢.查看服务器上各种资源都正常. 解决办法: 在物理机上找到和hyper-v绑定的那个网卡,把“虚拟机队列”禁用掉就好了,如下图: ...

  8. mysql开启调试日志general_log开启跟踪日志

    general_log = 1 general_log_file = /tmp/umail_mysql.log 有时候,不清楚程序执行了什么sql语句,但是又要排除错误,找不到原因的情况下, 可以在m ...

  9. 模板学习实践二 pointer

    c++ template学习记录 使用模板将实际类型的指针进行封装 当变量退出作用域 自动delete // 1111.cpp : 定义控制台应用程序的入口点. // #include "s ...

  10. iOS 开发之 KVC - setValuesForKeysWithDictionary 解析

    从字典映射到一个对象,这是KVC中的一个方法所提供的,这个方法就是 setValuesForKeysWithDictionary:,非常好用,不需要你来一一的给对象赋值而直接从字典初始化即可,但用的不 ...