0%

如何开始DDD(续) - young.han - 博客园

Excerpt

上一篇针对用户注册案例简单介绍了如何使用 DDD,接下来我将继续针对这个例子做一下补充。先将User模型丰富起来,因为目前看上去他和贫血模型还没有啥大的区别。首先还是由领域专家来说明业务,他提出了用户注册成功后需要完善个人信息,这些信息包括姓名、生日、手机号。还需要用户提供一些联系信息,如地址,邮编


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

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

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<span>public</span> <span>class</span><span> AddressInfo
{
</span><span>public</span> AddressInfo(<span>string</span> province, <span>string</span> city, <span>string</span> address, <span>string</span><span> postcode)
{
</span><span>this</span>.Province =<span> province;
</span><span>this</span>.City =<span> city;
</span><span>this</span>.Address =<span> address;
</span><span>this</span>.Postcode =<span> postcode;
}

</span><span>public</span> <span>string</span> Province { <span>get</span>; <span>private</span> <span>set</span><span>; }
</span><span>public</span> <span>string</span> City { <span>get</span>; <span>private</span> <span>set</span><span>; }
</span><span>public</span> <span>string</span> Address { <span>get</span>; <span>private</span> <span>set</span><span>; }
</span><span>public</span> <span>string</span> Postcode { <span>get</span>; <span>private</span> <span>set</span><span>; }
}

</span><span>public</span> <span>class</span><span> User
{
</span><span>public</span> User(<span>string</span> name, <span>string</span> password, <span>string</span><span> email)
{
</span><span>this</span>.Name =<span> name;
</span><span>this</span>.Password =<span> password;
</span><span>this</span>.Email =<span> email;
}

</span><span>public</span> <span>string</span> Id { <span>get</span>; <span>private</span> <span>set</span><span>; }
</span><span>public</span> <span>string</span> Name { <span>get</span>; <span>private</span> <span>set</span><span>; }
</span><span>public</span> <span>string</span> Password { <span>get</span>; <span>private</span> <span>set</span><span>; }
</span><span>public</span> <span>string</span> RealName { <span>get</span>; <span>private</span> <span>set</span><span>; }
</span><span>public</span> <span>string</span> Email { <span>get</span>; <span>private</span> <span>set</span><span>; }
</span><span>public</span> <span>string</span> Cellphone { <span>get</span>; <span>private</span> <span>set</span><span>; }
</span><span>public</span> <span>string</span> Birthday { <span>get</span>; <span>private</span> <span>set</span><span>; }
</span><span>public</span> AddressInfo Address { <span>get</span>; <span>private</span> <span>set</span><span>; }

</span><span>public</span> <span>void</span> UpdateBasicInfo(<span>string</span> realName, <span>string</span> birthday, <span>string</span><span> cellphone)
{
</span><span>this</span>.RealName =<span> realName;
</span><span>this</span>.Birthday =<span> birthday;
</span><span>this</span>.Cellphone =<span> cellphone;
}

</span><span>public</span> <span>void</span><span> UpdateAddress(AddressInfo address)
{
</span><span>this</span>.Address =<span> address;
}
}</span>

复制代码

那么前端的代码也很简单

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<span>public</span> <span>class</span><span> UserController
{
</span><span>private</span> <span>readonly</span><span> IUserRepository _userRepository;
</span><span>public</span> <span>void</span><span> SetProfile(FormCollection form)
{
</span><span>var</span> user = _userRepository.Get(form.Get(<span>"</span><span>id</span><span>"</span><span>));

user.UpdateBasicInfo(form.Get(</span><span>"</span><span>name</span><span>"</span>), form.Get(<span>"</span><span>birthday</span><span>"</span>), form.Get(<span>"</span><span>cellphone</span><span>"</span><span>));
}

</span><span>public</span> <span>void</span><span> SetAddress(FormCollection form)
{
</span><span>var</span> user = _userRepository.Get(form.Get(<span>"</span><span>id</span><span>"</span><span>));

</span><span>var</span> address = <span>new</span> AddressInfo(form.Get(<span>"</span><span>province</span><span>"</span>), form.Get(<span>"</span><span>city</span><span>"</span><span>),
form.Get(</span><span>"</span><span>address</span><span>"</span>), form.Get(<span>"</span><span>postcode</span><span>"</span><span>));

user.UpdateAddress(address);
}
}</span>

复制代码

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

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

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<span>public</span> <span>interface</span><span> IUserRepository
{
User GetByName(</span><span>string</span><span> loginId);
}

</span><span>public</span> <span>class</span><span> UserController
{
</span><span>private</span> <span>readonly</span><span> IUserRepository _userRepository;
</span><span>public</span><span> UserController(IUserRepository userRepository)
{
</span><span>this</span>._userRepository =<span> userRepository;
}

</span><span>public</span> <span>void</span><span> Logon(FormCollection form)
{
User user </span>= _userRepository.GetByName(form.Get(<span>"</span><span>LoginId</span><span>"</span><span>));
</span><span>if</span> (user == <span>null</span><span>)
</span><span>throw</span> <span>new</span> Exception(<span>"</span><span>loginId</span><span>"</span>, <span>"</span><span>账号不存在。</span><span>"</span><span>);
</span><span>if</span> (user.Password != form.Get(<span>"</span><span>Password</span><span>"</span><span>))
</span><span>throw</span> <span>new</span> Exception(<span>"</span><span>password</span><span>"</span>, <span>"</span><span>密码不正确。</span><span>"</span><span>);

FormsAuthentication.SetAuthCookie(user.Name, createPersistentCookie);
}
}</span>

复制代码

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

1
<span>if</span> (user.Password != _encryptionService.Encrypt(form.Get(<span>"</span><span>Password</span><span>"</span>)))

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

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<span>public</span> <span>class</span><span> User
{
</span><span>public</span> <span>bool</span> VerifyPassword(<span>string</span><span> password, IEncryptionService encryptionService)
{
</span><span>return</span> <span>this</span>.Pasword ==<span> encryptionService.Encrypt(password);
}
}

</span><span>public</span> <span>class</span><span> UserController
{
</span><span>public</span> <span>void</span><span> Logon(FormCollection form)
{
User user </span>= _userRepository.GetByName(form.Get(<span>"</span><span>LoginId</span><span>"</span><span>));
</span><span>if</span> (user == <span>null</span><span>)
</span><span>throw</span> <span>new</span> Exception(<span>"</span><span>loginId</span><span>"</span>, <span>"</span><span>账号不存在。</span><span>"</span><span>);
</span><span>if</span> (user.VerifyPassword(form.Get(<span>"</span><span>Password</span><span>"</span><span>), _encryptionService))
</span><span>throw</span> <span>new</span> Exception(<span>"</span><span>password</span><span>"</span>, <span>"</span><span>密码不正确。</span><span>"</span><span>);

FormsAuthentication.SetAuthCookie(user.Name, createPersistentCookie);
}
}</span>

复制代码

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

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<span>public</span> <span>class</span><span> DomainService
{
</span><span>private</span> <span>readonly</span><span> IUserRepository _userRepository;
</span><span>private</span> <span>readonly</span><span> IEncryptionService _encryptionService;
</span><span>public</span><span> DomainService(IUserRepository userRepository, IEncryptionService encryptionService)
{
</span><span>this</span>._userRepository =<span> userRepository;
</span><span>this</span>._encryptionService =<span> encryptionService;
}

</span><span>public</span> User Authenticate(<span>string</span> loginId, <span>string</span><span> password)
{
</span><span>var</span> user =<span> _userRepository.GetByName(loginId);
</span><span>if</span> (user == <span>null</span><span>)
</span><span>throw</span> <span>new</span> Exception(<span>"</span><span>loginId</span><span>"</span>, <span>"</span><span>账号不存在。</span><span>"</span><span>);
</span><span>if</span> (!<span>user.VerifyPassword(password, _encryptionService))
</span><span>throw</span> <span>new</span> Exception(<span>"</span><span>password</span><span>"</span>, <span>"</span><span>密码不正确。</span><span>"</span><span>);

</span><span>return</span><span> user;
}
}

</span><span>public</span> <span>class</span><span> UserController
{
</span><span>public</span> <span>void</span><span> Logon(FormCollection form)
{
</span><span>try</span><span> {
User user </span>= _domainService.Authenticate(form.Get(<span>"</span><span>LoginId</span><span>"</span>), form.Get(<span>"</span><span>Password</span><span>"</span><span>));
FormsAuthentication.SetAuthCookie(user.Name, createPersistentCookie);
}
</span><span>catch</span><span> (Exception) {
</span><span>throw</span><span>;
}
}
}</span>

复制代码

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

复制代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<span>public</span> <span>class</span><span> User
{
</span><span>public</span> <span>void</span> ChangePassword(<span>string</span> oldPwd, <span>string</span><span> newPwd, IEncryptionService encryptionService)
{
</span><span>if</span> (!<span>this</span><span>.VerifyPassword(oldPwd, encryptionService))
</span><span>throw</span> <span>new</span> Exception(<span>"</span><span>旧密码输入不正确。</span><span>"</span><span>);

</span><span>this</span>.Pasword =<span> encryptionService.Encrypt(newPwd);
}
}

</span><span>public</span> <span>class</span><span> UserController
{
</span><span>public</span> <span>void</span><span> ModifyPassword(FormCollection form)
{
</span><span>try</span><span> {
User user </span>=<span> _userRepository.GetByName(User.Identity.Name);
user.ChangePassword(form.Get(</span><span>"</span><span>oldPwd</span><span>"</span>), form.Get(<span>"</span><span>newPwd</span><span>"</span><span>), _encryptionService);
_userRepository.Update(user);
}
</span><span>catch</span><span> (Exception) {

</span><span>throw</span><span>;
}
}
}</span>

复制代码

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