
Apache Iceberg pour une architecture lakehouse sur AWS
Ce guide présente Apache Iceberg, un format de table moderne pour les données volumineuses, la gestion des versions et des performances optimisées.
Progression
Avant de présenter le projet, voici une liste de concepts dont vous pourrez vous servir comme d'un pense-bête tout au long de ce tutoriel.
Le Domain est donc le coeur de votre application, il contient tous les objets métier & les règles fonctionnelles.
Souvent on peut voir une autre couche dans les projets Clean, la couche Presentation. C'est elle qui s'occupe de :
Pour ma part, je n'utilise pas du tout cette couche. C'est un choix tout à fait personnel, je trouve que ça reste le rôle de l'Infrastructure, et je garde cette logique dans mes Controllers. Mais c'est une préférence qui peut être challengée dans vos projets bien entendu !
Durant ce tutoriel, nous allons prendre un projet existant que j'ai développé, une application Symfony classique, et très simple, pour petit à petit la migrer vers une architure Clean.
Pour cela, j'ai décidé de développer une Boîte de Leitner.
La méthode Leitner, c'est une stratégie d'apprentissage et de révision de fiches qui est décrite par les scientifiques comme l'une des plus efficaces.
On image une boîte compartimentée avec des numéros. Chaque compartiment correspond à un jour, et chaque compartiment successif doit être de plus en plus espacé temporellement:
Puis on écrit des cartes, aussi appelées flash cards, ou cartes de révision, qui contiennent une question au recto, et la réponse au verso.
Le jour 1 je sors les cartes présentes dans le compartiment 1 et j'essaie de répondre à chaque question de chaque carte:
Et on continue ainsi de suite avec le jour suivant. À chaque bonne réponse, je déplace la carte dans le compartement suivant. À la moindre mauvaise réponse, la carte retourne dans le tout premier compartiment, et on recommence.
Si je répond correctement à une Carte se trouvant dans le dernier compartiment, alors la carte est retirée pour de bon: On estime que la notion est assimilée.
Comme vous le devinez, ce système est assez simple à développer, et surtout à automatiser.
J'aimerais donc pouvoir créer des cartes de révision, et que celles-ci me soient soumises régulièrement (via l'envoi d'un e-mail par exemple), pour que je puisse tenter d'y répondre, et qu'elles soient automatiquement déplacées dans les compartiments correspondants.
Et ainsi de suite, je recevrai chaque jour une notification m'indiquant à quelles cartes je dois répondre aujourd'hui.
Pas de panique vous n'avez pas à tout développer de votre côté, on va partir ensemble de cette modeste base de code que vous retrouverez sur ce repo Github.
Ce projet utilise une base de donnéee PostgreSQL (dans un container Docker), PHP 8.4 et Symfony 7.3.
Avec Docker Compose et le Symfony CLI, vous devriez être en mesure de lancer le projet.
Dans le doute, n'hésitez pas à lancer un symfony check:requirements
pour vous assurer que tout est bon.
Pour le reste, le README
du projet devrait vous accompagner pour le setup (n'oubliez pas de lancer les migrations Doctrine).
Prenez le temps de découvrir et de vous familiariser avec l'application.
Important
tests/requests.http
dans lequel des requêtes HTTP sont prêtes à l'emploi (attention à changer les ID quand nécessaire) pour utiliser et tester l'API. Votre IDE devrait vous permettre de lancer ces requêtes directement depuis le fichier.
Pour un premier tour d'horizon, visitez la page d'accueil, puis créer votre première Carte. Une fois cela fait, elle apparaît dans votre liste de Cartes.
À présent, on vous être notifié chaque jour des Cartes auxquelles on doit répondre. Pour cela, imaginons une tâche cron qui appellera la commande DailyTestNotifCommand
.
Pour cela, on le fait manuellement via le terminal:
$ bin/console app:daily-test-notif
Et voilà, un email est envoyé ! Pour le consulter, rendez-vous sur , l'adresse Mailpit (automatiquement lancé via docker compose).
Un lien vous redirigera vers une page où seules les cartes du jour vous seront proposées pour y répondre !
Vous trouverez également une Entité Card
dont voici les propriétés:
$question
: La question associée à la Carte$answer
: La réponse$initialTestDate
: La date initiale à laquelle la question nous est soumise$delay
: Le délai entre la $initialTestDate
et la prochaine date de test (on incrémente cette valeur à chaque fois qu'on répond correctement à la question)$active
: La Carte est-elle activée ou désactivéePour le moment, notre entité utilise Doctrine pour se brancher à la base de données:
<?php declare(strict_types=1);
namespace App\Entity;
use App\Repository\CardRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: CardRepository::class)]
class Card
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private string $question;
#[ORM\Column(length: 255)]
private string $answer;
#[ORM\Column(type: Types::DATE_MUTABLE, nullable: true)]
private ?\DateTimeInterface $initialTestDate = null;
#[ORM\Column]
private ?bool $active = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $image = null;
#[ORM\Column(options: ['default' => 0])]
private int $delay = 0;
public function getId(): ?int
{
return $this->id;
}
public function getQuestion(): string
{
return $this->question;
}
public function setQuestion(string $question): self
{
$this->question = $question;
return $this;
}
public function getAnswer(): string
{
return $this->answer;
}
public function setAnswer(string $answer): self
{
$this->answer = $answer;
return $this;
}
public function getInitialTestDate(): ?\DateTimeInterface
{
return $this->initialTestDate;
}
public function setInitialTestDate(?\DateTimeInterface $initialTestDate): self
{
$this->initialTestDate = $initialTestDate;
return $this;
}
public function isActive(): ?bool
{
return $this->active;
}
public function setActive(bool $active): self
{
$this->active = $active;
return $this;
}
public function getImage(): ?string
{
return $this->image;
}
public function setImage(?string $image): self
{
$this->image = $image;
return $this;
}
public function getDelay(): int
{
return $this->delay;
}
public function setDelay(int $delay): self
{
$this->delay = $delay;
return $this;
}
}
C'est en jouant avec ces simples propriétés que notre Leitner Box est fonctionnelle.
On dispose d'un CRUD dans le Controller, ainsi que d'une méthode pour soumettre des réponses aux questions.
Visitez le dossier Service
et notamment la classe HandleCardSolving
pour découvrir la logique qui se cache sous le capot.
Vous dévouvrirez également la constante TEST_DELAY
, qui représente le délai, en nombre de jours, entre chaque compartiments. J'ai choisi ces délais arbitrairement.
Ici, si on répond bon à chaque fois, on répondra aux questions au J1, puis J+3, J+7, etc...
Je vous laisse explorer le repo pour plus de détails sur cette version de l'app sans Clean Archi !
Important
refacto-clean
pour découvrir le projet entièrement réécrit en Clean.
Auteur(s)
Arthur Jacquemin
Développeur de contenu + ou - pertinent @ ElevenLabs_🚀
Vous souhaitez en savoir plus sur le sujet ?
Organisons un échange !
Notre équipe d'experts répond à toutes vos questions.
Nous contacterDécouvrez nos autres contenus dans le même thème
Ce guide présente Apache Iceberg, un format de table moderne pour les données volumineuses, la gestion des versions et des performances optimisées.
Plongez dans le monde des AST et découvrez comment cette structure de données fondamentale révolutionne le développement moderne.
Retour sur les deux journées de conférences pour la SymfonyLive Paris 2025 à Paris.