Sélectionner une page

Préambule

Cet article est le dernier d’une liste de 5 chapitres, concernant l’architecture logicielle, afin de vous aider à analyser vos besoins, et à choisir l’architecture la plus adaptée pour réaliser votre projet.

  1. L’architecture Monolithique
  2. L’Architecture Orientée Service ou « Service Oriented Architecture (SOA) »
  3. L’Architecture Orientée Événement ou « Event Oriented Architecture (EOA) »
  4. Architecture Moderne : Les concepts essentiels
  5. Architecture Moderne : Exemple de création d’un nouveau projet

Lire l’article Architecture Moderne : Les concepts essentiels est recommandé pour une meilleure compréhension du présent article.

Le code source des projets est consultable ici !

Cahier des charges – Métier

Nous souhaitons développer une application, permettant aux citoyens de dire s’ils sont satisfaits ou non de la manière dont sont gérés les feux de circulation.

Deux applications devront être développées :

  • Interface de saisi de la satisfaction : Satisfy Me
  • Interface de consultation de la satisfaction : Who’s Satisfied

Satisfy Me

  • L’application devra être accessible sur internet
  • Il s’agira d’une succession d’étapes telles que :
    • L’utilisateur sélectionne sa ville (saisi dans un champ, et suggestion automatique)
    • L’utilisateur clique sur l’icône “pouce vers le haut” ou “pouce vers le bas”
    • Si l’utilisateur n’est pas satisfait (pouce vers le bas) il peut saisir un commentaire
    • Un message s’affiche remerciant l’utilisateur d’avoir donné son avis
    • Si l’utilisateur revient ultérieurement, on affiche à nouveau le message de remerciement (afin qu’il ne vote pas deux fois)

Etape 1

Cahier des charges – Métier : Satisfy Me – Etape 1

Etape 2

Cahier des charges – Métier : Satisfy Me – Etape 2

Etape 2B

Cahier des charges – Métier : Satisfy Me – Etape 2B

Etape 3

Cahier des charges – Métier : Satisfy Me – Etape 3

Who’s Satisfied

  • L’application devra être accessible sur internet
  • Son accès devra être limité par un identifiant/mot de passe admin
  • Il s’agira d’une page avec :
    • Tableau de satisfaction, avec sur chaque ligne :
      • Ville au format : “Nom (Code Postal)”
      • Nombre de “pouce vers le haut”
      • Nombre de “pouce vers le bas”
      • Taux de satisfaction
    • Le tableau sera trié par taux de satisfaction
Cahier des charges – Métier : Who’s Satisfied

Cahier des charges – Technique

Afin de réaliser la partie technique du cahier des charges, il est important de bien analyser tous les éléments de la partie métier. Dans cette dernière, le client/PO (Product Owner) souhaite deux applications. Cela ne signifie pas qu’il faille développer deux “projets” distincts. Ce peut être plus, ou moins. Tout ce qu’il veut, c’est pouvoir accéder à deux pages distinctes.

D’une manière générale, il est toujours intéressant de découper une application Web en 2 services minimum : Front et Back. Ceci permet d’utiliser deux Frameworks, chacun adapté à son domaine, et de le faire développer par deux développeurs/équipes différentes en limitant les conflits. En effet, si on choisi un Back en Symfony par exemple, le développeur Front n’aura besoin d’aucune connaissance en Symfony, et il pourra développer les WebApps avec Angular, React, VueJS, ou ce qu’il souhaite.

En analysant le contexte, j’ai choisi de découper en 6 micro-services : 2 interfaces homme-machine (saisi de la satisfaction, et consultation de la satisfaction), en séparant Front et Back, un micro-service « MariaDB », et un dernier micro-service « RabbitMQ ». Les 2 micro-services Front seront des serveurs HTML/JS/CSS (permettant d’envoyer le code de l’application sur le navigateur de l’utilisateur). Le micro-service MariaDB permettra d’isoler les bases de données afin de pouvoir éventuellement répliquer les données, faire de la haute-disponibilité, etc… Le micro-service RabbitMQ fournira un serveur RabbitMQ, lequel pourra également à terme être mis en haute disponibilité et load-balanced.

Services

Satisfy Me – Front

  • Développé sous VueJS
  • Communique avec le service “Satisfy Me – Back” via une API RESTful
  • Serveur Apache

Who’s Satisfied – Front

  • Développé sous VueJS
  • Communique avec le service “Who’s Satisfied – Back” via une API RESTful
  • Serveur Apache
  • Sécurisé via .htpasswd

Satisfy Me – Back

  • Développé sous Symfony
  • Communique avec les services internes via AMQP
  • Expose une API RESTFul
  • Serveur Apache
  • Base de données MariaDB

Who’s Satisfied – Back

  • Développé sous Symfony
  • Communique avec les services internes via AMQP
  • Serveur Apache
  • Base de données MariaDB

MariaDB

  • Permet l’hébergement de base de données MariaDB pour les micro-services

RabbitMQ

  • Met à disposition des services un broker RabbitMQ

Bases de données

Les services “Satisfy Me – Back” et “Who’s Satisfied – Back” auront chacun leur base de données. Ces bases de données seront stockées sur le micro-service “MariaDB”.

La base de données “satisfy_me_back” aura la structure suivante :

  • city
    • id : INT; AUTO-INCREMENT; NOT NULL; PRIMARY KEY
    • name : VARCHAR(255); NOT NULL
    • zip_code : VARCHAR(5); NOT NULL
  • opinion
    • id : INT; AUTO-INCREMENT; NOT NULL; PRIMARY KEY
    • city_id : INT; NOT NULL; FOREIGN KEY(city:id)
    • date_time : DATETIME; NOT NULL
    • satisfied : BOOL; NOT NULL
    • comment : VARCHAR(255)

La base de données “whos_satisfied_back” aura la structure suivante :

  • satisfaction
    • id : INT; AUTO-INCREMENT; NOT NULL; PRIMARY KEY
    • city_name : VARCHAR(255); NOT NULL
    • city_zip_code : VARCHAR(255); NOT NULL
    • satisfied_count : INT; NOT NULL; DEFAULT(0)
    • not_satisfied_count : INT; NOT NULL; DEFAULT(0)

Cette deuxième base de données n’est pas indispensable d’un point de vue “stockage de l’information”. En effet, il ne s’agit que d’une “vue”/”agrégat” de la base de données “satisfy_me_back”. Néanmoins, elle satisfait CQRS, et fournit donc une base de données parfaitement adaptée/optimisée pour le service “whos_satisfied_back”. En faisant de la sorte, le service “Who’s Satisfied – Back” n’aura pas besoin d’effectuer de requête sur la base de données du service “Satisfy Me – Back”, ce qui permet donc un couplage faible.

Environnements

Satisfy Me – Front

  • Container basé sur Debian
  • Serveur Apache
  • Ports exposés : 80

Who’s Satisfied – Front

  • Container basé sur Debian
  • Serveur Apache
  • Ports exposés : 80

Satisfy Me – Back

  • Container basé sur Debian
  • Serveur Apache
  • PHP 7.4
  • Ports exposés : 80

Who’s Satisfied – Back

  • Container basé sur Debian 10
  • Serveur Apache
  • PHP 7.4
  • Ports exposés : 80

MariaDB Server

  • Container basé sur Debian 10
  • Server MariaDB
  • Ports exposés : 3306

RabbitMQ

  • Container basé sur Debian 10
  • RabbitMQ
  • Ports exposés : 5672
Résumé de l’architecture

On constate que cette architecture est bien plus complexe que si l’on avait fait du “tout-en-un” (architecture monolithique). Cela demandera du temps, mais le travaille effectué maintenant aura une forte valeur sur le long terme : réutilisabilité des services “MariaDB” et “RabbitMQ”. Certaines actions pourront être effectuées sur le service “MariaDB” afin de sauvegarder les données par exemple. Dans ce cas, toutes les bases de données bénéficieraient de cette sauvegarde. De même, toutes les applications pourraient bénéficier en même temps des mises à jour de MariaDB, et bien plus encore. Il ne s’agit là que des premières briques. Chaque nouvelle brique permettra de gagner en productivité pour les briques suivantes.

Représentation en 2 dimensions des micro-services comme des briques

Sur le schéma ci-dessus, les micro-services sont représentés par des briques, empilées les unes sur les autres. En effet, les services “Back” reposent sur les services “MariaDB” et “RabbitMQ”. Mais en réalité, les relations qui peuvent exister entre les micro-services ne sont pas aussi simples. Les services ne fonctionnent pas en couche, car il s’agirait là d’une architecture “N-Tiers”. Ici, chaque service peut communiquer avec n’importe quel service.

Développement de la solution

Pour un développeur Web, cette application n’a rien de compliqué. Mais c’est peut-être bien la première fois que vous prenez en main un Broker tel qu’AMQP. Pour développer, nous aurons besoin d’un environnement comprenant RabbitMQ, un serveur MariaDB/MySQL, un serveur Apache, et PHP 7.4.

Pour tester nos APIs, il est intéressant d’installer un client HTTP tel que Postman !

Nous allons commencer par développer le service “Satisfy Me – Back”. Nous créons donc un nouveau projet Symfony.

Symfony est bien adapté à l’architecture orientée service puisqu’il intègre un bundle permettant d’utiliser AMQP (il faut bien sûr activer l’extension mod_amqp dans le fichier php.ini). Les phases du développement de ce service sont :

  • Création des entités avec Doctrine : City et Opinion
  • Création d’une API RESTful pour l’entité “Opinion” (POST et PUT)
  • Création d’une API RESTful pour l’entité “City” (GET)
  • Publication de messages AMQP lors de la création/modification d’un objet “Opinion”

Ensuite, nous développerons l’application “Who’s Satisfied – Back”, toujours avec Symfony. Les phases de développement de ce service sont :

  • Création de l’entité “Satisfaction” avec Doctrine
  • Création d’une API RESTful pour l’entité “Satisfaction” (seul le GET est nécessaire pour le moment)
  • Consommation des messages AMQP concernant la création/modification d’un objet “Opinion” afin de mettre à jour l’objet “Satisfaction” concerné. Cela signifie qu’il faudra démarrer un Worker qui “écoutera” les messages diffusés sur les queues concernées.

On continuera avec le développement de l’application “Satisfy Me – Front”, avec VueJS cette fois-ci (on aurait tout à fait pu choisir React, ou Angular). Les phases de développement de ce service sont :

  • Mise en place du template général (basé sur bootstrap)
  • Création des composants correspondant à chaque “page”
  • Communication avec l’API de “Satisfy Me – Back” :
    • Récupération des 7 villes les plus proches de la valeur saisie par l’utilisateur
    • Création d’un objet “Opinion” lors du clique sur un pouce
    • Mise à jour d’un objet “Opinion” lors de la validation d’un commentaire

Enfin, nous terminerons le développement avec l’application “Who’s Satisfied – Front”, toujours avec VueJS. Les phases de développement de ce service sont :

  • Mise en place du template général (basé sur bootstrap)
  • Création du composant “Tableau” pour afficher les résultats
  • Communication avec l’API de “Who’s Satisfied – Back” :
    • Récupération des 50 villes ayant le taux de satisfaction le plus bas

Le code source des projets est consultable ici

Déploiement de la solution

Le déploiement de la solution doit être pris en compte dans deux environnements différents minimum : Développement et Production.

Quelque soit l’environnement, l’application reposera sur Docker. De cette manière, l’environnement (dépendances, configuration des droits, paramétrage PHP, etc…) pourra être versionné directement dans le projet, et indépendamment pour chaque service.

Production

Chaque service pouvant tourner sur Docker, un seul serveur suffira pour faire tourner l’architecture complète. Ce qui est intéressant, c’est que suivant l’évolution de l’application (nouvelles fonctionnalités, charge réseau plus forte, etc…), nous ne serons pas bloqués. En effet, si le besoin se fait ressentir, il sera possible d’utiliser un autre serveur dédié pour le service “MariaDB” par exemple.

Développement

Cette aspect est très important, car avec ce type d’architecture, il est important de pouvoir déployer rapidement chaque service, sans avoir à installer de dépendances supplémentaires.

En effet, un développeur Front qui travaillera sur “Satisfy Me – Front” ne devrait pas avoir à installer PHP sur son ordinateur. Pourtant, “Satisfy Me – Front” a besoin de “Satisfy Me – Back” pour pouvoir fonctionner. Or, “Satisfy Me – Back” est développé en PHP.

Avec Docker, nous pourrons démarrer une instance en mode “dev” de chaque service, afin de pouvoir développer correctement, et sans perdre de temps.

Mais il est possible d’aller plus loin encore, et ce grâce à Docker-Compose. Cet outil nous permet de créer des applications multi-containers. Or, nos containers sont des services. Notre application est donc un ensemble de containers/services communiquant les uns avec les autres.

Configuration de Docker

Pour que le déploiement soit optimisé, nous allons “Dockeriser” nos services, en commençant par créer un Dockerfile pour chacun d’entre eux.

Ce Dockerfile devra :

  • Installer les dépendances systèmes nécessaires (ex: Apache)
  • Exposer les ports nécessaires
  • Copier une version “exécutable” du projet
  • Utiliser les variables d’environnement
  • Définir la manière de “lancer” le projet (ex: mettre à jour la base de données et démarrer Apache)

L’utilisation des variables d’environnement est très importante. C’est grâce à ces variables que l’on pourra utiliser Docker sur différents environnements, et dans différents contextes, sans avoir à créer de nouvelles images Docker.

Nos applications Symfony par exemple, devront absolument s’appuyer sur ces variables afin de pouvoir se connecter à la base de données.

Une fois nos applications “Dockerisées”, nous allons créer un projet (du nom du projet général), dont le but est de faire cohabiter tous les services qui composent notre application. Nous nommerons ce projet “Satisfaction”. Il sera versionné avec Git, et son contenu sera très simple : un fichier docker-compose.yml.

Vous le devinez, il sera basé sur Docker-Compose, lequel permet de créer/déployer des applications “multi-containers”.

C’est dans ce fichier que nous allons définir tous nos services, et les variables d’environnements à injecter. Ainsi, nous préciserons que nous souhaitons avoir un container, faisant tourner “Satisfy Me – Back”, lequel utilisera les paramètres définis dans “environment” pour se connecter à la base de données, au serveur RabbitMQ, etc…

De même, dans ce fichier, nous définirons des volumes (afin de stocker les données de MySQL sur la machine hôte par exemple), et des réseaux (afin de limiter les communications entre nos containers.

Nous pourrons builder nos images, ou bien utiliser directement les images stockées sur notre private registry (un article concernant « l’intégration et le déploiement continu” sera rédigé prochainement).

Une fois que nous disposons de toutes nos images “buildées”, nous pouvons lancer l’application avec un simple “docker-compose up -d”.

Fonctionnement de la solution

Un utilisateur X clique sur “http://satisfy-me.odeven.fr”. Son navigateur envoi donc une requête HTTP à notre VPS. Celui-ci redirige la requête sur le port 80 du container “Satisfy Me – Front”. Le serveur Apache installé sur ce container traite la requête, et retourne la page “index.html” de l’application “Satisfy Me – Front” au navigateur. Le navigateur envoi ensuite d’autres requêtes pour charger les assets définis dans le fichier “index.html” (javascript, css, images, …).

La communication avec le service “Satisfy Me – Front” se fait donc exclusivement en HTTP. Ce service n’a qu’une seule responsabilité : servir les fichiers qui composent l’application “Satisfy Me – Front”. S’agissant de HTML/CSS/JavaScript, son exécution se fait exclusivement sur le client (navigateur de l’utilisateur).

Ainsi, l’utilisateur va saisir les premiers caractères de sa ville. A ce moment, JavaScript détecte que la valeur du champ a changé, il envoi alors une requête HTTP (Ajax) de type “GET” au service “Satisfy Me – Back”. La requête passe par le VPS qui redirige vers le port 80 du service concerné. “Satisfy Me – Back” va exécuter une requête SQL sur le serveur MariaDB du service “MariaDB” pour récupérer les villes correspondant à la recherche de l’utilisateur. Il les retourne ensuite au navigateur.

Le JavaScript, lorsqu’il reçoit les données (les villes), va pouvoir les afficher pour que l’utilisateur puisse sélectionner sa ville. Ensuite, l’utilisateur passe à la deuxième étape, et clique par exemple sur “pouce vers le bas” pour exprimer son insatisfaction. Une autre requête HTTP, de type “POST” cette fois, est envoyée (via Ajax) au service “Satisfy Me – Back”, contenant l’id de la ville de l’utilisateur, et un booléen (s’il est satisfait).

Le service “Satisfy Me – Back” crée alors un nouvel objet “Opinion”, avec les informations reçues, et il l’enregistre sur le serveur MariaDB. Il retourne ensuite l’id de l’Opinion créée. Mais avant de répondre au navigateur, il va également publier un message sur la queue “satisfy_me_back.opinion.issued” avec les informations de l’opinion (ville, et satisfaction).

Une fois le message publié, le service “Who’s Satisfied – Back”, qui est abonné à la queue “satisfy_me_back.opinion.issued” va recevoir le message. Il va donc pouvoir mettre à jour sa base de données pour prendre en compte la nouvelle opinion.

Plus tard, un administrateur clique sur le lien “http://whos-satisfied.odeven.fr”. Son navigateur envoi donc une requête HTTP à notre VPS. Celui-ci redirige la requête sur le port 80 du container “Who’s Satisfied – Front”. Le serveur Apache installé sur ce container traite la requête, et retourne la page “index.html” de l’application “Who’s Satisfied – Front”, puis les assets associés.

Au chargement de la page, JavaScript effectue une requête HTTP de type “GET” pour récupérer les taux de satisfaction depuis le service “Who’s Satisfied – Back”. Ce dernier service est capable de fournir une réponse très rapidement, puisque sa base de données est déjà optimisée (avec des valeurs calculées), et que cette base est mise à jour en temps réel grâce au worker qui tourne en arrière plan.

Les données sont donc retournées au navigateur du client, qui les affiche alors dans le tableau.

En saisissant une recherche, une nouvelle requête HTTP est envoyée afin de récupérer les taux de satisfaction correspondant à la recherche.

Diagramme de séquence : saisie d’une opinion par un utilisateur

Aller plus loin

Sécurité

AMQP

RabbitMQ fonctionne avec un système d’authentification. Par défaut, l’utilisateur “guest” est créé. Il convient évidemment de n’utiliser cette utilisateur que pour des tests, l’idéal étant de créer plusieurs utilisateurs (un par service ?) avec des droits restreints aux exchanges/queues concernées.

Web Services

Les services Front créés dans ce POC communiquent avec les services Back via une API RESTful (HTTP). N’importe qui peut exécuter une requête directement sur le service “Who’s Satisfied – Back”.

Il n’est pas possible de mettre en place un filtrage IP tout en permettant un accès à “Who’s Satisfied – Front” depuis internet. Néanmoins, HTTP prévoit tous les champs nécessaires dans les “Headers” de la requête. Ainsi, de nombreux systèmes d’authentification peuvent être envisagés. Un exemple qui n’est pas sans faille serait de stocker dans l’application “Who’s Satisfied – Back” et “Who’s Satisfied – Front” un token. Celui-ci serait envoyé via le header “Authorization” de HTTP lors de chaque appel AJAX à “Who’s Satisfied – Back”. Du côté de ce dernier, un firewall pourrait être développé avec Symfony afin de vérifier si le header “Authorization” est bien présent dans l’entête HTTP, et s’il correspond avec le token stocké sur le serveur. Il ne s’agit là que d’un exemple, possédant d’ailleurs des failles (ex: vol de token), mais qui met en valeur le principe d’authentification “transparent”.

Noms de domaine

Si l’on héberge cette solution sur un seul VPS par exemple, et qu’on conteneurise les services avec Docker, il faudra mettre en place une règle de routage afin de pouvoir pointer sur le port 80 du container “satisfy_me_front” ou sur le port 80 du container “whos_satisfied_front” suivant le nom de domaine saisi par l’utilisateur. Pour que cela puisse fonctionner, il faudra mettre en place un “Reverse Proxy” sur le serveur “Hôte”, ou bien dans un autre container qui sera systématiquement sollicité lorsqu’une requête sur le port 80 de l’hôte sera effectuée. Ce Reverse Proxy redirigera la requête sur le port du container “satisfy_me_front” ou “whos_satisfied_front” suivant le nom de domaine.

Une solution bien connue et simple à mettre en place s’appelle Traefik.

Conclusion

L’Architecture logicielle ne connait pas de solution parfaite (les exemples ci-dessus en sont loin), mais uniquement des concepts ayant chacun des avantages et des inconvénients. Il existe néanmoins des architectures inadaptées, lesquelles sont facilement détectables (si le projet n’évolue plus, mais que les bugs continuent de croître par exemple).

L’essentiel à retenir sur l’utilité de l’Architecture Logicielle, c’est qu’elle doit permettre à votre projet d’évoluer dans n’importe quelle direction, de manière simple, rapide, et fonctionnelle, et doit vous aider à obtenir une bonne productivité, au moins sur le long terme.

Ce tutoriel est désormais terminé. J’espère qu’il vous aura aidé à mettre en place une architecture robuste et flexible au sein de vos projets.