Architecture : Interface utilisateur (User Interface)

Attention :

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 :

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 :

Questions/Réponses

Aucune TwitterEmail

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.