Architecture : Interface utilisateur (User Interface)
Attention :
Cet article fait partie d’une série consacrée à de grands principes d’architecture qui me sont propres et a pour objectif de faire comprendre aux tiers comment évoluer sur mes propres projets.
Cet article évolue suivant les retours et les différentes expériences.
User Interface fait partie des termes utilisés en DDD. N'y voyez cependant qu'un terme commun et une inspiration (forte mais une inspiration tout de même).
Le terme Interface Utilisateur (User Interface ou encore UI) désigne les différents moyens d’une application de communiquer avec un système extérieur : humain ou machine.
On trouve différents ensembles d’Interfaces Utilisateur :
- HTML
- API
- Console
- Message Queue
- ...
Tous ces ensembles peuvent eux-mêmes être divisés. Une Interface Utilisateur HTML pouvant par exemple représenter la partie vitrine d’un site internet et une autre Interface Utilisateur l’administration. Autre exemple, une Interface Utilisateur API peut quant à elle exister au travers de différentes versions clairement identifiées.
Pourquoi ?
Dans un système complexe et destiné à évoluer, il est plus simple de segmenter ses différentes Interfaces Utilisateur et de les identifier clairement.
Cette segmentation est aussi bien visuelle : les éléments d’une Interface Utilisateur sont regroupés dans un même package, que technique : il est plus facile de leur donner des prérequis différents, de les faire évoluer dans le temps ou encore de les déprécier.
Une Interface Utilisateur est également décorrélée des autres couches de l'application (qui sont quant à elles chargées du traitement métier). Cela sous-entend que différentes Interfaces Utilisateur effectuant le même traitement encourageront naturellement le ou la développeur·se à utiliser des services identiques et les bonnes pratiques SOLID.
Les Interfaces Utilisateur peuvent également fonctionner de manière autonome avec des données de test ou temporaires et simuler un traitement sans impliquer d’autres services (via de la donnée brute par exemple).
Supprimer une Interface Utilisateur ne devrait pas avoir d'impact sur les autres Interfaces Utilisateur mais seulement sur les systèmes extérieurs en lien avec l'Interface Utilisateur (exemple : moteur de recherche, visiteur...).
Exemples
<?php
namespace App\UI\Front\Action;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Zend\Diactoros\Response\HtmlResponse;
final class Hello
{
private Service $service;
public function __construct(Service $service)
{
$this->service = $service;
}
public function __invoke(ServerRequestInterface $request): ResponseInterface
{
// 1. entrée
$data = (string) $request->getBody();
// 2. traitement
$response = $this->service->__invoke($data);
// 3. sortie
return new HtmlResponse($response);
}
}
<?php
namespace App\UI\Console\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
final class HelloCommand extends Command
{
protected static $defaultName = 'app:hello';
private Service $service;
public function __construct(Service $service)
{
$this->service = $service;
parent::__construct();
}
protected function configure()
{
// arguments
}
protected function execute(InputInterface $input, OutputInterface $output): ResponseInterface
{
// 1. entrée
$data = $input->getArgument('foo');
// 2. traitement
$response = $this->service->__invoke($data);
// 3. sortie
$ouput->writeln($response);
return Command::SUCCESS;
}
}
Éléments annexes
Tous les éléments en lien avec l’extérieur font partie de l'Interface Utilisateur.
Cela implique par exemple les formulaires. On créera ses formulaires (et les éléments relatifs à son traitement) dans un namespace tel que App\UI\{...}\Form\{DTO,Type,Handler,...}
.
A propos des formulaires, ces derniers font partie de l'Interface Utilisateur car ils sont intimement liées aux données entrantes, données qui seront ensuite traitées par les autres couches de l'applications. Cela introduit un potentiel lien avec un composant tiers (comme le composant Form de Symfony) mais ce lien, dans ce cas précis, n'est actuellement pas considéré comme problématique car la valeur ajoutée globale de l'Interface Utilisateur est plus importante.
Dans le cas de l'intégration dans un système tiers (plugin, extension, surcouche...), il est conseillé de considérer l'outil principal comme une Interface Utilisateur. Cette considération permettra alors de segmenter clairement la partie du code en relation avec l'outil et le code métier ajouté. Une fois encore, le changement d'outil et donc d'Interface Utilisateur ne doit pas impacter le code métier.
Symfony
Certaines règles sont en désaccord avec les bonnes pratiques officielles Symfony concernant les formulaires :
- L’Interface Utilisateur encourage l’utilisation des Data Transfer Object et un traitement en deux temps (les traitements backend devraient être agnostiques des Interfaces Utilisateur) ;
- Dans le cas d’un même formulaire évoluant avec les mêmes données sources dans deux Interfaces Utilisateur, on encouragera la duplication du Type ;
- Sur la base du point précédent, compte tenu de la faible probabilité d’avoir deux Interfaces Utilisateur remplissant le même rôle avec les mêmes données sources, on déconseillera l’utilisation des Extensions sur autre chose que des types primitifs fournis par le framework et/ou des bundles. Si de telles extensions doivent être créées, elles ne doivent pas faire partie du package UI mais de l'Infrastructure.
Questions/Réponses
Conclusion
L’Interface Utilisateur est l’une des couches composant une application. Il est important de la traiter aussi bien que le reste de l’application, car c’est celle qui, dans le temps, pourra être constituée de code spaghetti. Charge à vous, sur cette base, de trouver votre meilleure façon de faire, qu’il s’agisse d’un modèle de type MVC, ADR ou maison.