Comment créer une Twelve-Factor-App avec Spring Boot, Heroku et Github

Pragmatic Nerdz
Sep 19, 2023 - 7 Minutes

Cet article est le second sur la série d'articles sur la méthodologie Twelve-Factor-Apps.

  • Le 1er article présente les principes de la méthodologie Twelve-Factor-Apps.
  • Cet article va montrer un exemple d'application codé en utilisant les principes de la méthodologie Twelve-Factor-Apps

Quelque définitions

  • Heroku est une plateforme qui permet aux développeurs de créer, exécuter et exploiter des applications entièrement dans le cloud. Heroku supporte un large éventail de langages de programmation tels que Java, Ruby, PHP, Node.js, Python, Scala et Clojure.
  • Spring Boot est un framework open source qui facilite la création de micro-services et d'applications Web en Java (ou tout autre langage basé sur la JVM: Kotlin, Scala etc).
  • Twelve-Factor-App est une méthodologie permettant de créer des applications distribuées qui s'exécutent dans le cloud.

Les 12 facteurs

Pour les besoins de cet article, on va implémenter un système de gestion de One Time Password basé sur Spring Boot et déployé dans Heroku, qui va permettre de:

  • Générer des mots de passe temporaire
  • Envoyer des mot de passe temporaire via email ou SMS
  • Vérifier les mots de passe

1. La base de code

Une base de code dans un système de contrôle de versions, plusieurs déploiements

La base de code est gérée par Github, la plateforme cloud de git qui permet de stocker, gérer  le code et ses versions.

2. Les dépendances

Déclarer et isoler explicitement les dépendances

L'application utilise Maven comme gestionnaire de dépendances. Avec Maven, toutes les dépendances sont déclarées dans le fichier pom.xml, qui est isolé du code.

...
<properties>
   ...
   <lettuce.version>6.2.6.RELEASE</lettuce.version>
   ...
</properties>
<dependencies>
    ...
    <dependency>
      <groupId>io.lettuce</groupId>
      <artifactId>lettuce-core</artifactId>
      <version>${lettuce.version}</version>
    </dependency>
    ...
</dependencies>
...

3. Configuration

Stocker la configuration dans l'environnement

Spring Boot permet d'externaliser la configuration afin qu'on puisse travailler avec le même code dans différents environnements. La configuration des applications Spring Boot est contenue dans les fichiers application*.yml.

  • application.yml : qui représente la configuration par default de l'application,  généralement pour l'environnement local de développement.
  • application-test.yml: pour l'environnement de test dans Heroku.

Voici par exemple la configuration de la base de données dans application.yml, dont l'adresse est redis://localhost:6379:

otp:
  ...
  resources:
    persistence:
      redis:
        url: redis://localhost:6379

Mais dans dans le fichier application-test.yml, où l'adresse de la base de données est définie par la variable d'environnement REDISCLOUD_URL:

otp:
  ...
  resources:
    persistence:
      redis:
        url: ${REDISCLOUD_URL}

Le code qui charge l'adresse de la base de données via la variable de configuration otp.resources.persistence.redis.url dont la valeur change en fonction de la configuration:

@Configuration
class RedisConfiguration(
    /* This is how the URL configuration is injected into the code */
    @Value("\${otp.resources.persistence.redis.url}") private val url: String,
) {
    ....
}

Ceci permet à l'application d'avoir une séparation claire entre code de sa configuration, et de s'adapter en fonction de l'environnement dans lequel il est exécuté.

4. Les services de support

Traitez les services de support comme des ressources attachées

Les services de support sont des services utilisés par l'application via le réseau lors de son fonctionnement normal. Dans notre cas, les services de support sont: RabbitMQ, MTN, Mailgun et Redis.

La plateforme Heroku offre des add-ons qui sont des services de supports qui peuvent être attachés aux applications. Le lien entre les applications et leurs add-ons est via des variables d'environnement qui contiennent les informations pour se connecter aux add-ons. 

Dans notre cas, on attachera à l'applications les add-ons suivants:

  • CloudAMQP qui offre l'accès au Message Queue RabbitMQ via la variable d'environnement CLOUDAMQP_URL
  • Redis Cloud qui offre l'accès à une instance de cache Redis via la variable d'environnement REDISCLOUD_URL
  • Mailgun qui offre l'accès à une API d'envoie de mots de passe via email via les variables d'environnement MAILGUN_API_KEY et MAILGUN_DOMAIN.

Ses variables sont déclarées dans le fichier de configuration application-test.yml afin que l'application les utilisent lorsqu'elle est dans l'environnement de test (Heroku)

otp:
  resources:
    mail:
      mailgun:
        api-key: ${MAILGUN_API_KEY}
        domain: ${MAILGUN_DOMAIN}
    persistence:
      redis:
        url: ${REDISCLOUD_URL}
    queue:
      rabbitmq:
        url: ${CLOUDAMQP_URL}
Receive my Stories your e-mail inbox as soon as I publish them.
Subscribe to my Blog

5. Build, Release and Run

Étapes de construction et d'exécution strictement séparées

Github offre le service GitHub Actions, qui est une plateforme de Continuous Intégration/Continuous Delivery (CI/CD) qui permet d'automatiser les pipelines de Build, Release and Run.

Le pipeline de CI/CD est définit dans le fichier master.yml qui contient les instructions pour: 

  • Build: qui checkout le code et utilise la commande maven mvn install pour générer le fichier exécutable target/otp-heroku.jar.
  • Release: Le fichier target/otp-heroku.jar est ensuite déployé dans Heroku. 
  • Run: Aussitôt déployée, Heroku va automatiquement lancer l'application et la mettre disponible aux utilisateurs.

6. Les Processus

Exécuter l'application en tant que processus stateless

Dans notre contexte:

  • Il y a un seul processus qui est l'API web. Le fichier Procfile contient la liste des processus qui sont associés à l'application, et qui vont être pris en charge par Heroku.
  • L'API est stateless dans le sens ou elle ne maintient aucun état en mémoire. L'état de l'API est géré dans la base de données Redis.

7. La liaison de port (port binding)

Les services sont exposé via liaison de port

Heroku prend en charge automatiquement la liaison de port des applications web. Heroku associe automatiquement aux applications web le port interne (défini par variable d'environnement PORT) et l'associe automatiquement aux ports HTTP 80 et 443 .

Dans le fichier Procfile, on configure le port du serveur en associant la configuration Spring Boot server.port à la variable d'environnement PORT.

web: java $JAVA_OPTS -Dserver.port=$PORT -jar target/otp-heroku.jar --spring.profiles.active=$APP_PROFILE

8. Concurrence

Plus de capacité en augmentant les processus

Heroku exécute chacun de processus d'une application dans des conteneurs virtuel linux appelé dyno. Heroku offre un moyen simple de faire évoluer et de gérer le nombre, la taille et le type de dynos dont votre application peut avoir besoin à tout moment.

9. Liquidable

Maximisez la robustesse avec un démarrage rapide et un arrêt progressif

Avec Spring Boot, les applications sont distribuées en un fichier exécutable de type jar, ce qui permet de démarrer ou arrêter rapidement les applications à partir de la ligne de commande.

Heroku utilise le fichier Procfile pour identifier la commande à exécuter lors du démarrage du dyno.

10. Parité Dev/Prod

Gardez les environnement de développement, test et production aussi similaires que possible

Pour garantir la parité entre les différents environnements:

  1. Les environnements de développement ont accès aux mêmes services de support. Dans notre cas, ça implique que les développeurs installent sur leur machine Redis et RabbitMQ, et ont les informations d'accès aux API externes de Mailgun et MTN SMS.
  2. La configuration de tous les environnements est la même, seule les valeurs des variables de configuration changent d'un environnement à l'autre.

11. Logs

Traiter les logs comme des flux d'événements

Quand vous aurez un problème en production, les logs vont vous permettre d'identifier la cause. Ainsi il est important de toujours penser à comment générer les logs lorsqu'on code,  afin d'être capable d'identifier les problèmes futurs en production.

Quelques règles à suivre pour produire les logs:

  • Les logs doivent être générés à la frontière du système, dans les composants qui reçoivent les messages de l'extérieur. Dans notre cas, les logs sont produits par Generator, Verifier et Sender.
  • Les logs doivent inclure au minimum les informations sur les messages reçus, les résultats et erreurs.
  • Les logs doivent être formatés dans le format key=value afin de simplifier le traitement des fichiers de logs.

Heroku offre des add-ons tels que Papertrail qui archive les logs et offre une console qui permet aux développeurs de visualiser et rechercher des informations des logs.

Voici à quoi ressemble les logs de l'app:

2023-09-08 21:01:44,747 Type=INFO Logger=com.pragmaticnerdz.otp.GenerateEndpoint request_address=roger.milla@gmail.com request_type=EMAIL response_otp_uuid=35f195ec-a234-42e9-b0b7-0c82fc36d95b
2023-09-08 21:01:45,000 Type=INFO Logger=com.pragmaticnerdz.otp.SenderConsumer event_uuid=35f195ec-a234-42e9-b0b7-0c82fc36d95b event_type=EMAIL event_address=roger.milla@gmail.com event_password=176017 message_id=<20230908210145.f57a994ce35972c0@sandboxfb075b54c3cb42d59f35f5718af3add6.mailgun.org>
2023-09-08 21:03:32,697 Type=INFO Logger=com.pragmaticnerdz.otp.VerifyEndpoint request_uuid=35f195ec-a234-42e9-b0b7-0c82fc36d95b request_password=176017 response_success=true response_error=NONE

12. Processus de d'administration

Exécuter des tâches d'administration en tant que processus ponctuels

Heroku offre les one-off dynos pour executer des processus ponctuels. Dans le contexte de notre application, ce principe n'est pas appliqué.


Si vous avez aimé cet article,  abonnez-vous à mon blog pour recevoir un article tous les mercredis.

Github
12 Factor App
Heroku
Spring Boot