Aller au contenu principal

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, CancellationToken sur les méthodes), d'autres sont des décisions architecturales (pas de retry intégré pour rester prévisible).