【Python之路】特别篇--基于领域驱动模型架构设计的京东用户管理后台
一、预备知识:
1、接口:
- URL形式
- 数据类型 (Python中不存在)
a.类中的方法可以写任意个,想要对类中的方法进行约束就可以使用接口;
b.定义一个接口,接口中定义一个方法f1;
c.只要继承(实现)了接口,那么类就会受约束,该类必须要有f1方法!
d.接口只用来做约束,不需要写具体功能。
由于python中无接口类型,但是可以人为构造,抛出异常!
raise Exception('子类中必须实现该方法')
class IOrderRepository: def fetch_one_by(self,nid): ''' 获取单条数据的方法,所有继承当前类的类必须实现(有)该方法 :param nid: :return: ''' raise Exception('子类中必须实现该方法') class OrderRepository(IOrderRepository): def fetch_one_by(self,nid): print('...获取数据') obj = OrderRepository obj.fetch_one_by(1)
类:类中的方法可以写功能,也可以不写功能,
抽象类:类和接口的集合,抽象类中可以有抽象方法和普通方法,
子类继承抽象类时,必须实现抽象方法!
抽象类即做了约束,还有具体功能
python提供了模块abc,用于实现抽象类和抽象方法的功能
import abc class Foo(metaclass=abc.ABCMeta): def f1(self): print(123) def f2(self): print(456) @abc.abstractmethod def f3(self): ''' :return: ''' class Bar(Foo): def f1(self): pass def f3(self): pass b = Bar() b.f1()
耦合:
不同模块之间互连程度的度量(耦合性也叫块间联系)。指软件系统中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。
class SqlHelper: def fetch_one(self): pass def fetch_all(self): pass 方式一: class UserInfo: def __init__(self): pass def login(self): s = SqlHelper() s.fetch_one() def logout(self): s = SqlHelper() s.fetch_one() def register(self): s = SqlHelper() s.fetch_one() ************************************************* 方式二: class UserInfo: def __init__(self,helper): self.s = SqlHelper() def login(self): self.s.fetch_one() def logout(self): self.s.fetch_one() def register(self): self.s.fetch_one() obj = UserInfo() obj.login()
为了降低耦合,一般可以这样做:
class SqlHelper: def fetch_one(self): pass def fetch_all(self): pass class UserInfo: def __init__(self,helper): self.s = helper def login(self): self.s.fetch_one() h = SqlHelper() obj = UserInfo(h) obj.login()
如果SqlHelper和其他类,如Foo类有依赖关系那么又要解耦了:
class Foo: def f1(self): pass class SqlHelper: def __init__(self,foo): self.f = foo def fetch_one(self): self.f.f1() class UserInfo: def __init__(self,helper): self.s = helper def login(self): self.s.fetch_one() f = Foo() s = SqlHelper(f) u = UserInfo(s) u.login()
最终发现代码,嵌套太多层了。
为了解决这种问题,可以使用依赖注入!
提案:实例化的时候,不需要自己传参数
面向对象知识点:
class Foo: def __init__(self,name): self.name = name def f1(self): print(self.name)
# 解释器解释的时候:
1.遇到 class Foo , 执行type 的__init__方法
2.Typr的init方法里做什么呢? => 创建了类
obj = Foo(123)
3.执行Type的__call__方法,
-> 执行Foo类的__new__方法
-> 执行Foo类的__init__方法
如何在执行__init__方法前,传递参数呢?
可以选择去重写Type的__call__方法
你写的任何类都是Type的对象,
class MyType(type): def __call__(self, *args, **kwargs): print(self) class Foo(metaclass=MyType): # 指定由MyType生成 def __init__(self,name): self.name = name obj = Foo(123) # <class '__main__.Foo'>
最终执行流程:
class MyType(type): def __call__(cls, *args, **kwargs): obj = cls.__new__(cls,*args, **kwargs) # 创建对象 obj.__init__(*args, **kwargs) # 初始化 return obj class Foo(metaclass=MyType): def __init__(self,name): self.name = name def f1(self): print(self.name) obj = Foo(123)
为了在调用时不传参数,设法在obj.__init__()执行前时,传入参数
class MyType(type): def __call__(cls, *args, **kwargs): obj = cls.__new__(cls,*args, **kwargs) args_list = list(args) args_list.append(123) obj.__init__(*args_list, **kwargs) return obj obj = Foo()
为了不让参数固定,做进一步修改,利用第三方来传递参数!
依赖注入:
class Mapper: __mapper_relation = {} @staticmethod def register(cls,value): Mapper.__mapper_relation[cls] = value @staticmethod def exist(cls): if cls in Mapper.__mapper_relation: return True else: return False @staticmethod def value(cls): return Mapper.__mapper_relation[cls] class MyType(type): def __call__(cls, *args, **kwargs): obj = cls.__new__(cls,*args, **kwargs) args_list = list(args) if Mapper.exist(cls): value = Mapper.value(cls) args_list.append(value) obj.__init__(*args_list, **kwargs) return obj class Foo(metaclass=MyType): def __init__(self,name): self.name = name def f1(self): print(self.name) Mapper.register(Foo,'666999') obj = Foo() obj.f1()
有了依赖注入后,之前嵌套过多的demo可以修改为:
#!/usr/bin/env python # -*-coding:utf-8 -*- class Mapper: __mapper_relation = {} @staticmethod def register(cls,value): Mapper.__mapper_relation[cls] = value @staticmethod def exist(cls): if cls in Mapper.__mapper_relation: return True else: return False @staticmethod def value(cls): return Mapper.__mapper_relation[cls] class MyType(type): def __call__(cls, *args, **kwargs): obj = cls.__new__(cls,*args, **kwargs) args_list = list(args) if Mapper.exist(cls): value = Mapper.value(cls) args_list.append(value) obj.__init__(*args_list, **kwargs) return obj class Test(): def __int__(self): pass class Foo(metaclass=MyType): def __init__(self,t): self.t = t def f1(self): print(self.name) class Bar(metaclass=MyType): def __init__(self,f): self.f = f def f1(self): print(self.name) Mapper.register(Foo,Test()) Mapper.register(Bar,Foo()) b = Bar() print(b) print(b.f) print(b.f.t) # <__main__.Bar object at 0x0000002D504C4550> # <__main__.Foo object at 0x0000002D504C4518> # <__main__.Test object at 0x0000002D504C4470>
1.总体框架目录结构:
备注:
- Infrastructure:一些公共组件,例如md5加密,分页模块,session等。
- Model :关于数据库的逻辑处理模块
- Repository :数据访问层,包含数据库的增删改查
- Service :服务层,调用Model,包含带有规则的请求和返回
- Statics:静态文件目录
- UI层:业务处理
- Views:模板文件
- Application:tornado程序的起始文件
- Config:配置文件
- Mapper:依赖注入文件,负责整个框架不同类的依赖注入
2.首先我们从Moldel开始查看:
本文主要以用户管理为例进行介绍,因此我们来关注一下User.py文件:
代码结构:
下面对上述代码结构做一一介绍:
IUseRepository类:接口类,用于约束数据库访问类的方法
示例代码:
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调用,具有承上启下的作用:
示例代码:
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。(详细见下文数据库处理类的方法)
我们先来看一下对应的数据库处理类中的一个方法:
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:
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:
class UserRequest: def __init__(self, username, email, password): self.username = username self.email = email self.password = password
Response:
class UserResponse: def __init__(self, status=True, message='', model_view=None): self.status = status # 是否登陆成功的状态 self.message = message #错误信息 self.modelView = model_view #登陆成功后的用户数据
UserService:
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的调用者:
示例代码:
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("已登陆")