【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
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
0.001291036605835