三层架构 领域驱动模型

数据访问层 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
DaoFactory.py
#!/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
PyMySQL.py
#!/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
PyORM.py
#!/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))
SqlAhchemyOrm.py 创建表

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
AccountService.py

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()
app.py启动文件

配置文件

#!/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
启动程序依赖程序配置单独的DAL访问

 

 

基于领域驱动模型设计用户管理后台 

一、预备知识:

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
User类
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)
UserType类
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类

 

注: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("已登陆")

 

 

 

 

 

 

posted @ 2016-08-31 11:48  众里寻,阑珊处  阅读(255)  评论(0编辑  收藏  举报
返回顶部