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 breakerPolly avec IHttpClientFactory (voir idempotence)
Pagination automatiqueEndpoints non paginés ; faites un client-side sliding window via Apres/Avant
Cache localIMemoryCache / IDistributedCache de votre côté (exemple plus haut)
OpenTelemetry / tracesActivez l'instrumentation HttpClient standard (AddHttpClientInstrumentation)
Streaming / chunked uploadCharge utile JSON en mémoire uniquement
CancellationToken sur les méthodes clientLe HttpClient sous-jacent respecte les annulations système, mais il n'y a pas de paramètre explicite
File upload binaireAucun 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 webhooksConfiguration manuelle par l'admin MCM
Génération OpenAPI clientLe SDK est écrit à la main et reste la source canonique
await using / IAsyncDisposable sur les clientsLes 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).