【tornado】系列專案(一)之基於領域驅動模型架構設計的京東使用者管理後臺
【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_typeUser類
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 |