领域驱动最佳实践--用代码来告诉你来如何进行领域驱动设计

做一个租户系统下的权限服务,接管用户的认证和授权,我们取名该服务为go-easy-login

     本文实质是领域驱动设计之实战权限系统微服务的进一步总结和改进,学习领域驱动设计本身是循序渐进的过程,培养的是领域的概念和面向对象编程思想,而过去以及现在,包括未来,多数人只是披着面向对象的皮,干着面向过程,面向数据库的糙活,详情请看为什么我们需要领域驱动设计,如果你接触过领域驱动设计,但是苦于不知道如何动手,概念虽懂但不知如何实践,本篇将能为你打开实践领域驱动设计的大门,如果你未曾了解过领域驱动设计,这篇同样也是入门领域驱动设计的最好文章之一,带你感受领域驱动的非凡魅力。

项目结构

     代码先行,先展示一下代码的目录结构以及相应的文件,大家可以先YY对应的作用,然后带着疑问去阅读。

login base encrypt.go token.go repoImpl.go domain service loginService.go loginService_test.go loginUser.go loginUser_test.go mocks EncryptHelper.go LoginUserRepo.go 如何脱离技术细节

     领域驱动设计更加强调业务逻辑以及相应对建立起的领域(模型),不应该出现任何
技术细节,即数据库,缓存等。面向对象是对于外在客观事物的一个模拟和反映,一
个User类应该具备eat,drink,play,happy等能力,一个User不可能具备连接数据库的能力,出现依赖任何技术细节是违反面向对象编程的。那么问题来了,道理我都懂,如何去做到,如果我们在一个项目中,什么技术都不用到的话,是不是就达到我们的目的了?(读者疑问:WTF,怎么可能?)

     新建一个项目,什么第三方包都不依赖,根据我们想做的功能,做一个租户系统下的权限服务,接管用户的认证和授权,我们新建一个LoginUserE来代表登陆用户,DoVerify执行认证过程,同时我们希望具备帐密登陆的时候,由调用系统决定加密方式,这就意味着LoginUserE这个领域需要可以根据EncryptWay来获取EncryptHelper,我们先新建一个loginUser.go

package domain type LoginUserE struct { Username string IsLock bool UniqueCode string Mobile string canLoginFunc func() bool EncryptWay } func (user *LoginUserE) CanLogin() bool { var can bool if user.canLoginFunc != nil { can = user.canLoginFunc() } else { can = !user.IsLock } return can } func (user *LoginUserE) DoVerify(sourceCode string, encryptedCode string) (bool, error) { if !user.CanLogin() { return false, errors.New("can not login") } match := user.EncryptHelper().Match(sourceCode, encryptedCode) return match, nil }

     这里的问题在于EncryptHelper()这个方法,我们知道加密方法,就拿MD5来说,必须需要依赖到其他包,而loginUser.go我们是不希望依赖到任何第三方包的,这似乎进入了一种矛盾。Alistair Cockburn 提出的六边形架构,在于domain处于核心内部,其他的依赖通过接口进行交流,再换句话说就是domain层定义接口,基础设施层(技术层)实现接口,我们定义EncryptHelper接口

type EncryptHelper interface { Encrypt(password string) string Decrypt(password string) string Match(source, encryptedString string) bool }

     然后在base基础设施层新建encrypt.go实现该类

type MD5Way struct{} func (md5 MD5Way) Match(source, encryptedString string) bool { return md5.Encrypt(source) == encryptedString } func (MD5Way) Encrypt(password string) string { data := []byte(password) md5Bytes := md5.Sum(data) return string(md5Bytes[:]) } func (MD5Way) Decrypt(password string) string { panic("not support") }

      问题还没能够解决,base层的具体实现类,如何让domain层中不直接依赖的同时,又能使用呢?最好的方法实际上是依赖注入,但是引入依赖注入又陷入另一种悖论--不依赖任何技术细节,依赖注入也可以归纳为技术的一种,下文再继续探讨这点,且看我如何不用依赖注入实现。在loginUser.go我们新建一个全局变量var EncryptMap = make(map[EncryptWay]EncryptHelper)

var EncryptMap = make(map[EncryptWay]EncryptHelper) func (encryptWay EncryptWay) EncryptHelper() EncryptHelper { if helper, ok := EncryptMap[encryptWay]; ok { return helper } else { panic("can not find helper") } } func AddEncryptHelper(encryptWay EncryptWay, helper EncryptHelper) { EncryptMap[encryptWay] = helper }

     核心层之外的类,通过AddEncryptHelper注册相应的EncryptHelper,这种设计初看尚可,但是一旦项目中具备更多个领域,再采用这种方法则会导致代码的维护成本的提高,依赖注入实则是屏蔽构建具体实现类的过程,要不要在domain层引入,因人而异,因项目而异。若有更好的方法,欢迎在评论区中提出。

领域服务

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyggdz.html