C'est le premier article d'une série que j'ai décidé de créer afin d'expliquer comment j'organise mes applications symfony et comment j'essaie d'écrire du code le plus orienté domaine possible.
Ci-dessous, vous pouvez trouver l'organigramme que j'utiliserai pendant toutes les parties de la série. Dans chaque article, je me concentrerai sur une partie concrète du diagramme et j'essaierai d'analyser les processus impliqués et de détecter quelles parties appartiendraient à notre domaine et comment se découpler des autres parties à l'aide de couches externes.
Dans cette première partie, nous nous concentrerons sur les processus d'extraction et de validation des données. Pour le processus d'extraction des données, nous supposerons que les données de la demande sont formatées en JSON.
Basé sur le fait que nous savons que les données de la requête font partie de la charge utile de la requête JSON, extraire la charge utile de la requête (dans ce cas, extraire signifie obtenir un tableau à partir de la charge utile JSON) serait aussi simple que d'utiliser la fonction 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 } }
Pour valider les données, nous avons besoin de trois éléments :
Pour le premier, nous allons créer un DTO (Data Transfer Object) qui représentera les données entrantes et nous utiliserons les attributs de contraintes de validation Symfony pour préciser comment ces données doivent être validées.
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 ){} }
Comme vous pouvez le constater, nous avons défini nos règles de validation des données d'entrée au sein du DTO récemment créé. Ces règles sont les suivantes :
Pour le second, nous utiliserons le composant normaliseur Symfony grâce auquel nous pourrons mapper les données entrantes de la requête dans notre 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); } }
Comme indiqué ci-dessus, la méthode denormalize fait le travail et avec une seule ligne de code, nous obtenons notre DTO rempli avec les données entrantes.
Enfin, pour valider les données nous nous appuierons sur le service validateur Symfony qui recevra une instance de notre DTO récemment dénormalisé (qui transportera les données entrantes) et validera les données selon les règles 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 } }
Jusqu'à présent, nous avons décomposé le processus d'Extraction et de Validation des données en quatre parties :
La question est maintenant : laquelle de ces parties appartient à notre domaine ?
Pour répondre à la question, analysons les processus impliqués :
1.- Extraction des données : Cette partie utilise uniquement la fonction "json_decode" pour transformer les données entrantes de json en tableau. Il n'ajoute pas de logique métier, donc cela n'appartient pas au domaine.
2.- Le DTO : Le DTO contient les propriétés associées aux données d'entrée et comment elles seront validées. Cela signifie que le DTO contient des règles métier (les règles de validation) et qu'il appartiendrait donc au domaine.
3.- Dénormaliser les données : Cette partie utilise simplement un service d'infrastructure (un composant de framework) pour dénormaliser les données en un objet. Cela ne contient pas de règles commerciales, donc cela n'appartient pas à notre domaine.
4.- Validation des données : De la même manière que le processus Dénormaliser les données, le processus de validation des données utilise également un service d'infrastructure (un composant de framework) pour valider les données entrantes . Celui-ci ne contient pas de règles métier puisqu'elles sont définies dans le DTO donc cela ne ferait pas non plus partie de notre domaine.
Après avoir analysé les derniers points, nous pouvons conclure que seul le DTO fera partie de notre domaine. Alors, que fait-on du reste du code ?
Personnellement, j'aime inclure ce genre de processus (extraction, dénormalisation et validation de données) dans la couche application ou la couche service. Pourquoi ?, introduisons la couche application.
En bref, la couche application est responsable de l'orchestration et de la coordination, laissant la logique métier à la couche domaine. De plus, il agit comme intermédiaire entre la couche domaine et les couches externes telles que la couche présentation (UI) et la couche infrastructure.
À partir de la définition ci-dessus, nous pourrions inclure les processus Extraction, Dénormalisation et Validation dans un service de la couche application puisque :
Parfait, nous allons créer un service applicatif pour gérer ces processus. Comment allons-nous faire ? Comment allons-nous gérer les responsabilités ?
Le principe de responsabilité unique (SRP) stipule que chaque classe ne doit être responsable que d'une partie du comportement de l'application. Si une classe a plusieurs responsabilités, elle devient plus difficile à comprendre, à maintenir et à modifier.
Comment cela nous affecte-t-il ? Analysons-le.
Jusqu’à présent, nous savons que notre service applicatif doit extraire, dénormaliser et valider les données entrantes. Sachant cela, il est facile d'extraire les responsabilités suivantes :
Faut-il diviser ces responsabilités en 3 services différents ? Je ne pense pas. Laissez-moi vous expliquer.
Comme nous l'avons vu, chaque responsabilité est gérée par une fonction ou un composant de l'infrastructure :
Comme le service applicatif peut déléguer ces responsabilités aux services d'infrastructure, nous pouvons créer une responsabilité plus abstraite (Processus des données) et l'attribuer au service applicatif.
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 } }
Comme indiqué ci-dessus, le service d'application DataProcessor utilise la fonction json_decode et le service de normalisation et de validation Symfony pour traiter la demande d'entrée et renvoyer un DTO frais et validé. On peut donc dire que le service DataProcessor :
Comme vous l'avez peut-être remarqué, le service DataProcessor renvoie une ValidationException Symfony lorsque le processus de validation trouve une erreur. Dans le prochain article de cette série, nous apprendrons comment appliquer nos règles métier pour structurer les erreurs et enfin les présenter au client.
Je sais que nous pourrions supprimer le service DataProcessor et utiliser MapRequestPayload comme couche d'application de service pour extraire, dénormaliser et valider les données mais, étant donné le contexte de cet article, j'ai pensé qu'il était plus pratique de écrivez-le de cette façon.
Dans ce premier article, nous nous sommes concentrés sur les processus d'extraction et de validation des données à partir du diagramme de flux. Nous avons répertorié les tâches impliquées dans ce processus et nous avons appris à détecter quelles parties appartiennent au domaine.
Sachant quelles parties appartiennent au domaine, nous avons écrit un service de couche d'application qui connecte les services d'infrastructure régis par le domaine et coordonne le processus d'extraction et de validation des données.
Dans le prochain article, nous explorerons comment définir nos règles métier pour gérer les exceptions et nous créerons également un service de domaine qui se chargera de transformer l'Input DTO en une entité persistante.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!