Bonnes pratiques
Recommandations pour une intégration optimale avec MCM.ApiProxy.
Architecture
Créer une couche de service
Ne pas injecter les clients MCM directement dans les contrôleurs. Créez une couche de service :
// BON - Couche de service
public class EmployeService
{
private readonly IEmployesClient _employesClient;
public EmployeService(IEmployesClient employesClient)
{
_employesClient = employesClient;
}
public async Task<EmployeDto?> GetEmployeAsync(string id)
{
var result = await _employesClient.GetEmployeById(id);
return result.IsError ? null : MapToDto(result.Value);
}
}
// Contrôleur utilise le service
public class EmployesController : ControllerBase
{
private readonly EmployeService _employeService;
// ...
}
Utiliser l'injection de dépendances
Toujours injecter les clients via le constructeur :
// BON
public class MonService
{
private readonly IEmployesClient _employesClient;
public MonService(IEmployesClient employesClient)
{
_employesClient = employesClient;
}
}
// MAUVAIS - Service locator
public class MonService
{
public void DoSomething(IServiceProvider services)
{
var client = services.GetService<IEmployesClient>();
}
}
Performance
Éviter les appels en boucle
// MAUVAIS - N appels API
foreach (var id in employeIds)
{
var result = await _employesClient.GetEmployeById(id);
// ...
}
// BON - Un seul appel
var allEmployes = await _employesClient.GetAllEmployes();
var filtered = allEmployes.Value.Where(e => employeIds.Contains(e.IdExterne));
Utiliser la synchronisation pour les lots
// BON - Un seul appel pour plusieurs opérations
await _syncClient.Sync(
employes,
employeurs,
objetsConsentement);
Mettre en cache quand approprié
public class CachedFormulaireService
{
private readonly IFormulaireClient _formulaireClient;
private readonly IMemoryCache _cache;
public async Task<List<B2BFormulaireItem>> GetFormulairesAsync()
{
return await _cache.GetOrCreateAsync("formulaires", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
var result = await _formulaireClient.GetAllFormulaires();
return result.IsError ? new List<B2BFormulaireItem>() : result.Value.ToList();
});
}
}
Gestion des erreurs
Logger systématiquement
public async Task<bool> CreateEmployeAsync(B2BUpdateEmployeDto employe)
{
using var _ = _logger.BeginScope(new { EmployeId = employe.IdExterne });
var result = await _employesClient.AddEmploye(employe);
if (result.IsError)
{
_logger.LogError(
"Échec création employé - Code: {ErrorCode}, Description: {ErrorDescription}",
result.FirstError.Code,
result.FirstError.Description);
return false;
}
_logger.LogInformation("Employé créé avec succès");
return true;
}
Ne pas ignorer les erreurs
// MAUVAIS - Erreur silencieuse
var result = await _employesClient.GetEmployeById(id);
return result.Value; // Exception si erreur!
// BON - Gestion explicite
var result = await _employesClient.GetEmployeById(id);
if (result.IsError)
{
throw new EmployeNotFoundException(id);
}
return result.Value;
Données
Valider avant d'envoyer
public async Task<ErrorOr<Success>> CreateEmployeAsync(CreateEmployeRequest request)
{
// Validation locale d'abord
if (string.IsNullOrWhiteSpace(request.IdExterne))
{
return Error.Validation("IdExterne.Required", "IdExterne est obligatoire");
}
if (!IsValidEmail(request.Courriel))
{
return Error.Validation("Courriel.Invalid", "Format de courriel invalide");
}
// Puis appel API
var dto = MapToDto(request);
return await _employesClient.AddEmploye(dto);
}
Utiliser des identifiants stables
// BON - Identifiant stable basé sur votre système
var employe = new B2BUpdateEmployeDto
{
IdExterne = $"HRIS-{employee.HrisId}", // Basé sur votre système RH
// ...
};
// MAUVAIS - Identifiant qui peut changer
var employe = new B2BUpdateEmployeDto
{
IdExterne = employee.Email, // L'email peut changer!
// ...
};
Tests
Mocker les clients dans les tests
public class EmployeServiceTests
{
[Fact]
public async Task GetEmploye_ReturnsEmploye_WhenFound()
{
// Arrange
var mockClient = new Mock<IEmployesClient>();
mockClient
.Setup(c => c.GetEmployeById("EMP-123"))
.ReturnsAsync(new B2BEmployeItem { IdExterne = "EMP-123", Nom = "Test" });
var service = new EmployeService(mockClient.Object);
// Act
var result = await service.GetEmployeAsync("EMP-123");
// Assert
Assert.NotNull(result);
Assert.Equal("Test", result.Nom);
}
}
Sécurité
Protéger la clé API
- Utilisez les secrets utilisateur en développement
- Utilisez Azure Key Vault ou équivalent en production
- Ne loggez jamais la clé API
- Faites la rotation régulièrement
Valider les entrées utilisateur
// Avant d'utiliser un ID fourni par l'utilisateur
public async Task<IActionResult> GetEmploye(string id)
{
if (string.IsNullOrWhiteSpace(id) || id.Length > 100)
{
return BadRequest("ID invalide");
}
// Seulement après validation
var result = await _employeService.GetEmployeAsync(id);
// ...
}
Ce que le SDK ne fait pas (et pourquoi)
Le SDK est volontairement mince. Plusieurs choses que vous pourriez attendre n'existent pas — soit parce qu'elles sont mieux faites par votre stack, soit parce que l'API n'en a pas besoin :
| Fonctionnalité | Présent ? | À utiliser à la place |
|---|---|---|
| Retry / circuit breaker | ❌ | Polly avec IHttpClientFactory (voir idempotence) |
| Pagination automatique | ❌ | Endpoints non paginés ; faites un client-side sliding window via Apres/Avant |
| Cache local | ❌ | IMemoryCache / IDistributedCache de votre côté (exemple plus haut) |
| OpenTelemetry / traces | ❌ | Activez l'instrumentation HttpClient standard (AddHttpClientInstrumentation) |
| Streaming / chunked upload | ❌ | Charge utile JSON en mémoire uniquement |
CancellationToken sur les méthodes client | ❌ | Le HttpClient sous-jacent respecte les annulations système, mais il n'y a pas de paramètre explicite |
| File upload binaire | ❌ | Aucun endpoint B2B n'attend de binaire à ce jour |
| Webhook émission (publier vers MCM) | ❌ | MCM ne reçoit pas — c'est lui qui émet vers vous |
| Abonnement / gestion des webhooks | ❌ | Configuration manuelle par l'admin MCM |
| Génération OpenAPI client | ❌ | Le SDK est écrit à la main et reste la source canonique |
await using / IAsyncDisposable sur les clients | ❌ | Les clients sont gérés par DI — pas besoin |
Si l'un de ces manques bloque votre cas d'usage, ouvrez un ticket. Plusieurs sont planifiés (OpenTelemetry,
CancellationTokensur les méthodes), d'autres sont des décisions architecturales (pas de retry intégré pour rester prévisible).