三层架构 领域驱动模型
数据访问层 DAL/DAO - 访问数据库 标准:数据库表》类 mysql_sql.py class UserInfo: def add(..) def remove(..) def fetch(..) def fetchall(..) class UserInfo: def add(..) def remove(..) def fetch(..) def fetchall(..) mysql_orm.py class UserInfo: def add(..) def remove(..) def fetch(..) def fetchall(..) class UserInfo: def add(..) def remove(..) def fetch(..) def fetchall(..) oracle_sql.py class UserInfo: def add(..) def remove(..) def fetch(..) def fetchall(..) class UserInfo: def add(..) def remove(..) def fetch(..) def fetchall(..) 业务处理层 BLL/Sevice factory读取配置文件 mysql_sql obj = UserInfo() obj.add(...) - 检查用户是否已经存在 - 存在,已经 - 注册 account: 用户表操作 验证 订单查询 account: 用户表操作 验证 订单查询 UI表示层 UI/UI M V C controller
测试:
dao service web model views controllers app.py
程序说明:
1 Mapper.static_mapper() #模块的方法执行。DaoFacotory.NewsDaoFactory.set_dao(PyMySQL.NewsDao()) ,定义了单独的表访问数据的方式
2 看一DaoFactory 调用静态方法 形似静态字段调用 设置新的查询方式。默认是orm现在改成pymysql
class NewsDaoFactory: __dao = PyORM.NewsDao() @staticmethod def set_dao(dao): NewsDaoFactory.__dao = dao @staticmethod def get_dao(): return NewsDaoFactory.__dao
3 看看service怎么调的呢?
def login(username, password): user_info_dao = UserInfoDaoFactory.get_dao() # 现获取 ret = user_info_dao.fetch_single_by_user_pwd(username, password) if ret: pass else: pass
流程都知道了吧
DAO
#!/usr/bin/env python # -*- coding:utf-8 -*- from Dao import PyORM class UserInfoDaoFactory: __dao = PyORM.UserInfoDao() @staticmethod def set_dao(dao): UserInfoDaoFactory.__dao = dao @staticmethod def get_dao(): return UserInfoDaoFactory.__dao class NewsDaoFactory: __dao = PyORM.NewsDao() @staticmethod def set_dao(dao): NewsDaoFactory.__dao = dao @staticmethod def get_dao(): return NewsDaoFactory.__dao
#!/usr/bin/env python # -*- coding:utf-8 -*- import pymysql import Config class DbConnection: def __init__(self): self.__conn_dict = Config.PY_MYSQL_CONN_DICT self.conn = None self.cursor = None def connect(self, cursor=pymysql.cursors.DictCursor): self.conn = pymysql.connect(**self.__conn_dict) self.cursor = self.conn.cursor(cursor=cursor) return self.cursor def close(self): self.conn.commit() self.cursor.close() self.conn.close() class UserInfoDao: def __init__(self): self.db_conn = DbConnection() def fetch_single_by_nid(self, nid): cursor = self.db_conn.connect() sql = "select nid,username from UserInfo where nid = %s" cursor.execute(sql, (nid,)) result = cursor.fetchone() self.db_conn.close() return result def fetch_single_by_user_pwd(self, user, pwd): cursor = self.db_conn.connect() sql = "select nid,username from UserInfo where username = %s and password = %s" cursor.execute(sql, (user,pwd)) result = cursor.fetchall() self.db_conn.close() return result def fetch_all(self): cursor = self.db_conn.connect() sql = "select nid,username from UserInfo" cursor.execute(sql) result = cursor.fetchall() self.db_conn.close() return result class NewsDao: def __init__(self): self.db_conn = DbConnection() def fetch_single(self): pass def fetch_all(self): pass def fetch_all_join_type(self): cursor = self.db_conn.connect() sql = "select nid,username from News LEFT JOIN NewsType ON News.type_id = NewsType.nid" cursor.execute(sql) result = cursor.fetchall() self.db_conn.close() return result
#!/usr/bin/env python # -*- coding:utf-8 -*- import Config from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine from Dao import SqlAchemyOrm as Orm class DbConnection: def __init__(self): self.__conn_str = Config.SQL_ALCHEMY_CONN_STR self.__max_overflow = Config.SQL_ALCHEMY_MAX_OVERFLOW self.conn = None def connect(self): engine = create_engine(self.__conn_str, max_overflow=self.__max_overflow) session = sessionmaker(bind=engine) self.conn = session() return self.conn def close(self): self.conn.commit() self.conn.close() class NewsDao: def __init__(self): self.db_conn = DbConnection() def fetch_single_by_nid(self, nid): conn = self.db_conn.connect() result = conn.query(Orm.News).filter(Orm.News.nid == nid).first() self.db_conn.close() return result def fetch_all(self): conn = self.db_conn.connect() result = conn.query(Orm.News).all() self.db_conn.close() return result def fetch_all_join_type(self): conn = self.db_conn.connect() result = conn.query(Orm.News).join(Orm.NewsType, Orm.News.news_type_id == Orm.NewsType.nid, isouter=True).all() self.db_conn.close() return result class UserInfoDao: def __init__(self): self.db_conn = DbConnection() def fetch_single_by_nid(self, nid): conn = self.db_conn.connect() result = conn.query(Orm.UserInfo).filter(Orm.UserInfo.nid == nid).first() self.db_conn.close() return result def fetch_single_by_user_pwd(self, user, pwd): conn = self.db_conn.connect() result = conn.query(Orm.UserInfo).filter(Orm.UserInfo.username == user,Orm.UserInfo.password == pwd ).first() self.db_conn.close() return result def fetch_all(self): conn = self.db_conn.connect() result = conn.query(Orm.UserInfo).all() self.db_conn.close() return result
#!/usr/bin/env python # -*- coding:utf-8 -*- from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column from sqlalchemy import Integer, String, TIMESTAMP from sqlalchemy import ForeignKey, UniqueConstraint, Index from sqlalchemy.orm import relationship Base = declarative_base() class SendMsg(Base): __tablename__ = 'sendmsg' nid = Column(Integer, primary_key=True, autoincrement=True) code = Column(String(6)) email = Column(String(32), index=True) times = Column(Integer, default=0) ctime = Column(TIMESTAMP) class UserInfo(Base): __tablename__ = 'userinfo' nid = Column(Integer, primary_key=True, autoincrement=True) username = Column(String(32)) password = Column(String(32)) email = Column(String(32)) ctime = Column(TIMESTAMP) __table_args__ = ( Index('ix_user_pwd', 'username', 'password'), Index('ix_email_pwd', 'email', 'password'), ) def __repr__(self): return "%s-%s-%s" %(self.nid, self.username, self.email) class NewsType(Base): __tablename__ = 'newstype' nid = Column(Integer, primary_key=True, autoincrement=True) caption = Column(String(32)) class News(Base): __tablename__ = 'news' nid = Column(Integer, primary_key=True, autoincrement=True) user_info_id = Column(Integer, ForeignKey("userinfo.nid")) news_type_id = Column(Integer, ForeignKey("newstype.nid")) ctime = Column(TIMESTAMP) title = Column(String(32)) url = Column(String(128)) content = Column(String(150)) class Favor(Base): __tablename__ = 'favor' nid = Column(Integer, primary_key=True, autoincrement=True) user_info_id = Column(Integer, ForeignKey("userinfo.nid")) news_id = Column(Integer, ForeignKey("news.nid")) ctime = Column(TIMESTAMP) __table_args__ = ( UniqueConstraint('user_info_id', 'news_id', name='uix_uid_nid'), ) class Comment(Base): __tablename__ = 'comment' nid = Column(Integer, primary_key=True, autoincrement=True) user_info_id = Column(Integer, ForeignKey("userinfo.nid")) news_id = Column(Integer, ForeignKey("news.nid")) reply_id = Column(Integer, ForeignKey("comment.nid"), nullable=True, default=None) up = Column(Integer) down = Column(Integer) ctime = Column(TIMESTAMP) device = Column(String(32)) content = Column(String(150))
service
#!/usr/bin/env python # -*- coding:utf-8 -*- from Dao.DaoFacotory import UserInfoDaoFactory from Dao.DaoFacotory import NewsDaoFactory def login(username, password): user_info_dao = UserInfoDaoFactory.get_dao() ret = user_info_dao.fetch_single_by_user_pwd(username, password) if ret: pass else: pass def logout(): pass def register(): pass
NewsService.py 暂时未写
UTILS
前面提到的各种组件
WEBS
暂时未写
启动主程序
#!/usr/bin/env python # -*- coding:utf-8 -*- import Mapper import tornado.ioloop import tornado.web settings = { 'template_path': 'Views', 'static_path': 'Statics', 'static_url_prefix': '/statics/', } application = tornado.web.Application([ #(r"/index", home.IndexHandler), ], **settings) if __name__ == "__main__": Mapper.static_mapper() application.listen(8888) tornado.ioloop.IOLoop.instance().start()
配置文件
#!/usr/bin/env python # -*- coding:utf-8 -*- PY_MYSQL_CONN_DICT = { "host": '127.0.0.1', "port": 3306, "user": 'root', "passwd": '123', "db": 't1' } SQL_ALCHEMY_CONN_STR = "mysql+pymysql://root:123@127.0.0.1:3306/t1" SQL_ALCHEMY_MAX_OVERFLOW = 1
启动程序依赖程序配置单独的DAL访问
#!/usr/bin/env python # -*- coding:utf-8 -*- from Dao import PyORM from Dao import PyMySQL from Dao import DaoFacotory def static_mapper(): DaoFacotory.NewsDaoFactory.set_dao(PyMySQL.NewsDao()) def dynamic_mapper(handler): pass
基于领域驱动模型设计用户管理后台
一、预备知识:
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( "已登陆" ) |