日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

導讀:微服務遵循領域驅動設計(DDD),與開發平臺無關。Python/ target=_blank class=infotextkey>Python 微服務也不例外。Python3 的面向對象特性使得按照 DDD 對服務進行建模變得更加容易。

本文字數:12786,閱讀時長大約: 15分鐘

微服務遵循領域驅動設計(DDD),與開發平臺無關。Python 微服務也不例外。Python3 的面向對象特性使得按照 DDD 對服務進行建模變得更加容易。本系列的第 10 部分演示了如何將用戶管理系統的查找服務作為 Python 微服務部署在 Kube.NETes 上。

微服務架構的強大之處在于它的多語言性。企業將其功能分解為一組微服務,每個團隊自由選擇一個平臺。

我們的用戶管理系統已經分解為四個微服務,分別是添加、查找、搜索和日志服務。添加服務在 JAVA 平臺上開發并部署在 Kubernetes 集群上,以實現彈性和可擴展性。這并不意味著其余的服務也要使用 Java 開發,我們可以自由選擇適合個人服務的平臺。

讓我們選擇 Python 作為開發查找服務的平臺。查找服務的模型已經設計好了(參考 2022 年 3 月份的文章),我們只需要將這個模型轉換為代碼和配置。

Pythonic 方法

Python 是一種通用編程語言,已經存在了大約 30 年。早期,它是自動化腳本的首選。然而,隨著 Django 和 Flask 等框架的出現,它的受歡迎程度越來越高,現在各種領域中都在應用它,如企業應用程序開發。數據科學和機器學習進一步推動了它的發展,Python 現在是三大編程語言之一。

許多人將 Python 的成功歸功于它容易編碼。這只是一部分原因。只要你的目標是開發小型腳本,Python 就像一個玩具,你會非常喜歡它。然而,當你進入嚴肅的大規模應用程序開發領域時,你將不得不處理大量的ifelse,Python 變得與任何其他平臺一樣好或一樣壞。例如,采用一種面向對象的方法!許多 Python 開發人員甚至可能沒意識到 Python 支持類、繼承等功能。Python 確實支持成熟的面向對象開發,但是有它自己的方式 -- Pythonic!讓我們探索一下!

領域模型

AddService通過將數據保存到一個 MySQL 數據庫中來將用戶添加到系統中。FindService的目標是提供一個 REST API 按用戶名查找用戶。域模型如圖 1 所示。它主要由一些值對象組成,如User實體的NamePhoneNumber以及UserRepository

圖 1: 查找服務的域模型

讓我們從Name開始。由于它是一個值對象,因此必須在創建時進行驗證,并且必須保持不可變。基本結構如所示:

 

  1. class Name:

     

  2. value: str

     

  3. def __post_init__(self):

     

  4. if self.value is None or len(self.value.strip()) < 8 or len(self.value.strip()) > 32:

     

  5. raise ValueError("Invalid Name")

     

如你所見,Name包含一個字符串類型的值。作為后期初始化的一部分,我們會驗證它。

Python 3.7 提供了@dataclass裝飾器,它提供了許多開箱即用的數據承載類的功能,如構造函數、比較運算符等。如下是裝飾后的Name類:

 

  1. from dataclasses import dataclass

     

  2. @dataclass

  3. class Name:

  4. value: str

  5. def __post_init__(self):

  6. if self.value is None or len(self.value.strip()) < 8 or len(self.value.strip()) > 32:

  7. raise ValueError("Invalid Name")

 

以下代碼可以創建一個Name對象:

 

  1. name = Name("Krishna")

value屬性可以按照如下方式讀取或寫入:

 

  1. name.value = "Mohan"

     

  2. print(name.value)

     

可以很容易地與另一個Name對象比較,如下所示:

 

  1. other = Name("Mohan")

     

  2. if name == other:

     

  3. print("same")

     

如你所見,對象比較的是值而不是引用。這一切都是開箱即用的。我們還可以通過凍結對象使對象不可變。這是Name值對象的最終版本:

 

  1. from dataclasses import dataclass

     

  2. @dataclass(frozen=True)

  3. class Name:

  4. value: str

  5. def __post_init__(self):

  6. if self.value is None or len(self.value.strip()) < 8 or len(self.value.strip()) > 32:

  7. raise ValueError("Invalid Name")

     

PhoneNumber也遵循類似的方法,因為它也是一個值對象:

 

  1. @dataclass(frozen=True)

     

  2. class PhoneNumber:

     

  3. value: int

     

  4. def __post_init__(self):

     

  5. if self.value < 9000000000:

     

  6. raise ValueError("Invalid Phone Number")

     

 

User類是一個實體,不是一個值對象。換句話說,User是可變的。以下是結構:

 

  1. from dataclasses import dataclass

     

  2. import datetime

     

  3. @dataclass

  4. class User:

  5. _name: Name

  6. _phone: PhoneNumber

  7. _since: datetime.datetime

  8. def __post_init__(self):

  9. if self._name is None or self._phone is None:

  10. raise ValueError("Invalid user")

  11. if self._since is None:

  12. self.since = datetime.datetime.now()
     

你能觀察到User并沒有凍結,因為我們希望它是可變的。但是,我們不希望所有屬性都是可變的。標識字段如_name_since是希望不會修改的。那么,這如何做到呢?

Python3 提供了所謂的描述符協議,它會幫助我們正確定義 getter 和 setter。讓我們使用@property裝飾器將 getter 添加到User的所有三個字段中。

 

  1. @property

  2. def name(self) -> Name:

  3. return self._name

  4. @property

  5. def phone(self) -> PhoneNumber:

  6. return self._phone

  7. @property

  8. def since(self) -> datetime.datetime:

  9. return self._since

phone字段的 setter 可以使用@<字段>.setter來裝飾:

 

  1. @phone.setter

  2. def phone(self, phone: PhoneNumber) -> None:

  3. if phone is None:

  4. raise ValueError("Invalid phone")

  5. self._phone = phone

 

通過重寫__str__()函數,也可以為User提供一個簡單的打印方法:

 

  1. def __str__(self):

  2. return self.name.value + " [" + str(self.phone.value) + "] since " + str(self.since)

     

這樣,域模型的實體和值對象就準備好了。創建異常類如下所示:

 

  1. class UserNotFoundException(Exception):

  2. pass

     

域模型現在只剩下UserRepository了。Python 提供了一個名為abc的有用模塊來創建抽象方法和抽象類。因為UserRepository只是一個接口,所以我們可以使用abc模塊。

任何繼承自abc.ABC的類都將變為抽象類,任何帶有@abc.abstractmethod裝飾器的函數都會變為一個抽象函數。下面是UserRepository的結構:

 

  1. from abc import ABC, abstractmethod

     

  2. class UserRepository(ABC):

  3. @abstractmethod

  4. def fetch(self, name:Name) -> User:

  5. pass

 

UserRepository遵循倉儲模式。換句話說,它在User實體上提供適當的 CRUD 操作,而不會暴露底層數據存儲語義。在本例中,我們只需要fetch()操作,因為FindService只查找用戶。

因為UserRepository是一個抽象類,我們不能從抽象類創建實例對象。創建對象必須依賴于一個具體類實現這個抽象類。數據層UserRepositoryImpl提供了UserRepository的具體實現:

 

  1. class UserRepositoryImpl(UserRepository):

     

  2. def fetch(self, name:Name) -> User:

     

  3. pass

     

 

由于AddService將用戶數據存儲在一個 MySQL 數據庫中,因此UserRepositoryImpl也必須連接到相同的數據庫去檢索數據。下面是連接到數據庫的代碼。注意,我們正在使用 MySQL 的連接庫。

 

  1. from mysql.connector import connect, Error

     

  2. class UserRepositoryImpl(UserRepository):

  3. def fetch(self, name:Name) -> User:

  4. try:

  5. with connect(

  6. host="mysqldb",

  7. user="root",

  8. password="admin",

  9. database="glarimy",

  10. ) as connection:

  11. with connection.cursor() as cursor:

  12. cursor.execute("SELECT * FROM ums_users where name=%s", (name.value,))

  13. row = cursor.fetchone()

  14. if cursor.rowcount == -1:

  15. raise UserNotFoundException()

  16. else:

  17. return User(Name(row[0]), PhoneNumber(row[1]), row[2])

  18. except Error as e:

  19. raise e

在上面的片段中,我們使用用戶root/ 密碼admin連接到一個名為mysqldb的數據庫服務器,使用名為glarimy的數據庫(模式)。在演示代碼中是可以包含這些信息的,但在生產中不建議這么做,因為這會暴露敏感信息。

fetch()操作的邏輯非常直觀,它對ums_users表執行 SELECT 查詢。回想一下,AddService正在將用戶數據寫入同一個表中。如果 SELECT 查詢沒有返回記錄,fetch()函數將拋出UserNotFoundException異常。否則,它會從記錄中構造User實體并將其返回給調用者。這沒有什么特殊的。

應用層

最終,我們需要創建應用層。此模型如圖 2 所示。它只包含兩個類:控制器和一個 DTO。

圖 2: 添加服務的應用層

眾所周知,一個 DTO 只是一個沒有任何業務邏輯的數據容器。它主要用于在FindService和外部之間傳輸數據。我們只是提供了在 REST 層中將UserRecord轉換為字典以便用于 JSON 傳輸:

 

  1. class UserRecord:

     

  2. def toJSON(self):

     

  3. return {

     

  4. "name": self.name,

     

  5. "phone": self.phone,

     

  6. "since": self.since

     

  7. }

     

 

控制器的工作是將 DTO 轉換為用于域服務的域對象,反之亦然。可以從find()操作中觀察到這一點。

 

  1. class UserController:

     

  2. def __init__(self):

  3. self._repo = UserRepositoryImpl()

  4. def find(self, name: str):

  5. try:

  6. user: User = self._repo.fetch(Name(name))

  7. record: UserRecord = UserRecord()

  8. record.name = user.name.value

  9. record.phone = user.phone.value

  10. record.since = user.since

  11. return record

  12. except UserNotFoundException as e:

  13. return None

 

find()操作接收一個字符串作為用戶名,然后將其轉換為Name對象,并調用UserRepository獲取相應的User對象。如果找到了,則使用檢索到的User`` 對象創建UserRecord`。回想一下,將域對象轉換為 DTO 是很有必要的,這樣可以對外部服務隱藏域模型。

UserController不需要有多個實例,它也可以是單例的。通過重寫__new__,可以將其建模為一個單例。

 

  1. class UserController:

  2. def __new__(self):

  3. if not hasattr(self, ‘instance’):

  4. self.instance = super().__new__(self)

  5. return self.instance

  6. def __init__(self):

  7. self._repo = UserRepositoryImpl()

  8. def find(self, name: str):

  9. try:

  10. user: User = self._repo.fetch(Name(name))

  11. record: UserRecord = UserRecord()

  12. record.name = user.name.getValue()

  13. record.phone = user.phone.getValue()

  14. record.since = user.since

  15. return record

  16. except UserNotFoundException as e:

  17. return None

 

我們已經完全實現了FindService的模型,剩下的唯一任務是將其作為 REST 服務公開。

REST API

FindService只提供一個 API,那就是通過用戶名查找用戶。顯然 URI 如下所示:

 

  1. GET /user/{name}

     

此 API 希望根據提供的用戶名查找用戶,并以 JSON 格式返回用戶的電話號碼等詳細信息。如果沒有找到用戶,API 將返回一個 404 狀態碼。

我們可以使用 Flask 框架來構建 REST API,它最初的目的是使用 Python 開發 Web 應用程序。除了 html 視圖,它還進一步擴展到支持 REST 視圖。我們選擇這個框架是因為它足夠簡單。 創建一個 Flask 應用程序:

 

  1. from flask import Flask

     

  2. App = Flask(__name__)

     

 

然后為 Flask 應用程序定義路由,就像函數一樣簡單:

 

  1. @app.route('/user/')

     

  2. def get(name):

     

  3. pass

     

注意@app.route映射到 API/user/,與之對應的函數的get()

如你所見,每次用戶訪問 API 如http://server:port/user/Krishna時,都將調用這個get()函數。Flask 足夠智能,可以從 URL 中提取Krishna作為用戶名,并將其傳遞給get()函數。

get()函數很簡單。它要求控制器找到該用戶,并將其與通常的 HTTP 頭一起打包為 JSON 格式后返回。如果控制器返回None,則get()函數返回合適的 HTTP 狀態碼。

 

  1. from flask import jsonify, abort

  2. controller = UserController()

  3. record = controller.find(name)

  4. if record is None:

  5. abort(404)

  6. else:

  7. resp = jsonify(record.toJSON())

  8. resp.status_code = 200

  9. return resp

     

最后,我們需要 Flask 應用程序提供服務,可以使用waitress服務:

 

  1. from waitress import serve

  2. serve(app, host="0.0.0.0", port=8080)

 

在上面的片段中,應用程序在本地主機的 8080 端口上提供服務。最終代碼如下所示:

 

  1. from flask import Flask, jsonify, abort

  2. from waitress import serve

  3. app = Flask(__name__)

  4. @app.route('/user/')

  5. def get(name):

  6. controller = UserController()

  7. record = controller.find(name)

  8. if record is None:

  9. abort(404)

  10. else:

  11. resp = jsonify(record.toJSON())

  12. resp.status_code = 200

  13. return resp

  14. serve(app, host="0.0.0.0", port=8080)

 

部署

FindService的代碼已經準備完畢。除了 REST API 之外,它還有域模型、數據層和應用程序層。下一步是構建此服務,將其容器化,然后部署到 Kubernetes 上。此過程與部署其他服務妹有任何區別,但有一些 Python 特有的步驟。

在繼續前進之前,讓我們來看下文件夾和文件結構:

 

  1. + ums-find-service

  2. + ums

  3. - domain.py

  4. - data.py

  5. - app.py

  6. - Dockerfile

  7. - requirements.txt

  8. - kube-find-deployment.yml

     

如你所見,整個工作文件夾都位于ums-find-service下,它包含了ums文件夾中的代碼和一些配置文件,例如Dockerfilerequirements.txtkube-find-deployment.yml

domain.py包含域模型,data.py包含UserRepositoryImplapp.py包含剩余代碼。我們已經閱讀過代碼了,現在我們來看看配置文件。

第一個是requirements.txt,它聲明了 Python 系統需要下載和安裝的外部依賴項。我們需要用查找服務中用到的每個外部 Python 模塊來填充它。如你所見,我們使用了 MySQL 連接器、Flask 和 Waitress 模塊。因此,下面是requirements.txt的內容。

 

  1. Flask==2.1.1

  2. Flask_RESTful

  3. mysql-connector-python

  4. waitress

     

第二步是在Dockerfile中聲明 Docker 相關的清單,如下:

 

  1. FROM python:3.8-slim-buster

  2. WORKDIR /ums

  3. ADD ums /ums

  4. ADD requirements.txt requirements.txt

  5. RUN pip3 install -r requirements.txt

  6. EXPOSE 8080

  7. ENTRYPOINT ["python"]

  8. CMD ["/ums/app.py"]

 

總的來說,我們使用 Python 3.8 作為基線,除了移動requirements.txt之外,我們還將代碼從ums文件夾移動到 Docker 容器中對應的文件夾中。然后,我們指示容器運行pip3 install命令安裝對應模塊。最后,我們向外暴露 8080 端口(因為 waitress 運行在此端口上)。

為了運行此服務,我們指示容器使用使用以下命令:

 

  1. python /ums/app.py

 

一旦Dockerfile準備完成,在ums-find-service文件夾中運行以下命令,創建 Docker 鏡像:

 

  1. docker build -t glarimy/ums-find-service

     

它會創建 Docker 鏡像,可以使用以下命令查找鏡像:

 

  1. docker images

     

嘗試將鏡像推送到 Docker Hub,你也可以登錄到 Docker。

 

  1. docker login

  2. docker push glarimy/ums-find-service

 

最后一步是為 Kubernetes 部署構建清單。

在之前的文章中,我們已經介紹了如何建立 Kubernetes 集群、部署和使用服務的方法。我假設仍然使用之前文章中的清單文件來部署添加服務、MySQL、Kafka 和 Zookeeper。我們只需要將以下內容添加到kube-find-deployment.yml文件中:

 

  1. apiVersion: apps/v1

  2. kind: Deployment

  3. metadata:

  4. name: ums-find-service

  5. labels:

  6. app: ums-find-service

  7. spec:

  8. replicas: 3

  9. selector:

  10. matchLabels:

  11. app: ums-find-service

  12. template:

  13. metadata:

  14. labels:

  15. app: ums-find-service

  16. spec:

  17. containers:

  18. - name: ums-find-service

  19. image: glarimy/ums-find-service

  20. ports:

  21. - containerPort: 8080

  22. ---

  23. apiVersion: v1

  24. kind: Service

  25. metadata:

  26. name: ums-find-service

  27. labels:

  28. name: ums-find-service

  29. spec:

  30. type: LoadBalancer

  31. ports:

  32. - port: 8080

  33. selector:

  34. app: ums-find-service

     

上面清單文件的第一部分聲明了glarimy/ums-find-service鏡像的FindService,它包含三個副本。它還暴露 8080 端口。清單的后半部分聲明了一個 Kubernetes 服務作為FindService部署的前端。請記住,在之前文章中,mysqldb 服務已經是上述清單的一部分了。

運行以下命令在 Kubernetes 集群上部署清單文件:

 

  1. kubectl create -f kube-find-deployment.yml

 

部署完成后,可以使用以下命令驗證容器組和服務:

 

  1. kubectl get services

     

輸出如圖 3 所示:

圖 3: Kubernetes 服務

它會列出集群上運行的所有服務。注意查找服務的外部 IP,使用curl調用此服務:

 

  1. curl http://10.98.45.187:8080/user/KrishnaMohan

     

注意:10.98.45.187 對應查找服務,如圖 3 所示。

如果我們使用AddService創建一個名為KrishnaMohan的用戶,那么上面的curl命令看起來如圖 4 所示:

圖 4: 查找服務

用戶管理系統(UMS)的體系結構包含AddServiceFindService,以及存儲和消息傳遞所需的后端服務,如圖 5 所示。可以看到終端用戶使用ums-add-service的 IP 地址添加新用戶,使用ums-find-service的 IP 地址查找已有用戶。每個 Kubernetes 服務都由三個對應容器的節點支持。還要注意:同樣的 mysqldb 服務用于存儲和檢索用戶數據。

圖 5: UMS 的添加服務和查找服務

其他服務

UMS 系統還包含兩個服務:SearchServiceJournalService。在本系列的下一部分中,我們將在 Node 平臺上設計這些服務,并將它們部署到同一個 Kubernetes 集群,以演示多語言微服務架構的真正魅力。最后,我們將觀察一些與微服務相關的設計模式。

via:

作者: 選題: 譯者: 校對:

本文由 原創編譯, 榮譽推出

LCTT 譯者 :MjSeven

翻譯: 171.0 篇

貢獻: 1720 天

2018-01-30

2022-10-16

https://linux.cn/lctt/MjSeven

歡迎遵照 CC-BY-SA 協議規定轉載,

如需轉載,請在文章下留言 “ 轉載:公眾號名稱”,

分享到:
標簽:Python
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定