L’intégration de services externes, publics ou privés, via des API est une tâche courante dans le développement Laravel. Si elle paraît simple au départ, cette intégration peut rapidement devenir difficile à maintenir, à tester, et à faire évoluer proprement sans une architecture claire.
Pour remédier à cela, l’approche des modules de service propose une structure modulaire, réutilisable et testable, s’appuyant sur des patterns éprouvés (Repository, Factory, DTO) et des bonnes pratiques Laravel.
Problème courant : une intégration directe dans les contrôleurs
Prenons l’exemple d’un service météo interrogé via une API. Il est fréquent de voir l’appel direct à l’API réalisé dans le contrôleur :
namespace App\\Http\\Controllers;
use Illuminate\\Support\\Facades\\Http;
class WeatherController extends Controller
{
public function getWeather($city)
{
$response = Http::get('<https://api.weatherapi.com/v1/current.json>', [
'key' => config('services.weatherapi.key'),
'q' => $city,
]);
if ($response->failed()) {
return response()->json(['error' => 'Impossible de récupérer les données météo'], 500);
}
$weather = $response->json();
return response()->json([
'city' => $weather['location']['name'],
'temperature' => $weather['current']['temp_c'],
'condition' => $weather['current']['condition']['text']
]);
}
}
Bien que fonctionnelle, cette approche présente plusieurs limites :
- Duplication de la logique : en cas de réutilisation ailleurs, le code est copié, multipliant les points de rupture possibles.
- Couplage fort : les contrôleurs dépendent directement de l’API.
- Tests difficiles : le test unitaire nécessite de simuler l’appel HTTP à chaque fois.
- Absence d’abstraction : aucune structuration claire des données retournées.
- Gestion des erreurs dispersée : chaque point d’appel nécessite une logique d’erreur propre.
Étape 1 : structuration du service
La création d’un dossier Services
dans app/
permet de regrouper la logique métier liée à un service externe. Chaque service (ex. Weather
) peut contenir :
Repositories
: logique métier et appels externesDTOs
: structure des donnéesFacades
: points d’entrée simplifiésProviders
: injection de dépendancesExceptions
: gestion d’erreurs dédiée
Étape 2 : le Repository
Ce composant centralise la logique d’appel à l’API.
namespace App\\Services\\Weather\\Repositories;
use Illuminate\\Support\\Facades\\Http;
class WeatherRepository
{
public function getCurrentWeather($city)
{
$response = Http::get('<https://api.weatherapi.com/v1/current.json>', [
'key' => config('services.weatherapi.key'),
'q' => $city,
]);
if ($response->failed()) {
throw new \\Exception('Impossible de récupérer les données météo');
}
return $response->json();
}
}
Étape 3 : le DTO (Data Transfer Object)
Le DTO permet de manipuler les données de manière structurée.
namespace App\\Services\\Weather\\DTOs;
class WeatherData
{
public $city;
public $temperature;
public $condition;
public function __construct(array $data)
{
$this->city = $data['location']['name'];
$this->temperature = $data['current']['temp_c'];
$this->condition = $data['current']['condition']['text'];
}
}
Étape 4 : l’Exception personnalisé
Permet de centraliser la gestion des erreurs spécifiques à un service.
namespace App\\Services\\Weather\\Exceptions;
use Exception;
class WeatherServiceException extends Exception
{
//
}
Étape 5 : le Service Provide
Configure l’injection du repository dans le conteneur de services Laravel.
namespace App\\Services\\Weather\\Providers;
use Illuminate\\Support\\ServiceProvider;
use App\\Services\\Weather\\Repositories\\WeatherRepository;
class WeatherServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton(WeatherRepository::class, function ($app) {
return new WeatherRepository();
});
}
}
Étape 6 : la Facade
Fournit une interface simple pour accéder au service.
namespace App\\Services\\Weather\\Facades;
use Illuminate\\Support\\Facades\\Facade;
class Weather extends Facade
{
protected static function getFacadeAccessor()
{
return 'App\\Services\\Weather\\Repositories\\WeatherRepository';
}
}
Étape 7 : utilisation dans un contrôleur
Avec tous les composants en place, l’appel dans le contrôleur est propre, simple et testable :
namespace App\\Http\\Controllers;
use App\\Services\\Weather\\Facades\\Weather;
use App\\Services\\Weather\\DTOs\\WeatherData;
class WeatherController extends Controller
{
public function getWeather($city)
{
try {
$data = Weather::getCurrentWeather($city);
$weather = new WeatherData($data);
return response()->json([
'city' => $weather->city,
'temperature' => $weather->temperature,
'condition' => $weather->condition
]);
} catch (\\Exception $e) {
return response()->json(['error' => 'Impossible de récupérer les données météo'], 500);
}
}
}
Pour aller plus loin
Afin d’accélérer la mise en place de cette architecture, un package Laravel est disponible :
bash
CopierModifier
composer require shreifelagamy/laravel-service-modules
Ce package permet de générer automatiquement la structure complète d’un module de service via des commandes Artisan.
Ecrit par
Alyson Paya
Partager l'article :
Un site vitrine ? e-commerce ? une application ?