這是我決定創建的系列文章的第一篇,旨在解釋我如何組織我的 symfony 應用程式以及如何嘗試編寫盡可能面向領域的程式碼。
下面,您可以找到我將在所有系列部分中使用的流程圖。在每篇文章中,我將重點放在該圖的特定部分,並嘗試分析所涉及的流程並檢測哪些部分屬於我們的領域以及如何使用外部層與其他部分解耦。
在第一部分中,我們將專注於資料擷取和驗證過程。對於資料擷取過程,我們假設請求資料的格式為 JSON。
基於我們知道請求資料位於 JSON 請求負載中的事實,提取請求負載(在本例中,提取意味著從 JSON 負載中獲取數組)就像使用 php json_decode 函數一樣簡單。
class ApiController extends AbstractController { #[Route('/api/entity', name: 'api_v1_post_entity', methods: ['POST'])] public function saveAction(Request $request,): JsonResponse { $requestData = json_decode($request->getContent(), true); // validate data } }
為了驗證數據,我們需要三個元素:
對於第一個,我們將建立一個 DTO(資料傳輸物件)來表示傳入的數據,我們將使用 Symfony 驗證約束屬性來指定如何驗證此資料。
readonly class UserInputDTO { public function __construct( #[NotBlank(message: 'Email cannot be empty')] #[Email(message: 'Email must be a valid email')] public string $email, #[NotBlank(message: 'First name cannot be empty')] public string $firstname, #[NotBlank(message: 'Last name cannot be empty')] public string $lastname, #[NotBlank(message: 'Date of birth name cannot be empty')] #[Date(message: 'Date of birth must be a valid date')] public string $dob ){} }
如您所見,我們在最近建立的 DTO 中定義了輸入資料驗證規則。這些規則如下:
對於第二個,我們將使用 Symfony 標準化器元件,透過它我們能夠將請求傳入資料對應到我們的 DTO 中。
class ApiController extends AbstractController { #[Route('/api/entity', name: 'api_v1_post_entity', methods: ['POST'])] public function saveAction(Request $request, SerializerInterface $serializer): JsonResponse { $requestData = json_decode($request->getContent(), true); $userInputDTO = $serializer->denormalize($requestData, UserInputDTO::class); } }
如上所示,denormalize 方法完成這些工作,並且只需一行程式碼,我們就可以用傳入的資料填入 DTO。
最後,為了驗證數據,我們將依賴 Symfony 驗證器服務,該服務將接收我們最近非規範化的 DTO 實例(它將攜帶傳入資料),並根據 DTO 規則驗證資料。
class ApiController extends AbstractController { #[Route('/api/entity', name: 'api_v1_post_entity', methods: ['POST'])] public function saveAction(Request $request,): JsonResponse { $requestData = json_decode($request->getContent(), true); // validate data } }
到目前為止,我們已經將提取和驗證資料的過程分為四個部分:
現在的問題是:這些部分中哪些屬於我們的領域?
為了回答這個問題,我們來分析一下涉及的流程:
1.- 擷取資料:這部分只使用「json_decode」函數將傳入的資料從 json 轉換為陣列。它不添加業務邏輯,因此這不屬於該域。
2.- DTO:DTO 包含與輸入資料關聯的屬性以及如何驗證它們。這意味著 DTO 包含業務規則(驗證規則),因此它屬於網域。
3.- 反規範化資料:這部分只是使用基礎設施服務(框架元件)將資料反規範化為物件。這不包含業務規則,因此這不屬於我們的網域。
4.- 驗證資料:與反規範化資料流程相同,驗證資料流程也使用基礎設施服務(框架元件)來驗證傳入資料。這不包含業務規則,因為它們是在 DTO 中定義的,因此它也不會是我們領域的一部分。
分析完最後一點後,我們可以得出結論,只有 DTO 才是我們領域的一部分。那麼,我們要如何處理其餘的程式碼呢?
就我個人而言,我喜歡將這種過程(提取、反規範化和驗證資料)包含到應用程式層或服務層中。為什麼?我們來介紹一下應用層。
簡而言之,應用層負責編排與協調,將業務邏輯留給領域層。此外,它充當領域層和外部層(例如表示層(UI)和基礎設施層)之間的中介。
從上面的定義開始,我們可以將 Extracting、Denormalizing 和 Validating 流程包含到應用程式層的服務中,因為:
完美,我們將建立一個應用程式服務來管理此流程。我們要怎麼做呢?我們如何管理責任?
單一職責原則(SRP)規定每個類別應該只負責應用程式行為的一部分。如果一個類別有多重職責,那麼理解、維護和修改就會變得更加困難。
這對我們有何影響?我們來分析一下。
到目前為止,我們知道我們的應用程式服務必須提取、反規範化並驗證傳入的資料。知道了這一點,就很容易提取出以下職責:
我們應該將這些職責分成 3 個不同的服務嗎?我不這麼認為。讓我解釋一下。
如我們所見,每個職責都由基礎設施功能或元件管理:
由於應用程式服務可以將這些職責委託給基礎架構服務,因此我們可以建立更抽象的職責(處理資料)並將其指派給應用程式服務。
class ApiController extends AbstractController { #[Route('/api/entity', name: 'api_v1_post_entity', methods: ['POST'])] public function saveAction(Request $request,): JsonResponse { $requestData = json_decode($request->getContent(), true); // validate data } }
如上所示,DataProcessor 應用程式服務使用 json_decode 函數以及 Symfony 規範器和驗證器服務來處理輸入請求並傳回新的且經過驗證的 DTO。所以我們可以說 DataProcessor 服務:
您可能已經注意到,當驗證過程發現錯誤時,DataProcessor 服務會拋出 Symfony ValidationException 。在本系列的下一篇文章中,我們將學習如何應用業務規則來建立錯誤並最終將其呈現給客戶。
我知道我們可以刪除 DataProcessor 服務並使用 MapRequestPayload 作為服務應用程式層來提取、反規範化和驗證數據,但是,考慮到本文的上下文,我認為這樣更方便這樣寫。
在第一篇文章中,我們將重點放在從流程圖中提取和驗證資料流程。我們列出了此過程中涉及的任務,並且了解如何偵測哪些部分屬於該網域。
知道哪些部分屬於網域,我們編寫了一個應用程式層服務,連接域規則的基礎設施服務並協調提取和驗證資料過程。
在下一篇文章中,我們將探討如何定義業務規則來管理異常,我們還將建立一個網域服務,負責將輸入 DTO 轉換為持久實體。
以上是創建專注的領域應用程式。 Symfony 方法(第 1 部分)的詳細內容。更多資訊請關注PHP中文網其他相關文章!