Développement Backend avec Dart
Dart Backend
Section titled “Dart Backend”Le développement backend avec Dart offre plusieurs frameworks et outils puissants pour créer des serveurs robustes et performants :
- Shelf - Framework minimal et composable
- Shelf router - Routage pour Shelf
- DartFrog - Framework web moderne et rapide
- Serverpod - Framework backend complet avec ORM
- Celest - Plateforme cloud native pour Dart
Shelf Framework
Section titled “Shelf Framework”Shelf est un framework web minimal et composable pour Dart. Il utilise une architecture basée sur les middlewares et fournit une API simple pour gérer les requêtes HTTP.
Shelf Router
Section titled “Shelf Router”Le package shelf_router permet de définir facilement des routes et des gestionnaires pour votre application web.
import 'package:shelf_router/shelf_router.dart';import 'package:shelf/shelf.dart';import 'package:shelf/shelf_io.dart' as io;
var app = Router();
app.get('/hello', (Request request) { return Response.ok('hello-world');});
app.get('/user/<user>', (Request request, String user) { return Response.ok('hello $user');});
var server = await io.serve(app, 'localhost', 8080);Shelf Router Generator
Section titled “Shelf Router Generator”Shelf propose également un générateur de code pour créer des routes de manière déclarative avec des annotations :
import 'package:shelf/shelf.dart';import 'package:shelf_router/shelf_router.dart';
part 'userservice.g.dart'; // generated with 'pub run build_runner build'
class UserService { final DatabaseConnection connection;
UserService(this.connection);
@Route.get('/users/') Future<Response> listUsers(Request request) async { return Response.ok('["user1"]'); }
@Route.get('/users/<userId>') Future<Response> fetchUser(Request request, String userId) async { if (userId == 'user1') { return Response.ok('user1'); } return Response.notFound('no such user'); }
// Create router using the generate function defined in 'userservice.g.dart'. Router get router => _$UserServiceRouter(this);}
void main() async { // You can setup context, database connections, cache connections, email // services, before you create an instance of your service. var connection = await DatabaseConnection.connect('localhost:1234');
// Create an instance of your service, using one of the constructors you've // defined. var service = UserService(connection);
// Service request using the router, note the router can also be mounted. var router = service.router; var server = await io.serve(router.handler, 'localhost', 8080);}DartFrog
Section titled “DartFrog”DartFrog est un framework web moderne développé par Very Good Ventures. Il se concentre sur la performance, la simplicité et la productivité des développeurs.
Caractéristiques principales :
- Routage basé sur les fichiers
- Middlewares intégrés
- Support du hot reload
- Génération automatique de documentation API
- Performance optimisée
Serverpod
Section titled “Serverpod”Serverpod est un framework backend complet qui inclut :
Fonctionnalités :
- ORM intégré avec génération automatique de code
- Authentification et autorisation
- Cache Redis intégré
- Support des WebSockets
- Monitoring et logging avancés
- Déploiement cloud facilité
Avantages :
- Type safety de bout en bout
- Génération automatique du client
- Scaling horizontal simple
- Intégration native avec PostgreSQL
Celest
Section titled “Celest”Celest est une plateforme cloud native spécialement conçue pour les applications Dart et Flutter.
Fonctionnalités :
- Déploiement serverless automatique
- Authentification intégrée
- Base de données managed
- APIs générées automatiquement
- Intégration CI/CD
Patterns d’Architecture Backend
Section titled “Patterns d’Architecture Backend”Architecture en Couches
Section titled “Architecture en Couches”// Couche de données (Repository Pattern)abstract class UserRepository { Future<User?> findById(String id); Future<List<User>> findAll(); Future<void> save(User user); Future<void> delete(String id);}
class DatabaseUserRepository implements UserRepository { final DatabaseConnection db;
DatabaseUserRepository(this.db);
@override Future<User?> findById(String id) async { // Implémentation base de données }
// ... autres méthodes}
// Couche service (Business Logic)class UserService { final UserRepository repository;
UserService(this.repository);
Future<User?> getUserById(String id) async { return await repository.findById(id); }
Future<void> createUser(CreateUserRequest request) async { // Validation métier final user = User( id: generateId(), name: request.name, email: request.email, );
await repository.save(user); }}
// Couche présentation (Controllers)class UserController { final UserService userService;
UserController(this.userService);
@Route.get('/users/<id>') Future<Response> getUser(Request request, String id) async { try { final user = await userService.getUserById(id); if (user == null) { return Response.notFound('User not found'); } return Response.ok(jsonEncode(user.toJson())); } catch (e) { return Response.internalServerError(body: 'Server error'); } }}Dependency Injection
Section titled “Dependency Injection”// Container DI simpleclass DIContainer { final Map<Type, dynamic> _services = {};
void register<T>(T service) { _services[T] = service; }
T get<T>() { final service = _services[T]; if (service == null) { throw Exception('Service $T not registered'); } return service as T; }}
// Configuration des dépendancesvoid setupDependencies(DIContainer container) { container.register<DatabaseConnection>( DatabaseConnection.create('localhost:5432') );
container.register<UserRepository>( DatabaseUserRepository(container.get<DatabaseConnection>()) );
container.register<UserService>( UserService(container.get<UserRepository>()) );}Middleware Pattern
Section titled “Middleware Pattern”// Middleware d'authentificationMiddleware authMiddleware() { return (Handler innerHandler) { return (Request request) async { final authHeader = request.headers['authorization']; if (authHeader == null || !isValidToken(authHeader)) { return Response.forbidden('Access denied'); }
// Ajouter l'utilisateur au contexte de la requête final updatedRequest = request.change( context: {'user': getUserFromToken(authHeader)} );
return await innerHandler(updatedRequest); }; };}
// Middleware de loggingMiddleware loggingMiddleware() { return logRequests(logger: (message, isError) { if (isError) { print('ERROR: $message'); } else { print('INFO: $message'); } });}
// Utilisation des middlewaresfinal handler = Pipeline() .addMiddleware(corsHeaders()) .addMiddleware(loggingMiddleware()) .addMiddleware(authMiddleware()) .addHandler(router);Error Handling Pattern
Section titled “Error Handling Pattern”// Result pattern pour la gestion d'erreursabstract class Result<T> { const Result();}
class Success<T> extends Result<T> { final T value; const Success(this.value);}
class Failure<T> extends Result<T> { final String error; final int? code; const Failure(this.error, [this.code]);}
// Utilisation dans les servicesclass UserService { Future<Result<User>> createUser(CreateUserRequest request) async { try { // Validation if (request.email.isEmpty) { return const Failure('Email is required', 400); }
// Logique métier final user = User( id: generateId(), email: request.email, name: request.name, );
await repository.save(user); return Success(user);
} catch (e) { return Failure('Failed to create user: $e', 500); } }}
// Utilisation dans les controllers@Route.post('/users')Future<Response> createUser(Request request, String body) async { final createRequest = CreateUserRequest.fromJson(jsonDecode(body)); final result = await userService.createUser(createRequest);
return switch (result) { Success(:final value) => Response.ok(jsonEncode(value.toJson())), Failure(:final error, :final code) => Response( code ?? 500, body: jsonEncode({'error': error}), ), };}Bonnes Pratiques
Section titled “Bonnes Pratiques”Structure de Projet
Section titled “Structure de Projet”lib/├── controllers/ # Couche présentation│ ├── user_controller.dart│ └── auth_controller.dart├── services/ # Logique métier│ ├── user_service.dart│ └── auth_service.dart├── repositories/ # Couche données│ ├── user_repository.dart│ └── interfaces/├── models/ # Modèles de données│ ├── user.dart│ └── auth_token.dart├── middleware/ # Middlewares personnalisés│ ├── auth_middleware.dart│ └── cors_middleware.dart├── config/ # Configuration│ ├── database.dart│ └── app_config.dart└── main.dart # Point d'entréeConfiguration et Variables d’Environnement
Section titled “Configuration et Variables d’Environnement”class AppConfig { static String get databaseUrl => Platform.environment['DATABASE_URL'] ?? 'localhost:5432';
static String get jwtSecret => Platform.environment['JWT_SECRET'] ?? 'dev-secret';
static int get port => int.parse(Platform.environment['PORT'] ?? '8080');
static bool get isDevelopment => Platform.environment['ENVIRONMENT'] == 'development';}Tests Backend
Section titled “Tests Backend”// Test d'intégrationvoid main() { group('User API', () { late TestServer server;
setUp(() async { server = await TestServer.start(); });
tearDown(() async { await server.stop(); });
test('should create user', () async { final response = await server.post( '/users', body: jsonEncode({ 'name': 'John Doe', 'email': 'john@example.com', }), );
expect(response.statusCode, 201); final user = jsonDecode(response.body); expect(user['name'], 'John Doe'); }); });}Ces frameworks et patterns permettent de construire des backends Dart robustes, maintenables et performants, que ce soit pour des APIs REST, des applications en temps réel ou des microservices.