Lyon
CPE - 16/05/2025
📍
Lyon
CPE - 16/05/2025
📍
Lead Developer
Symfony
@NicolasJourdan_
NicolasJourdan
nicolas-jourdan.medium.com
@afup@NicolasJourdan_@afup@NicolasJourdan_@afup@NicolasJourdan_🍊
🍍
🍇
🍍
🍇
🍍
🍇
@afup@NicolasJourdan_Nom
Taille
Capacité
SKU
Couleur
Tags
@afup@NicolasJourdan_[
{
"name": "YouPhone 16 Pro Max",
"size": 6.7,
"capacity": 512,
"sku": "YOUPHONE_16_PRO_MAX_ZXC",
"color": "black",
"tags": [
"premium",
"5G",
"waterproof"
]
}
]POST /api/phones
@afup@NicolasJourdan_[
{
"name": "YouPhone 16 Pro Max",
"size": 6.7,
"capacity": 512,
"sku": "YOUPHONE_16_PRO_MAX_ZXC",
"color": "black",
"tags": [
"premium",
"5G",
"waterproof"
]
}
]POST
🍐
🍋
🍇
@afup@NicolasJourdan_@afup@NicolasJourdan_@afup@NicolasJourdan_@afup@NicolasJourdan_🍐
@afup@NicolasJourdan_🍐
🍐
🍐
🍍
🍇
🍐
🍐
@afup@NicolasJourdan_Nom
Couleur
Taille
SKU
Famille
Capacité
Résistance
Connectivité
Prix
@afup@NicolasJourdan_[
{
"nom": "YouPhone 16 Pro Max",
"taille": 6.7,
"capacité": 512,
"code_produit": "YOUPHONE_16_PRO_MAX_ZXC",
"couleur": "black",
"connectivité": "5G",
"prix": 1499.90,
"famille": "pro",
"résistance": "IP68"
}
]GET
🍊
🍍
🍇
@afup@NicolasJourdan_[
{
"nom": "YouPhone 16 Pro Max",
"taille": 6.7,
"capacité": 512,
"code_produit": "YOUPHONE_16_PRO_MAX_ZXC",
"couleur": "black",
"connectivité": "5G",
"prix": 1499.90,
"famille": "pro",
"résistance": "IP68"
}
]GET /api/telephones
@afup@NicolasJourdan_[
{
"nom": "YouPhone 16 Pro Max",
"taille": 6.7,
"capacité": 512,
"code_produit": "YOUPHONE_16_PRO_MAX_ZXC",
"couleur": "black",
"connectivité": "5G",
"prix": 1499.90,
"famille": "pro",
"résistance": "IP68"
}
]GET /api/telephones
[
{
"name": "YouPhone 16 Pro Max",
"size": 6.7,
"capacity": 512,
"sku": "YOUPHONE_16_PRO_MAX_ZXC",
"color": "black",
"tags": [
"premium",
"5G",
"waterproof"
]
}
]POST /api/phones
@afup@NicolasJourdan_Délai serré
Récupération des données
Développements spécifiques (tags)
Mapping des champs
Faible volumétrie
@afup@NicolasJourdan_@afup@NicolasJourdan_| 🧃 | 1 L |
| 🍇 | 1,5 kg |
| 🍌 | 1 kg |
| 🍇 | 1,5 kg |
| 🧃 | 1 L |
| 🍇 | 1,5 kg |
| 🍌 | 1 kg |
[
{
"nom": "YouPhone 16 Pro Max",
"taille": 6.7,
"capacité": 512,
"code_produit": "YOUPHONE_16_PRO_MAX_ZXC",
"couleur": "black",
"connectivité": "5G",
"prix": 1499.90,
"famille": "pro",
"résistance": "IP68"
}
]GET
🍐[
{
"name": "YouPhone 16 Pro Max",
"size": 6.7,
"capacity": 512,
"sku": "YOUPHONE_16_PRO_MAX_ZXC",
"color": "black",
"tags": [
"premium",
"5G",
"waterproof"
]
}
]POST
🍊@afup@NicolasJourdan_AWS Glue
Talend Open Studio
Apache Airflow
@afup@NicolasJourdan_"Un connecteur est un module préconfiguré qui permet à un outil ETL de communiquer avec une source ou une destination."
@afup@NicolasJourdan_PostgreSQL
MongoDB
MySQL
S3
HubSpot
SalesForce
ElasticSearch
Google Analytics
AWS Glue
@afup@NicolasJourdan_AWS Glue
🍐Poire/Mandarine🍊
@afup@NicolasJourdan_| Critère | Outils existants | Projet interne |
|---|---|---|
| Temps de développement | Long si besoin de créer un connecteur (Python/Scala) | Rapide |
🍐Poire/Mandarine🍊
@afup@NicolasJourdan_| Critère | Outils existants | Projet interne |
|---|---|---|
| Temps de développement | Long si besoin de créer un connecteur (Python/Scala) | Rapide |
| Connectivité | Limitée | Facile |
🍐Poire/Mandarine🍊
@afup@NicolasJourdan_| Critère | Outils existants | Projet interne |
|---|---|---|
| Temps de développement | Long si besoin de créer un connecteur (Python/Scala) | Rapide |
| Connectivité | Limitée | Facile |
| Flexibilité (format, etc.) | Limitée | Totale |
🍐Poire/Mandarine🍊
@afup@NicolasJourdan_| Critère | Outils existants | Projet interne |
|---|---|---|
| Temps de développement | Long si besoin de créer un connecteur (Python/Scala) | Rapide |
| Connectivité | Limitée | Facile |
| Flexibilité (format, etc.) | Limitée | Totale |
| Évolutivité | Limitée | Facile |
🍐Poire/Mandarine🍊
@afup@NicolasJourdan_| Critère | Outils existants | Projet interne |
|---|---|---|
| Temps de développement | Long si besoin de créer un connecteur (Python/Scala) | Rapide |
| Connectivité | Limitée | Facile |
| Flexibilité (format, etc.) | Limitée | Totale |
| Évolutivité | Limitée | Facile |
| Scalabilité | Optimisée | Limitée |
🍐Poire/Mandarine🍊
@afup@NicolasJourdan_| Critère | Outils existants | Projet interne |
|---|---|---|
| Temps de développement | Long si besoin de créer un connecteur (Python/Scala) | Rapide |
| Connectivité | Limitée | Facile |
| Flexibilité (format, etc.) | Limitée | Totale |
| Évolutivité | Limitée | Facile |
| Scalabilité | Optimisée | Limitée |
| Coût | Potentiellement élevé | Faible |
🍐Poire/Mandarine🍊
@afup@NicolasJourdan_| Critère | Outils existants | Projet interne |
|---|---|---|
| Temps de développement | Long si besoin de créer un connecteur (Python/Scala) | Rapide |
| Connectivité | Limitée | Facile |
| Flexibilité (format, etc.) | Limitée | Totale |
| Évolutivité | Limitée | Facile |
| Scalabilité | Optimisée | Limitée |
| Coût | Potentiellement élevé | Faible |
| Performance | Optimisée | Limitée |
🍐Poire/Mandarine🍊
@afup@NicolasJourdan_| Critère | Outils existants | Projet interne |
|---|---|---|
| Temps de développement | Long si besoin de créer un connecteur (Python/Scala) | Rapide |
| Connectivité | Limitée | Facile |
| Flexibilité (format, etc.) | Limitée | Totale |
| Évolutivité | Limitée | Facile |
| Scalabilité | Optimisée | Limitée |
| Coût | Potentiellement élevé | Faible |
| Performance | Optimisée | Limitée |
| Monitoring | Intégré | À développer manuellement |
🍐Poire/Mandarine🍊
@afup@NicolasJourdan_| Critère | Outils existants | Projet interne |
|---|---|---|
| Temps de développement | Long si besoin de créer un connecteur (Python/Scala) | Rapide |
| Connectivité | Limitée | Facile |
| Flexibilité (format, etc.) | Limitée | Totale |
| Évolutivité | Limitée | Facile |
| Scalabilité | Optimisée | Limitée |
| Coût | Potentiellement élevé | Faible |
| Performance | Optimisée | Limitée |
| Monitoring | Intégré | À développer manuellement |
| Sécurité | Intégrée | À gérer manuellement |
🍐Poire/Mandarine🍊
@afup@NicolasJourdan_| Critère | Outils existants | Projet interne |
|---|---|---|
| Temps de développement | Rapide | Rapide |
| Connectivité | Facile | Facile |
| Flexibilité (format, etc.) | Totale | Totale |
| Évolutivité | Facile | Facile |
| Scalabilité | Optimisée | Limitée |
| Coût | Potentiellement élevé | Faible |
| Performance | Optimisée | Limitée |
| Monitoring | Intégré | À développer manuellement |
| Sécurité | Intégrée | À gérer manuellement |
Avec un connecteur existant
@afup@NicolasJourdan_@afup@NicolasJourdan_@afup@NicolasJourdan_@afup@NicolasJourdan_🍐🍊@afup@NicolasJourdan_<?php
readonly class ProductFetcher
{
//...
public function fetch(): array
{
$response = $this->client->request('GET', $this->apiUrl, [
'auth_bearer' => $this->apiKey,
]);
if ($response->getStatusCode() !== 200) {
throw new \RuntimeException('Failed to fetch products');
}
return $response->toArray();
}
}
$pearProducts = [
[
"nom" => "YouPhone 16 Pro Max",
"taille" => 6.7,
"capacité" => 512,
"code_produit" => "YOUPHONE_16_PRO_MAX_ZXC",
"couleur" => "black",
"connectivité" => "5G",
"prix" => 1499.90,
"famille" => "pro",
"résistance" => "IP68"
]
];
@afup@NicolasJourdan_@afup@NicolasJourdan_🍐🍊$pearProducts = [
[
"nom" => "YouPhone 16 Pro Max",
"taille" => 6.7,
"capacité" => 512,
"code_produit" => "YOUPHONE_16_PRO_MAX_ZXC",
"couleur" => "black",
"connectivité" => "5G",
"prix" => 1499.90,
"famille" => "pro",
"résistance" => "IP68"
]
];
@afup@NicolasJourdan_<?php
readonly class ProductMapper
{
public function map(array $pearProducts): array
{
$mappedProducts = [];
foreach ($pearProducts as $pearProduct) {
$mappedProducts[] = [
"name" => $pearProduct["nom"],
"size" => $pearProduct["taille"],
// ...
'tags' => $this->resolveTags($pearProduct),
];
}
return $mappedProducts;
}
// ...
}
<?php
readonly class ProductMapper
{
//...
private function resolveTags(array $pearProduct): array
{
return \array_filter([
$pearProduct['connectivité'] ?? null, // 5G, 4G, 3G, etc.
//...
]);
}
}
@afup@NicolasJourdan_<?php
readonly class ProductMapper
{
//...
private function resolveTags(array $pearProduct): array
{
return \array_filter([
//...
(
isset($pearProduct['famille']) && $pearProduct['famille'] === 'pro'
&& isset($pearProduct['prix']) && $pearProduct['prix'] > 1000
) ? 'premium' : null,
//...
]);
}
}
@afup@NicolasJourdan_<?php
readonly class ProductMapper
{
//...
private function resolveTags(array $pearProduct): array
{
return \array_filter([
//...
(
isset($pearProduct['résistance'])
&& $this->extractResistance($pearProduct['résistance']) >= 67
) ? 'waterproof' : null,
]);
}
}
@afup@NicolasJourdan_$mandarineProducts = [
[
"name" => "YouPhone 16 Pro Max",
"size" => 6.7,
"capacity" => 512,
"sku" => "YOUPHONE_16_PRO_MAX_ZXC",
"color" => "black",
"tags" => [
"premium",
"5G",
"waterproof"
]
]
];
@afup@NicolasJourdan_$mandarineProducts = [
[
"name" => "YouPhone 16 Pro Max",
"size" => 6.7,
"capacity" => 512,
"sku" => "YOUPHONE_16_PRO_MAX_ZXC",
"color" => "black",
"tags" => [
"premium",
"5G",
"waterproof"
]
]
];
$pearProducts = [
[
"nom" => "YouPhone 16 Pro Max",
"taille" => 6.7,
"capacité" => 512,
"code_produit" => "YOUPHONE_16_PRO_MAX_ZXC",
"couleur" => "black",
"connectivité" => "5G",
"prix" => 1499.90,
"famille" => "pro",
"résistance" => "IP68"
]
];@afup@NicolasJourdan_@afup@NicolasJourdan_🍐🍊$pearProducts = [
[
"nom" => "YouPhone 16 Pro Max",
"taille" => 6.7,
"capacité" => 512,
"code_produit" => "YOUPHONE_16_PRO_MAX_ZXC",
"couleur" => "black",
"connectivité" => "5G",
"prix" => 1499.90,
"famille" => "pro",
"résistance" => "IP68"
]
];
$mandarineProducts = [
[
"name" => "YouPhone 16 Pro Max",
"size" => 6.7,
"capacity" => 512,
"sku" => "YOUPHONE_16_PRO_MAX_ZXC",
"color" => "black",
"tags" => [
"premium",
"5G",
"waterproof"
]
]
];
@afup@NicolasJourdan_<?php
readonly class ProductLoader
{
//...
public function load(array $mandarineProducts): void
{
$response = $this->client->request('POST', $this->apiUrl, [
'auth_bearer' => $this->apiKey,
'json' => $mandarineProducts,
]);
if ($response->getStatusCode() !== 201) {
throw new \RuntimeException('Failed to load products');
}
}
}
@afup@NicolasJourdan_🍐🍊$pearProducts = [
[
"nom" => "YouPhone 16 Pro Max",
"taille" => 6.7,
"capacité" => 512,
"code_produit" => "YOUPHONE_16_PRO_MAX_ZXC",
"couleur" => "black",
"connectivité" => "5G",
"prix" => 1499.90,
"famille" => "pro",
"résistance" => "IP68"
]
];
$mandarineProducts = [
[
"name" => "YouPhone 16 Pro Max",
"size" => 6.7,
"capacity" => 512,
"sku" => "YOUPHONE_16_PRO_MAX_ZXC",
"color" => "black",
"tags" => [
"premium",
"5G",
"waterproof"
]
]
];
@afup@NicolasJourdan_<?php
readonly class ProductJob
{
public function __construct(
private ProductFetcher $productFetcher,
private ProductMapper $productMapper,
private ProductLoader $productLoader,
) {
}
public function __invoke(): void
{
$products = $this->productFetcher->fetch();
$mappedProducts = $this->productMapper->map($products);
$this->productLoader->load($mappedProducts);
}
}@afup@NicolasJourdan_@afup@NicolasJourdan_@afup@NicolasJourdan_Système de logs
Nouveau flux en entrée / sortie
@afup@NicolasJourdan_Données manquantes
@afup@NicolasJourdan_<?php
readonly class ProductMapper
{
private OptionsResolver $resolver;
public function __construct()
{
$this->resolver = new OptionsResolver();
$this->configureOptions($this->resolver);
}
//...
}
<?php
readonly class ProductMapper
{
//...
public function configureOptions(OptionsResolver $resolver): void
{
$resolver
->setRequired(['nom', 'taille', 'capacité', 'code_produit', 'couleur'])
->setDefined(['connectivité', 'famille', 'prix', 'résistance'])
->setAllowedTypes('nom', 'string')
->setAllowedTypes('taille', 'float')
->setAllowedTypes('capacité', 'int')
->setAllowedTypes('code_produit', 'string')
->setAllowedTypes('couleur', 'string')
//...
;
}
}
<?php
readonly class ProductMapper
{
//...
public function map(array $pearProducts): array
{
$mappedProducts = [];
foreach ($pearProducts as $pearProduct) {
$pearProduct = $this->resolver->resolve($pearProduct);
$mappedProducts[] = [
"name" => $pearProduct["nom"],
//...
];
}
return $mappedProducts;
}
//...
}
@afup@NicolasJourdan_<?php
use Psr\Log\LoggerInterface;
readonly class ProductFetcher
{
public function __construct(
private LoggerInterface $logger,
//...
) {
}
//...
}
<?php
readonly class ProductFetcher
{
//...
public function fetch(): array
{
$this->logger->info('Starting product fetch from external API', [
'url' => $this->apiUrl,
]);
//...
}
}
<?php
readonly class ProductFetcher
{
//...
public function fetch(): array
{
//...
try {
$response = $this->client->request('GET', $this->apiUrl, [
'auth_bearer' => $this->apiKey,
]);
} catch (\Throwable $e) {
$this->logger->error('API request failed', [
'exception' => $e,
'url' => $this->apiUrl,
]);
throw new \RuntimeException('API request failed');
}
//...
}
}
<?php
readonly class ProductFetcher
{
//...
public function fetch(): array
{
//...
$statusCode = $response->getStatusCode();
if ($statusCode !== 200) {
$this->logger->error('Product API returned unexpected status code', [
'status_code' => $statusCode,
]);
throw new \RuntimeException('Unexpected status code');
}
//...
}
}
<?php
readonly class ProductFetcher
{
//...
public function fetch(): array
{
//...
$products = $response->toArray();
$this->logger->info('Product data successfully fetched', [
'product_count' => \count($products),
]);
return $products;
}
}
<?php
readonly class ProductMapper
{
//...
public function map(array $pearProducts): array
{
$mappedProducts = [];
foreach ($pearProducts as $pearProduct) {
try {
$resolved = $this->resolver->resolve($pearProduct);
//...
} catch (ExceptionInterface $e) {
$this->logger->error('Failed to resolve product', [
'error' => $e->getMessage(),
'product' => $pearProduct,
]);
continue;
}
}
//...
}
//...
}
<?php
readonly class ProductLoader
{
//...
public function load(array $mandarineProducts): void
{
$this->logger->info('Starting product load to internal API', [
'url' => $this->apiUrl,
'product_count' => \count($mandarineProducts),
]);
//...
}
}
<?php
readonly class ProductLoader
{
//...
public function load(array $mandarineProducts): void
{
//...
try {
$response = $this->client->request('POST', $this->apiUrl, [
'auth_bearer' => $this->apiKey,
'json' => $mandarineProducts,
]);
} catch (\Throwable $e) {
$this->logger->error('API request failed during product load', [
'exception' => $e,
'url' => $this->apiUrl,
]);
throw new \RuntimeException('Failed to load products');
}
//...
}
}
<?php
readonly class ProductLoader
{
//...
public function load(array $mandarineProducts): void
{
//...
$statusCode = $response->getStatusCode();
if ($statusCode !== 201) {
$this->logger->error('Unexpected status code', [
'status_code' => $statusCode,
]);
throw new \RuntimeException('Failed to load products');
}
$this->logger->info('Product load completed successfully');
}
}
@afup@NicolasJourdan_<?php
interface ProductFetcherInterface
{
public function fetch(): array;
}
readonly class ProductFetcher implements ProductFetcherInterface {...}
@afup@NicolasJourdan_<?php
interface ProductMapperInterface
{
public function map(array $pearProducts): array;
}
readonly class ProductMapper implements ProductMapperInterface {...}
@afup@NicolasJourdan_<?php
interface ProductLoaderInterface
{
public function load(array $mandarineProducts): void;
}
readonly class ProductLoader implements ProductLoaderInterface {...}@afup@NicolasJourdan_<?php
readonly class ProductJob
{
public function __construct(
private ProductFetcherInterface $productFetcher,
private ProductMapperInterface $productMapper,
private ProductLoaderInterface $productLoader,
) {
}
public function __invoke(): void
{
$products = $this->productFetcher->fetch();
$mappedProducts = $this->productMapper->map($products);
$this->productLoader->load($mappedProducts);
}
}@afup@NicolasJourdan_Performances
Volume de données
...
@afup@NicolasJourdan_@afup@afup@NicolasJourdan_Bien comprendre le besoin métier et les contraintes techniques
Volume ?
Charge ?
Performances ?
Flux ?
@afup@NicolasJourdan_Bien comprendre le besoin métier et les contraintes techniques
Choisir la solution la plus adaptée
@afup@NicolasJourdan_Bien comprendre le besoin métier et les contraintes techniques
Choisir la solution la plus adaptée
Documenter (mappings, schémas, flux, etc.)
@afup@NicolasJourdan_Tests et validation des données
Gestion des erreurs et des cas limites
Logs et alerting
Flexibilité et évolutivité
@afup@NicolasJourdan_@afup@NicolasJourdan_