【tornado】系列项目(一)之基于领域驱动模型架构设计的京东用户管理后台
本博文将一步步揭秘京东等大型网站的领域驱动模型,致力于让读者完全掌握这种网络架构中的“高富帅”。
一、预备知识:
1.接口:
python中并没有类似java等其它语言中的接口类型,但是python中有抽象类和抽象方法。如果一个抽象类有抽象方法,那么继承它的子类必须实现抽象类的所有方法,因此,我们基于python的抽象类和抽象方法实现接口功能。
示例代码:
from abc import ABCMeta from abc import abstractmethod #导入抽象方法 class Father(metaclass=ABCMeta):#创建抽象类 @abstractmethod def f1(self):pass @abstractmethod def f2(self):pass class F1(Father): def f1(self):pass def f2(self):pass def f3(self):pass obj=F1()
2.依赖注入:
依赖注入的作用是将一系列无关的类通过注入参数的方式实现关联,例如将类A的对象作为参数注入给类B,那么当调用类B的时候,类A会首先被实例化。
示例代码:
class Mapper: __mapper_ralation={} @staticmethod def regist(cls,value): Mapper.__mapper_ralation[cls]=value @staticmethod def exist(cls): if cls in Mapper.__mapper_ralation: return True else: return False @staticmethod def value(cls): return Mapper.__mapper_ralation[cls] class Mytype(type): def __call__(cls, *args, **kwargs): obj=cls.__new__(cls, *args, **kwargs) arg_list=list(args) if Mapper.exist(cls): value=Mapper.value(cls) arg_list.append(value) obj.__init__(*arg_list) return obj class Foo(metaclass=Mytype): def __init__(self,name): self.name=name def f1(self): print(self.name) class Bar(metaclass=Mytype): def __init__(self,name): self.name=name def f1(self): print(self.name) Mapper.regist(Foo,"666") Mapper.regist(Bar,"999") f=Foo() print(f.name) b=Bar() print(b.name)
注:原理:首先需要明确一切事物皆对象类也是对象,类是有Type创建的,当类实例化的时候,会调用type类的call方法,call方法会调用new方法,new方法调用init方法。
二、企业级应用设计
1.总体框架目录结构:
备注:
- Infrastructure:一些公共组件,例如md5加密,分页模块,session等。
- Model :关于数据库的逻辑处理模块
- Repository :数据访问层,包含数据库的增删改查
- Service :服务层,调用Model,包含带有规则的请求和返回
- Statics:静态文件目录
- UI层:业务处理
- Views:模板文件
- Application:tornado程序的起始文件
- Config:配置文件
- Mapper:依赖注入文件,负责整个框架不同类的依赖注入
2.首先我们从Moldel开始查看:
文件目录:
本文主要以用户管理为例进行介绍,因此我们来关注一下User.py文件:
代码结构:
下面对上述代码结构做一一介绍:
IUseRepository类:接口类,用于约束数据库访问类的方法
示例代码:
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
|
class
IUseRepository:
"""
用户信息仓库接口
"""
def
fetch_one_by_user_pwd(
self
, username, password):
"""
根据用户名密码获取模型对象
:param username: 主键ID
:param password: 主键ID
:return:
"""
def
fetch_one_by_email_pwd(
self
, email, password):
"""
根据邮箱密码获取模型对象
:param email: 主键ID
:param password: 主键ID
:return:
"""
def
update_last_login_by_nid(
self
,nid,current_date):
"""
根据ID更新最新登陆时间
:param nid:
:return:
"""
|
从上述代码可以看出,数据库访问类如果继承IUseRepository类,就必须实现其中的抽象方法。
接下来的三个类,VipType、UserType、User是与用户信息相关的类,是数据库需要保存的数据,我们希望存入数据库的数据格式为:nid 、username、email、last_login、user_type_id、vip_type_id,其中User类用于保存上述数据。因为user_type_id、vip_type_id存的是数字,即user_type_id、vip_type_id是外键,不能直接在前端进行展示,因此,我们创建了VipType、UserType类,用于根据id,获取对应的VIP级别和用户类型。
示例代码:
class User: """领域模型""" def __init__(self, nid, username, email, last_login, user_type, vip_type): self.nid = nid self.username = username self.email = email self.last_login = last_login self.user_type = user_type self.vip_type = vip_type
class UserType: USER_TYPE = ( {'nid': 1, 'caption': '用户'}, {'nid': 2, 'caption': '商户'}, {'nid': 3, 'caption': '管理员'}, ) def __init__(self, nid): self.nid = nid def get_caption(self): caption = None for item in UserType.USER_TYPE: if item['nid'] == self.nid: caption = item['caption'] break return caption caption = property(get_caption)
class VipType: VIP_TYPE = ( {'nid': 1, 'caption': '铜牌'}, {'nid': 2, 'caption': '银牌'}, {'nid': 3, 'caption': '金牌'}, {'nid': 4, 'caption': '铂金'}, ) def __init__(self, nid): self.nid = nid def get_caption(self): caption = None for item in VipType.VIP_TYPE: if item['nid'] == self.nid: caption = item['caption'] break return caption caption = property(get_caption)
注:VipType、UserType这两个类获取对应的caption均是通过类的普通特性访问,即类似字段方式访问。
接下来的类UserService是本py文件的重中之重,它负责调用对应的数据库访问类的方法,并被服务层service调用,具有承上启下的作用:
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class
UserService:
def
__init__(
self
, user_repository):
self
.userRepository
=
user_repository
def
check_login(
self
, username
=
None
, email
=
None
, password
=
None
):
if
username:
user_model
=
self
.userRepository.fetch_one_by_user_pwd(username, password)
else
:
user_model
=
self
.userRepository.fetch_one_by_email_pwd(email, password)
if
user_model:
current_date
=
datetime.datetime.now()
self
.userRepository.update_last_login_by_nid(user_model.nid, current_date)
return
user_model
|
这里,我们重点介绍一下上述代码:
初始化参数user_repository:通过依赖注入对应的数据库访问类的对象;
check_login:访问数据库的关键逻辑处理方法,根据用户是用户名+密码方式还是邮箱加密码的方式登录,然后调用对应的数据库处理方法,如果登陆成功,更新时间和最后登录时间,最后将User类的实例返回给调用它的服务层service。(详细见下文数据库处理类的方法)
我们先来看一下对应的数据库处理类中的一个方法:
1
|
示例代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
def
fetch_one_by_user_pwd(
self
, username, password):
ret
=
None
cursor
=
self
.db_conn.connect()
sql
=
"""select nid,username,email,last_login,vip,user_type from UserInfo where username=%s and password=%s"""
cursor.execute(sql, (username, password))
db_result
=
cursor.fetchone()
self
.db_conn.close()
if
db_result:
ret
=
User(nid
=
db_result[
'nid'
],
username
=
db_result[
'username'
],
email
=
db_result[
'email'
],
last_login
=
db_result[
'last_login'
],
user_type
=
UserType(nid
=
db_result[
'user_type'
]),
vip_type
=
VipType(nid
=
db_result[
'vip'
])
)
return
ret
|
这里我们使用pymysql进行数据库操作,以用户名+密码登陆为例,如果数据库有对应的用户名和密码,将查询结果放在User类中进行初始化,至此,ret中封装了用户的全部基本信息,将ret返回给上面的check_login方法,即对应上文中的返回值user_model,user_model返回给调用它的服务层service。
总结:Molde最终将封装了用户基本信息的User类的实例返回给服务层service。
3.接下来我们看一下服务层service:
service也是一个承上启下的作用,它调用Moldel文件对应的数据库业务协调方法,并被对应的UI层调用(本例中是UIadmin)。
目录结构:
同样的,我们只介绍User文件夹:它包含4个py文件:
- ModelView:用于用户前端展示的数据格式化,重点对user_type_id、vip_type_id增加对应的用户类型和VIP级别,即将外键数据对应的caption放在外键后面,作为增加的参数。
- Request:封装请求Moldel文件对应数据库协调类的参数
- Response:封装需要返回UI层的参数
- Sevice:核心服务处理文件
下面对上述目录做详细代码:
ModelView:
1
2
3
4
5
6
7
8
9
10
11
|
class
UserModelView:
def
__init__(
self
, nid, username, email, last_login, user_type_id, user_type_caption, vip_type_id, vip_type_caption):
self
.nid
=
nid
self
.username
=
username
self
.email
=
email
self
.last_login
=
last_login
self
.user_type
=
user_type_id
self
.user_type_caption
=
user_type_caption
self
.vip_type
=
vip_type_id
self
.vip_type_caption
=
vip_type_caption
|
注:对user_type_id、vip_type_id增加对应的用户类型和VIP级别,即将外键数据对应的caption放在外键后面,作为增加的参数。
Request:
1
2
3
4
5
6
|
class
UserRequest:
def
__init__(
self
, username, email, password):
self
.username
=
username
self
.email
=
email
self
.password
=
password
|
Response:
1
2
3
4
5
6
|
class
UserResponse:
def
__init__(
self
, status
=
True
, message
=
'', model_view
=
None
):
self
.status
=
status
# 是否登陆成功的状态
self
.message
=
message
#错误信息
self
.modelView
=
model_view
#登陆成功后的用户数据
|
UserService:
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
|
class
UserService:
def
__init__(
self
, model_user_service):
#通过依赖注入Moldel对应的数据库业务协调方法,此例中对应上文中的user_service
self
.modelUserService
=
model_user_service
def
check_login(
self
, user_request):
#核心服务层业务处理方法
response
=
UserResponse()
#实例化返回类
try
:
model
=
self
.modelUserService.check_login(user_request.username, user_request.email, user_request.password)
#接收上文中的用户基本信息,是User类的实例
if
not
model:
raise
Exception(
'用户名或密码错误'
)
else
:
#如果登陆成功,通过UserModelView类格式化返回前端的数据
model_view
=
UserModelView(nid
=
model[
'nid'
],
username
=
model[
'usename'
],
email
=
model[
'email'
],
last_login
=
model[
'last_login'
],
user_type_id
=
model[
'user_type'
].nid,
user_type_caption
=
model[
'user_type'
].caption,
vip_type_id
=
model[
'vip_type'
].nid,
vip_type_caption
=
model[
'vip_type'
].caption,)
response.modelView
=
model_view
#定义返回UI层的用户信息
except
Exception as e:
response.status
=
False
response.message
=
str
(e)
|
总结:至此,Service返回给Ui层的数据是是否登陆成功的状态status、错误信息、已经格式化的用户基本信息。
4.UI层
UI层主要负责业务处理完成后与前端的交互,它是服务层Service的调用者:
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class
Login(AdminRequestHandler):
def
get(
self
,
*
args,
*
*
kwargs):
is_login
=
self
.session[
"is_login"
]
current_user
=
""
if
is_login:
current_user
=
self
.session[
"user_info"
].username
self
.render(
'Account/Login.html'
,current_user
=
current_user)
def
post(
self
,
*
args,
*
*
kwargs):
user_request
=
[]
#获取用户输入的用户名邮箱密码
user_request.append(
self
.get_argument(
'name'
,
None
))
user_request.append(
self
.get_argument(
'email'
,
None
))
user_request.append(
self
.get_argument(
'pwd'
,
None
))
checkcode
=
self
.get_argument(
"checkcode"
,
None
)
Mapper.mapper(
*
user_request)
obj
=
UserService.check_login()
self
.session[
"is_login"
]
=
True
self
.session[
"user_info"
]
=
obj.modelView
self
.write(
"已登陆"
)
|
总结以上就是基于领域驱动模型的用户管理后台,包含数据库操作文件,数据库业务处理文件,服务端文件,UI层文件。
本博文将一步步揭秘京东等大型网站的领域驱动模型,致力于让读者完全掌握这种网络架构中的“高富帅”。
一、预备知识:
1.接口:
python中并没有类似java等其它语言中的接口类型,但是python中有抽象类和抽象方法。如果一个抽象类有抽象方法,那么继承它的子类必须实现抽象类的所有方法,因此,我们基于python的抽象类和抽象方法实现接口功能。
示例代码:
from abc import ABCMeta from abc import abstractmethod #导入抽象方法 class Father(metaclass=ABCMeta):#创建抽象类 @abstractmethod def f1(self):pass @abstractmethod def f2(self):pass class F1(Father): def f1(self):pass def f2(self):pass def f3(self):pass obj=F1()
2.依赖注入:
依赖注入的作用是将一系列无关的类通过注入参数的方式实现关联,例如将类A的对象作为参数注入给类B,那么当调用类B的时候,类A会首先被实例化。
示例代码:
class Mapper: __mapper_ralation={} @staticmethod def regist(cls,value): Mapper.__mapper_ralation[cls]=value @staticmethod def exist(cls): if cls in Mapper.__mapper_ralation: return True else: return False @staticmethod def value(cls): return Mapper.__mapper_ralation[cls] class Mytype(type): def __call__(cls, *args, **kwargs): obj=cls.__new__(cls, *args, **kwargs) arg_list=list(args) if Mapper.exist(cls): value=Mapper.value(cls) arg_list.append(value) obj.__init__(*arg_list) return obj class Foo(metaclass=Mytype): def __init__(self,name): self.name=name def f1(self): print(self.name) class Bar(metaclass=Mytype): def __init__(self,name): self.name=name def f1(self): print(self.name) Mapper.regist(Foo,"666") Mapper.regist(Bar,"999") f=Foo() print(f.name) b=Bar() print(b.name)
注:原理:首先需要明确一切事物皆对象类也是对象,类是有Type创建的,当类实例化的时候,会调用type类的call方法,call方法会调用new方法,new方法调用init方法。
二、企业级应用设计
1.总体框架目录结构:
备注:
- Infrastructure:一些公共组件,例如md5加密,分页模块,session等。
- Model :关于数据库的逻辑处理模块
- Repository :数据访问层,包含数据库的增删改查
- Service :服务层,调用Model,包含带有规则的请求和返回
- Statics:静态文件目录
- UI层:业务处理
- Views:模板文件
- Application:tornado程序的起始文件
- Config:配置文件
- Mapper:依赖注入文件,负责整个框架不同类的依赖注入
2.首先我们从Moldel开始查看:
文件目录:
本文主要以用户管理为例进行介绍,因此我们来关注一下User.py文件:
代码结构:
下面对上述代码结构做一一介绍:
IUseRepository类:接口类,用于约束数据库访问类的方法
示例代码:
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
|
class
IUseRepository:
"""
用户信息仓库接口
"""
def
fetch_one_by_user_pwd(
self
, username, password):
"""
根据用户名密码获取模型对象
:param username: 主键ID
:param password: 主键ID
:return:
"""
def
fetch_one_by_email_pwd(
self
, email, password):
"""
根据邮箱密码获取模型对象
:param email: 主键ID
:param password: 主键ID
:return:
"""
def
update_last_login_by_nid(
self
,nid,current_date):
"""
根据ID更新最新登陆时间
:param nid:
:return:
"""
|
从上述代码可以看出,数据库访问类如果继承IUseRepository类,就必须实现其中的抽象方法。
接下来的三个类,VipType、UserType、User是与用户信息相关的类,是数据库需要保存的数据,我们希望存入数据库的数据格式为:nid 、username、email、last_login、user_type_id、vip_type_id,其中User类用于保存上述数据。因为user_type_id、vip_type_id存的是数字,即user_type_id、vip_type_id是外键,不能直接在前端进行展示,因此,我们创建了VipType、UserType类,用于根据id,获取对应的VIP级别和用户类型。
示例代码:
class User: """领域模型""" def __init__(self, nid, username, email, last_login, user_type, vip_type): self.nid = nid self.username = username self.email = email self.last_login = last_login self.user_type = user_type self.vip_type = vip_type
class UserType: USER_TYPE = ( {'nid': 1, 'caption': '用户'}, {'nid': 2, 'caption': '商户'}, {'nid': 3, 'caption': '管理员'}, ) def __init__(self, nid): self.nid = nid def get_caption(self): caption = None for item in UserType.USER_TYPE: if item['nid'] == self.nid: caption = item['caption'] break return caption caption = property(get_caption)
class VipType: VIP_TYPE = ( {'nid': 1, 'caption': '铜牌'}, {'nid': 2, 'caption': '银牌'}, {'nid': 3, 'caption': '金牌'}, {'nid': 4, 'caption': '铂金'}, ) def __init__(self, nid): self.nid = nid def get_caption(self): caption = None for item in VipType.VIP_TYPE: if item['nid'] == self.nid: caption = item['caption'] break return caption caption = property(get_caption)
注:VipType、UserType这两个类获取对应的caption均是通过类的普通特性访问,即类似字段方式访问。
接下来的类UserService是本py文件的重中之重,它负责调用对应的数据库访问类的方法,并被服务层service调用,具有承上启下的作用:
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class
UserService:
def
__init__(
self
, user_repository):
self
.userRepository
=
user_repository
def
check_login(
self
, username
=
None
, email
=
None
, password
=
None
):
if
username:
user_model
=
self
.userRepository.fetch_one_by_user_pwd(username, password)
else
:
user_model
=
self
.userRepository.fetch_one_by_email_pwd(email, password)
if
user_model:
current_date
=
datetime.datetime.now()
self
.userRepository.update_last_login_by_nid(user_model.nid, current_date)
return
user_model
|
这里,我们重点介绍一下上述代码:
初始化参数user_repository:通过依赖注入对应的数据库访问类的对象;
check_login:访问数据库的关键逻辑处理方法,根据用户是用户名+密码方式还是邮箱加密码的方式登录,然后调用对应的数据库处理方法,如果登陆成功,更新时间和最后登录时间,最后将User类的实例返回给调用它的服务层service。(详细见下文数据库处理类的方法)
我们先来看一下对应的数据库处理类中的一个方法:
1
|
示例代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
def
fetch_one_by_user_pwd(
self
, username, password):
ret
=
None
cursor
=
self
.db_conn.connect()
sql
=
"""select nid,username,email,last_login,vip,user_type from UserInfo where username=%s and password=%s"""
cursor.execute(sql, (username, password))
db_result
=
cursor.fetchone()
self
.db_conn.close()
if
db_result:
ret
=
User(nid
=
db_result[
'nid'
],
username
=
db_result[
'username'
],
email
=
db_result[
'email'
],
last_login
=
db_result[
'last_login'
],
user_type
=
UserType(nid
=
db_result[
'user_type'
]),
vip_type
=
VipType(nid
=
db_result[
'vip'
])
)
return
ret
|
这里我们使用pymysql进行数据库操作,以用户名+密码登陆为例,如果数据库有对应的用户名和密码,将查询结果放在User类中进行初始化,至此,ret中封装了用户的全部基本信息,将ret返回给上面的check_login方法,即对应上文中的返回值user_model,user_model返回给调用它的服务层service。
总结:Molde最终将封装了用户基本信息的User类的实例返回给服务层service。
3.接下来我们看一下服务层service:
service也是一个承上启下的作用,它调用Moldel文件对应的数据库业务协调方法,并被对应的UI层调用(本例中是UIadmin)。
目录结构:
同样的,我们只介绍User文件夹:它包含4个py文件:
- ModelView:用于用户前端展示的数据格式化,重点对user_type_id、vip_type_id增加对应的用户类型和VIP级别,即将外键数据对应的caption放在外键后面,作为增加的参数。
- Request:封装请求Moldel文件对应数据库协调类的参数
- Response:封装需要返回UI层的参数
- Sevice:核心服务处理文件
下面对上述目录做详细代码:
ModelView:
1
2
3
4
5
6
7
8
9
10
11
|
class
UserModelView:
def
__init__(
self
, nid, username, email, last_login, user_type_id, user_type_caption, vip_type_id, vip_type_caption):
self
.nid
=
nid
self
.username
=
username
self
.email
=
email
self
.last_login
=
last_login
self
.user_type
=
user_type_id
self
.user_type_caption
=
user_type_caption
self
.vip_type
=
vip_type_id
self
.vip_type_caption
=
vip_type_caption
|
注:对user_type_id、vip_type_id增加对应的用户类型和VIP级别,即将外键数据对应的caption放在外键后面,作为增加的参数。
Request:
1
2
3
4
5
6
|
class
UserRequest:
def
__init__(
self
, username, email, password):
self
.username
=
username
self
.email
=
email
self
.password
=
password
|
Response:
1
2
3
4
5
6
|
class
UserResponse:
def
__init__(
self
, status
=
True
, message
=
'', model_view
=
None
):
self
.status
=
status
# 是否登陆成功的状态
self
.message
=
message
#错误信息
self
.modelView
=
model_view
#登陆成功后的用户数据
|
UserService:
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
|
class
UserService:
def
__init__(
self
, model_user_service):
#通过依赖注入Moldel对应的数据库业务协调方法,此例中对应上文中的user_service
self
.modelUserService
=
model_user_service
def
check_login(
self
, user_request):
#核心服务层业务处理方法
response
=
UserResponse()
#实例化返回类
try
:
model
=
self
.modelUserService.check_login(user_request.username, user_request.email, user_request.password)
#接收上文中的用户基本信息,是User类的实例
if
not
model:
raise
Exception(
'用户名或密码错误'
)
else
:
#如果登陆成功,通过UserModelView类格式化返回前端的数据
model_view
=
UserModelView(nid
=
model[
'nid'
],
username
=
model[
'usename'
],
email
=
model[
'email'
],
last_login
=
model[
'last_login'
],
user_type_id
=
model[
'user_type'
].nid,
user_type_caption
=
model[
'user_type'
].caption,
vip_type_id
=
model[
'vip_type'
].nid,
vip_type_caption
=
model[
'vip_type'
].caption,)
response.modelView
=
model_view
#定义返回UI层的用户信息
except
Exception as e:
response.status
=
False
response.message
=
str
(e)
|
总结:至此,Service返回给Ui层的数据是是否登陆成功的状态status、错误信息、已经格式化的用户基本信息。
4.UI层
UI层主要负责业务处理完成后与前端的交互,它是服务层Service的调用者:
示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class
Login(AdminRequestHandler):
def
get(
self
,
*
args,
*
*
kwargs):
is_login
=
self
.session[
"is_login"
]
current_user
=
""
if
is_login:
current_user
=
self
.session[
"user_info"
].username
self
.render(
'Account/Login.html'
,current_user
=
current_user)
def
post(
self
,
*
args,
*
*
kwargs):
user_request
=
[]
#获取用户输入的用户名邮箱密码
user_request.append(
self
.get_argument(
'name'
,
None
))
user_request.append(
self
.get_argument(
'email'
,
None
))
user_request.append(
self
.get_argument(
'pwd'
,
None
))
checkcode
=
self
.get_argument(
"checkcode"
,
None
)
Mapper.mapper(
*
user_request)
obj
=
UserService.check_login()
self
.session[
"is_login"
]
=
True
self
.session[
"user_info"
]
=
obj.modelView
self
.write(
"已登陆"
)
|
总结以上就是基于领域驱动模型的用户管理后台,包含数据库操作文件,数据库业务处理文件,服务端文件,UI层文件。