Contexte Projet ContribFactory (CACIB) : développement d’une API.NET 8 pour gérer un workflow de contribution de prix. Profil recherché Développeur .NET/C# senior, autonome, capable de faire évoluer une API dans une architecture structurée (Clean Architecture / DDD), avec une bonne maîtrise du CI/CD.
Une connaissance du métier de la Contribution est un plus.
Compétences clés C# /.NET 8+, ASP.NET Core API Clean
Architecture, DDD EF Core, SQL Server GitLab CI/CD
Tests : xUnit, Moq, FluentAssertions
Bonus appréciés Channels, Resilience, Authentification,
FluentValidation AWS, Kubernetes
Intégrations providers : Bloomberg, Refinitiv, Six Angular,
Agile / Scrum
Connaissance métier :ISIN, RIC, tickers
Soft skills Autonomie, sens des responsabilités
Communication claire avec les équipes techniques et métiers Capacité à
reprendre un existant structuré et à le faire évoluer Esprit collaboratif,
partage de connaissances Modalités
Mission longue durée (3 ans) – Montrouge – 3 jours sur site.
Compétences fonctionnelles requises
ISIN, RIC, tickers Equity Derivatives
Compétences techniques requises
C# /.NET 8+, ASP.NET Core API Clean
Architecture, DDD EF Core, SQL Server GitLab CI/CD
Quel est le principal enjeu business du workflow de
contribution de prix ?
EF Core est-il utilisé en Code First ou Database First ?
Quelle version de SQL Server est utilisée ?
Y a-t-il des contraintes de performance ou de volumétrie ?
L’authentification est-elle basée sur JWT, OAuth2, Azure AD,
autre ?
Les intégrations se font-elles via API REST, fichiers, MQ,
SFTP ou SDK propriétaire ?
Quelle est la dette technique
principale à traiter ?
1. Question : Quels sont les points forts /
nouveautés de .NET 8 ?
Réponse (courte en français) :
.NET 8 est une version LTS (Long Term Support) qui
apporte principalement :
- De
meilleures performances et une consommation mémoire réduite.
- Des
améliorations pour les Minimal APIs et ASP.NET Core.
- Une
meilleure prise en charge du Cloud Native, de Docker et Kubernetes.
- L’optimisation
de l’AOT (Ahead-Of-Time Compilation) pour des applications plus
rapides au démarrage.
- Des
améliorations de Blazor avec le mode de rendu hybride.
- De
nouvelles fonctionnalités dans C# 12.
- Des
améliorations de productivité pour EF Core 8.
2. Question : Qu'est-ce que la Clean
Architecture ?
Réponse (courte en français) :
La Clean Architecture est un modèle d'architecture
qui sépare clairement les responsabilités de l'application afin de la rendre
plus maintenable, testable et évolutive.
Elle est généralement organisée en plusieurs couches :
- Domain
: contient les règles métier et les entités.
- Application
: contient les cas d’usage et la logique applicative.
- Infrastructure
: gère les accès aux bases de données, APIs externes, fichiers, etc.
- Presentation/API
: expose les endpoints REST et gère les requêtes utilisateurs.
Le principe clé est que les dépendances vont toujours vers
le cœur métier (Domain), qui reste indépendant des technologies externes
(EF Core, SQL Server, API externes...).
Avantages :
- Facilité
de test unitaire.
- Faible
couplage entre les couches.
- Évolution
plus simple du système.
- Meilleure
séparation des responsabilités.
3. Question : Qu'est-ce que le DDD
(Domain-Driven Design) ?
Réponse (courte en français) :
Le DDD (Domain-Driven Design) est une approche de
conception logicielle qui place le métier au centre du développement.
L'objectif est de modéliser l'application selon les besoins
métier en utilisant un langage commun entre les experts métier et les
développeurs (Ubiquitous Language).
Quelques concepts clés :
- Entity
: objet avec une identité unique (ex. : Contribution de prix).
- Value
Object : objet sans identité, défini par ses valeurs (ex. : montant,
devise).
- Aggregate
: groupe cohérent d'objets métier.
- Repository
: abstraction de l'accès aux données.
- Domain
Service : logique métier qui ne relève pas d'une seule entité.
- Bounded
Context : périmètre métier clairement défini.
Le DDD est souvent utilisé avec la Clean Architecture
pour isoler et protéger la logique métier du reste de l'application.
4. Question : Qu'est-ce qu'un Aggregate Root
?
Réponse (courte en français) :
En DDD, un Aggregate Root est l'entité
principale d'un agrégat. C'est le seul point d'entrée autorisé pour accéder et
modifier les objets contenus dans cet agrégat.
Son rôle est de :
- Garantir
les règles métier et la cohérence des données.
- Contrôler
les modifications des entités enfants.
- Être
l'unique objet chargé par le Repository.
Exemple :
Dans un système de contribution de prix :
Contribution
(Aggregate Root)
├── Price
├── Comment
└── ValidationHistory
La classe Contribution est l'Aggregate Root. Les objets
Price, Comment et ValidationHistory ne doivent pas être modifiés directement
depuis l'extérieur ; toutes les opérations passent par Contribution.
Avantage :
Cela permet de préserver l'intégrité métier et d'éviter des mises à jour
incohérentes dans le domaine.
5. Question : Quelle est la différence entre
une Entity et un Value Object ?
Réponse (courte en français) :
Entity
Une Entity possède une identité unique qui
reste la même durant tout son cycle de vie.
Exemple
:
public
class Contribution
{
public Guid Id { get; set; }
public decimal Price { get; set; }
}
Même si le prix change, c'est toujours la même contribution
grâce à son Id.
Value Object
Un Value Object n'a pas d'identité propre. Il est
défini uniquement par ses valeurs et est généralement immutable.
Exemple
:
public
record Money(decimal Amount, string Currency);
Deux objets Money(100, "EUR") sont considérés
identiques car leurs valeurs sont identiques.
Résumé
|
Entity |
Value Object |
|
Possède une identité (Id) |
Pas d'identité |
|
Peut évoluer dans le temps |
Généralement immutable |
|
Comparaison par identité |
Comparaison par valeurs |
|
Exemple : Contribution, User, Order |
Exemple : Money, Address, ISIN |
Règle simple :
- Si
l'objet doit être suivi individuellement → Entity
- Si
seules ses valeurs comptent → Value Object.
6. Question : Qu'est-ce que le Repository
Pattern ?
Réponse (courte en français) :
Le Repository Pattern est un pattern qui permet de séparer
la logique métier de l'accès aux données.
Au lieu d'accéder directement à la base de données via EF
Core ou SQL, l'application passe par une interface Repository.
Exemple :
public interface IContributionRepository
{
Task<Contribution?>
GetByIdAsync(Guid id);
Task AddAsync(Contribution
contribution);
Task UpdateAsync(Contribution
contribution);
}
Implémentation avec EF Core :
public
class ContributionRepository : IContributionRepository
{
private readonly AppDbContext
_context;
public async
Task<Contribution?> GetByIdAsync(Guid id)
=> await
_context.Contributions.FindAsync(id);
}
Avantages
- Découplage
entre le métier et la persistance.
- Tests
unitaires facilités (Mock du Repository).
- Changement
de technologie de stockage plus simple.
- Respect
des principes DDD et Clean Architecture.
Dans DDD, on utilise généralement un Repository pour
chaque Aggregate Root. Par exemple :
- IContributionRepository
- IOrderRepository
- IUserRepository
7. Question : Qu'est-ce que le CQRS ?
Réponse (courte en français) :
CQRS (Command Query Responsibility Segregation) est
un pattern qui consiste à séparer :
- Les
commandes (Commands) : opérations qui modifient les données (Create,
Update, Delete).
- Les
requêtes (Queries) : opérations qui lisent les données.
Exemple
Command :
CreateContributionCommand
→ crée une contribution.
Query :
GetContributionByIdQuery
→ récupère une contribution.
Avec MediatR
await
_mediator.Send(new CreateContributionCommand(...));
var contribution = await _mediator.Send(
new GetContributionByIdQuery(id));
Avantages
- Séparation
claire entre lecture et écriture.
- Code
plus lisible et maintenable.
- Facilite
les tests.
- Permet
d'optimiser indépendamment les performances de lecture et d'écriture.
Attention
CQRS n'impose pas deux bases de données différentes. Dans la
plupart des projets .NET, on commence avec une seule base SQL Server et on
sépare simplement les Commands et les Queries dans le code.
Phrase d'entretien :
"CQRS permet de séparer les opérations de lecture et
d'écriture afin d'améliorer la maintenabilité, la testabilité et l'évolutivité
de l'application. Je l'utilise souvent avec MediatR dans des architectures
Clean Architecture et DDD."
8. Bạn sẽ thiết kế API Contribution như thế nào?
Réponse en français :
Je proposerais une API basée sur Clean Architecture + DDD
+ CQRS.
1. Modèle métier principal
L’Aggregate Root serait Contribution.
Exemple de
statuts :
Draft →
Submitted → Validated → Published
→ Rejected
Une contribution pourrait contenir :
Contribution
- Id
- InstrumentId
- ISIN / RIC / Ticker
- Price
- Currency
- Status
- Contributor
- ValidationHistory
- CreatedAt
- UpdatedAt
2.
Endpoints API
POST /api/contributions
GET /api/contributions/{id}
GET /api/contributions
PUT /api/contributions/{id}
POST /api/contributions/{id}/submit
POST /api/contributions/{id}/validate
POST /api/contributions/{id}/reject
POST /api/contributions/{id}/publish
3.
Architecture
Domain
- Entity Contribution
- Value Objects: Price, InstrumentIdentifier
- Domain Events
Application
- Commands / Queries
- Validators FluentValidation
- Handlers CQRS
- Interfaces Repository
Infrastructure
- EF Core
- SQL Server
- Providers Bloomberg / Refinitiv / Six
API
- Controllers
- Authentication
- Middleware
4. Points importants
J’ajouterais :
- validation
métier stricte des transitions de statut ;
- audit
trail complet ;
- tests
unitaires avec xUnit, Moq, FluentAssertions ;
- gestion
des erreurs avec middleware global ;
- CI/CD
GitLab ;
- résilience
sur les providers externes : retry, timeout, circuit breaker.
Phrase d’entretien :
Je commencerais par modéliser le workflow métier autour de
l’Aggregate Root Contribution, puis j’exposerais des endpoints orientés cas
d’usage, tout en gardant le domaine indépendant de l’infrastructure.
9. Question : Quelle est la différence entre
Tracking et NoTracking dans EF Core ?
Réponse (courte en français) :
Tracking (par défaut)
Lorsque EF Core charge une entité, il la surveille
(tracke) dans le DbContext.
Ainsi, si l'objet est modifié, EF Core détecte
automatiquement les changements lors du SaveChanges().
var contribution = await context.Contributions
.FirstOrDefaultAsync(x => x.Id ==
id);
contribution.Price = 100;
await context.SaveChangesAsync();
➡️ EF Core génère automatiquement
un UPDATE.
NoTracking
Avec AsNoTracking(), EF Core ne suit plus l'entité en
mémoire.
var contribution = await context.Contributions
.AsNoTracking()
.FirstOrDefaultAsync(x => x.Id ==
id);
➡️ Meilleures performances pour
la lecture, mais les modifications ne seront pas sauvegardées automatiquement.
Quand utiliser ?
- Tracking
: pour modifier ou supprimer des données.
- NoTracking
: pour les requêtes de lecture (consultation, reporting, listes).
Avantage de NoTracking
- Moins
de mémoire consommée.
- Requêtes
plus rapides.
- Très
utile dans les APIs de consultation.
Phrase d’entretien :
Pour les opérations de lecture, j'utilise généralement
AsNoTracking() afin d'améliorer les performances. Je conserve le mode Tracking
uniquement lorsque l'entité doit être modifiée puis persistée via
SaveChanges().
10. Question : Quand utilisez-vous Include()
dans EF Core ?
Réponse (courte en français) :
J'utilise Include() lorsque j'ai besoin de charger
une entité ainsi que ses données liées (navigation properties) en une seule
requête.
Exemple
var contribution = await context.Contributions
.Include(c => c.ValidationHistory)
.FirstOrDefaultAsync(c => c.Id ==
id);
Ici, EF Core charge la contribution et son historique de
validation.
Pourquoi utiliser Include ?
- Éviter
plusieurs requêtes vers la base de données.
- Charger
explicitement les données nécessaires.
- Éviter
les problèmes de Lazy Loading (si utilisé).
Attention
Il ne faut pas abuser des Include() :
.Include(...)
.Include(...)
.Include(...)
Trop d'Include peuvent générer des requêtes SQL lourdes et
dégrader les performances.
Pour les écrans de consultation ou les APIs, je privilégie
souvent une projection avec Select() afin de récupérer uniquement les
champs nécessaires.
Exemple recommandé
var result = await context.Contributions
.Select(c => new ContributionDto
{
Id = c.Id,
Price = c.Price,
Status = c.Status
})
.ToListAsync();
Phrase d’entretien :
J'utilise Include() lorsque j'ai besoin des entités liées
pour appliquer une logique métier. Pour les simples consultations, je préfère
généralement une projection avec Select() afin d'optimiser les performances et
de limiter les données retournées.
11. Question : Comment fonctionnent les
migrations dans EF Core ?
Réponse (courte en français) :
Les migrations EF Core permettent de gérer
l'évolution du schéma de la base de données à partir du modèle C#.
Étapes
principales
1.
Modifier les entités
public
class Contribution
{
public Guid Id { get; set; }
public decimal Price { get; set; }
public string Currency { get; set; }
}
2. Générer une migration
dotnet ef migrations add AddCurrencyToContribution
EF Core compare le modèle actuel avec la dernière migration
et génère le script de modification.
3. Appliquer la migration
dotnet ef database update
La base de données est mise à jour automatiquement.
Que
contient une migration ?
protected
override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Currency",
table: "Contributions",
nullable: true);
}
- Up()
: applique les changements.
- Down()
: permet de revenir en arrière.
Bonnes pratiques
- Une
migration par évolution fonctionnelle.
- Vérifier
le SQL généré avant la mise en production.
- Versionner
les migrations dans Git.
- Ne
jamais modifier une migration déjà déployée en production.
Phrase d’entretien :
J'utilise les migrations EF Core en mode Code First. Après
chaque évolution du modèle, je génère une migration, je vérifie les changements
produits puis je l'intègre au pipeline CI/CD afin de garantir la cohérence
entre le code et la base de données.
12. Question : Comment optimiser EF Core ?
Réponse courte en français :
Pour optimiser EF Core, j’applique surtout ces bonnes
pratiques :
- Utiliser
AsNoTracking() pour les requêtes en lecture seule.
- Utiliser
Select() pour récupérer uniquement les colonnes nécessaires.
- Éviter
les Include() inutiles ou trop profonds.
- Ajouter
des index SQL sur les colonnes utilisées dans les filtres, tris et
jointures.
- Utiliser
la pagination avec Skip() / Take().
- Éviter
le problème N+1 queries.
- Utiliser
Any() au lieu de Count() quand on veut seulement vérifier l’existence.
- Regrouper
les appels à SaveChanges() au lieu de l’appeler trop souvent.
- Utiliser
des requêtes asynchrones : ToListAsync(), FirstOrDefaultAsync().
- Analyser
le SQL généré avec ToQueryString() ou les logs EF Core.
Exemple
:
var result
= await context.Contributions
.AsNoTracking()
.Where(c => c.Status ==
ContributionStatus.Validated)
.OrderByDescending(c =>
c.CreatedAt)
.Select(c => new ContributionDto
{
Id = c.Id,
Price = c.Price,
Currency = c.Currency,
Status = c.Status
})
.Take(50)
.ToListAsync();
Phrase d’entretien :
Pour optimiser EF Core, je commence par limiter les données
chargées, utiliser AsNoTracking() pour les lectures, éviter les Include()
excessifs, mettre en place les bons index SQL et vérifier le SQL généré afin
d’identifier les requêtes coûteuses.
13. Question : Quelle est la différence entre
IEnumerable et IQueryable ?
Réponse (courte en français) :
IEnumerable
IEnumerable<T> travaille sur des données déjà chargées
en mémoire.
Les filtres sont exécutés côté application (.NET).
var contributions = context.Contributions.ToList();
var result = contributions
.Where(c => c.Price > 100);
➡️ Toute la table est chargée
puis filtrée en mémoire.
IQueryable
IQueryable<T> construit une requête qui sera traduite
en SQL et exécutée par la base de données.
var result
= context.Contributions
.Where(c => c.Price > 100);
SQL généré
:
SELECT *
FROM Contributions
WHERE Price > 100
➡️ Seules les données nécessaires
sont récupérées.
Résumé
|
IEnumerable |
IQueryable |
|
Exécution en mémoire |
Exécution en base de données |
|
LINQ to Objects |
LINQ to Entities |
|
Moins performant sur gros volumes |
Plus performant |
|
Utilisé après ToList() |
Utilisé avant ToListAsync() |
Exemple à éviter
var data =
await context.Contributions
.ToListAsync();
var result = data.Where(c => c.Price > 100);
Ici le filtrage est effectué en mémoire.
Exemple recommandé
var result = await context.Contributions
.Where(c => c.Price > 100)
.ToListAsync();
Le filtrage est effectué directement dans SQL Server.
Phrase d’entretien :
IQueryable permet à EF Core de traduire la requête en SQL et
d'exécuter le filtrage côté base de données. IEnumerable, quant à lui,
travaille sur des données déjà chargées en mémoire. Pour des raisons de
performance, je privilégie IQueryable tant que la requête n'est pas
matérialisée.
15. Question : Qu'est-ce que l'Injection de
Dépendances (Dependency Injection) ?
Réponse (courte en français) :
L'Injection de Dépendances (DI) est un principe qui
consiste à fournir les dépendances d'une classe depuis l'extérieur plutôt que
de les créer directement dans la classe.
Sans DI
public
class ContributionService
{
private readonly
ContributionRepository _repository =
new ContributionRepository();
}
➡️ Classe fortement couplée.
Avec DI
public class ContributionService
{
private readonly
IContributionRepository _repository;
public
ContributionService(IContributionRepository repository)
{
_repository = repository;
}
}
➡️ La dépendance est injectée via
le constructeur.
Enregistrement
dans .NET
builder.Services.AddScoped<IContributionRepository,
ContributionRepository>();
builder.Services.AddScoped<IContributionService,
ContributionService>();
Avantages
- Faible
couplage.
- Code
plus maintenable.
- Tests
unitaires facilités (Mock des dépendances).
- Respect
des principes SOLID, notamment le Dependency Inversion Principle.
Lifetimes principaux
- Transient
: nouvelle instance à chaque injection.
- Scoped
: une instance par requête HTTP.
- Singleton
: une seule instance pour toute l'application.
Phrase d’entretien :
J'utilise l'injection de dépendances pour découpler les
composants, faciliter les tests unitaires et respecter les principes SOLID.
Dans les APIs ASP.NET Core, j'enregistre généralement les services métier et
repositories en Scoped.
16. Question : Qu'est-ce qu'un Middleware
dans ASP.NET Core ?
Réponse (courte en français) :
Un Middleware est un composant qui intercepte les
requêtes HTTP et les réponses dans le pipeline ASP.NET Core.
Chaque requête passe par une chaîne de middlewares avant
d'atteindre le contrôleur.
Exemple du pipeline
Request
↓
Authentication
↓
Authorization
↓
Exception Middleware
↓
Controller
↓
Response
Middleware personnalisé
public class LoggingMiddleware
{
private readonly RequestDelegate
_next;
public
LoggingMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task
InvokeAsync(HttpContext context)
{
Console.WriteLine($"Request:
{context.Request.Path}");
await _next(context);
Console.WriteLine($"Response: {context.Response.StatusCode}");
}
}
Enregistrement :
app.UseMiddleware<LoggingMiddleware>();
Cas d'utilisation fréquents
- Gestion
globale des exceptions.
- Logging.
- Authentification
/ Autorisation.
- Gestion
des headers.
- Rate
limiting.
- Monitoring
et audit.
Exemple très courant
Global Exception Middleware :
app.UseMiddleware<GlobalExceptionMiddleware>();
➡️ Permet de centraliser la
gestion des erreurs et retourner des réponses HTTP cohérentes.
Phrase d’entretien :
Les middlewares permettent de traiter les requêtes et
réponses de manière transversale. Je les utilise notamment pour le logging, la
gestion centralisée des exceptions, l'authentification et l'observabilité des
APIs.
17. Question : Quelle est la différence entre
Authentication et Authorization ?
Réponse (courte en français) :
Authentication (Authentification)
L'authentification consiste à vérifier qui est
l'utilisateur.
Exemples :
- Login
/ mot de passe
- JWT
Token
- OAuth2
- Azure
AD
Qui êtes-vous ?
Authorization (Autorisation)
L'autorisation consiste à vérifier ce que l'utilisateur a
le droit de faire.
Exemples :
- Lire
une contribution
- Valider
une contribution
- Accéder
à l'administration
Que pouvez-vous faire ?
Exemple ASP.NET Core
Authentification JWT :
builder.Services.AddAuthentication()
.AddJwtBearer();
Autorisation
:
[Authorize(Roles
= "Validator")]
public IActionResult ValidateContribution()
{
...
}
Seuls les utilisateurs ayant le rôle Validator
pourront accéder à cette action.
Résumé
|
Authentication |
Authorization |
|
Vérifie l'identité |
Vérifie les permissions |
|
Qui êtes-vous ? |
Que pouvez-vous faire ? |
|
Login, JWT, OAuth2 |
Roles, Claims, Policies |
|
Exécutée en premier |
Exécutée après |
Phrase d’entretien :
L'authentification permet d'identifier l'utilisateur, tandis
que l'autorisation détermine les actions qu'il est autorisé à effectuer. Dans
ASP.NET Core, j'utilise généralement JWT pour l'authentification et des rôles
ou policies pour l'autorisation.
18. Question : Comment fonctionne un JWT ?
Réponse (courte en français) :
JWT (JSON Web Token) est un mécanisme
d'authentification stateless utilisé dans les APIs.
Fonctionnement
- L'utilisateur
s'authentifie (login/password).
- Le
serveur génère un JWT signé.
- Le
client stocke le token.
- À
chaque requête, le client envoie :
Authorization: Bearer <token>
- L'API
valide le token et autorise l'accès.
Structure d'un JWT
Un JWT est composé de 3 parties :
Header.Payload.Signature
Exemple :
eyJhbGciOiJIUzI1Ni...
Le Payload contient généralement :
{
"sub": "123",
"name": "John Doe",
"role":
"Validator",
"exp": 1750000000
}
Validation
L'API vérifie :
- la
signature ;
- la
date d'expiration (exp) ;
- l'émetteur
(issuer) ;
- l'audience
(audience).
Configuration
ASP.NET Core
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters
= ...
});
Avantages
- Stateless
(pas de session côté serveur).
- Adapté
aux architectures distribuées et microservices.
- Facile
à intégrer avec Angular, React, etc.
Attention
- Ne
jamais stocker d'informations sensibles dans le Payload.
- Utiliser
HTTPS.
- Prévoir
une durée de vie raisonnable et éventuellement un Refresh Token.
Phrase d’entretien :
Avec JWT, l'utilisateur s'authentifie une seule fois. Le
serveur génère un token signé contenant ses claims. À chaque requête, l'API
valide ce token pour authentifier et autoriser l'utilisateur sans maintenir de
session côté serveur.
19. Question : Comment gérez-vous les
exceptions globalement ?
Réponse courte en français :
Dans une API ASP.NET Core, je gère les exceptions
globalement avec un middleware dédié.
L’objectif est de centraliser la gestion des erreurs et de
retourner une réponse HTTP cohérente.
Exemple
public
class GlobalExceptionMiddleware
{
private readonly RequestDelegate
_next;
private readonly ILogger<GlobalExceptionMiddleware>
_logger;
public
GlobalExceptionMiddleware(RequestDelegate next,
ILogger<GlobalExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext
context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Unhandled exception");
context.Response.StatusCode =
500;
context.Response.ContentType =
"application/json";
await
context.Response.WriteAsJsonAsync(new
{
message = "Une
erreur interne est survenue."
});
}
}
}
Enregistrement :
app.UseMiddleware<GlobalExceptionMiddleware>();
Bonnes pratiques
- Logger
l’erreur technique côté serveur.
- Ne
pas exposer les détails sensibles au client.
- Retourner
des codes HTTP adaptés : 400, 401, 403, 404, 500.
- Utiliser
éventuellement ProblemDetails pour standardiser les réponses.
Phrase d’entretien :
Je centralise la gestion des exceptions dans un middleware
global, avec logging structuré et réponses standardisées, par exemple via
ProblemDetails, afin d’éviter de répéter les try/catch dans chaque contrôleur.
20. Question : Qu'est-ce que FluentValidation
?
Réponse (courte en français) :
FluentValidation est une bibliothèque .NET permettant
de définir des règles de validation de manière fluide et centralisée.
Elle est souvent utilisée dans les APIs ASP.NET Core pour
valider les DTOs ou les Commands CQRS.
Exemple
DTO :
public
class CreateContributionRequest
{
public string ISIN { get; set; }
public decimal Price { get; set; }
}
Validator :
public
class CreateContributionValidator
:
AbstractValidator<CreateContributionRequest>
{
public CreateContributionValidator()
{
RuleFor(x => x.ISIN)
.NotEmpty()
.Length(12);
RuleFor(x => x.Price)
.GreaterThan(0);
}
}
Avantages
- Validation
séparée du code métier.
- Syntaxe
lisible et maintenable.
- Facile
à tester.
- S'intègre
très bien avec ASP.NET Core et MediatR.
Exemple
de règles courantes
RuleFor(x
=> x.Email).EmailAddress();
RuleFor(x => x.Name).NotEmpty();
RuleFor(x => x.Age).InclusiveBetween(18, 65);
Dans une architecture CQRS
Souvent, chaque commande possède son validator :
CreateContributionCommand
CreateContributionValidator
UpdateContributionCommand
UpdateContributionValidator
Phrase d’entretien :
J'utilise FluentValidation pour centraliser les règles de
validation, garder les contrôleurs légers et garantir que seules des données
valides atteignent la couche métier. Cela s'intègre très bien avec CQRS et
MediatR.
21. Question : Qu'est-ce que les Channels
dans .NET ?
Réponse (courte en français) :
Les Channels sont une fonctionnalité de .NET
permettant de mettre en place une communication asynchrone entre un
producteur (Producer) et un consommateur (Consumer).
Ils servent à traiter des tâches en arrière-plan de manière
performante et thread-safe.
Schéma
Producer
↓
Channel
↓
Consumer
Exemple
simple
var channel
= Channel.CreateUnbounded<string>();
await channel.Writer.WriteAsync("Contribution créée");
var message = await channel.Reader.ReadAsync();
Cas d'utilisation
- Traitements
en arrière-plan.
- Files
d'attente en mémoire.
- Envoi
d'emails.
- Notifications.
- Traitement
de contributions de prix.
- Découplage
entre réception et traitement des requêtes.
Avantages
- Très
performant.
- Thread-safe.
- Support
natif de l'asynchrone (async/await).
- Alternative
légère à des solutions comme RabbitMQ pour des besoins simples.
Exemple dans ContribFactory
Lorsqu'une contribution est créée :
API
↓
Channel
↓
Worker
↓
Validation / Publication / Notification
L'API répond rapidement au client tandis que le traitement
lourd est effectué en arrière-plan.
Phrase d’entretien :
Les Channels permettent de mettre en place une communication
asynchrone Producteur/Consommateur. Je les utilise pour découpler les
traitements en arrière-plan et améliorer les performances sans bloquer les
requêtes HTTP.
22. Question : Que savez-vous sur la
résilience (Resilience) ?
Réponse (courte en français) :
La résilience consiste à rendre une application
capable de continuer à fonctionner malgré des erreurs temporaires ou des
indisponibilités de services externes.
Dans une API .NET qui communique avec des fournisseurs comme
Bloomberg ou Refinitiv, c'est un point essentiel.
Principaux mécanismes
Retry
Réessayer automatiquement une opération après un échec
temporaire.
Erreur temporaire
↓
Retry
↓
Succès
Circuit Breaker
Empêcher les appels vers un service qui est déjà en panne.
Service KO
↓
Circuit Open
↓
Plus d'appels pendant un certain temps
Timeout
Limiter la durée d'attente d'un appel externe.
Appel > 5 secondes
↓
Timeout
Fallback
Fournir une réponse alternative lorsque le service principal
est indisponible.
Avec
.NET 8 / Polly
builder.Services.AddHttpClient<IBloombergClient,
BloombergClient>()
.AddResilienceHandler("default");
Ou avec des stratégies :
- Retry
- Circuit
Breaker
- Timeout
- Rate
Limiter
Cas ContribFactory
Si Bloomberg est indisponible :
- Retry
3 fois.
- Timeout
après quelques secondes.
- Circuit
Breaker pour éviter de saturer le provider.
- Logging
et monitoring des erreurs.
Phrase d’entretien :
La résilience permet de gérer les pannes temporaires des
systèmes externes grâce à des mécanismes comme Retry, Circuit Breaker, Timeout
et Fallback. Dans .NET 8, j'utilise généralement Polly ou les Resilience
Pipelines intégrés pour sécuriser les appels aux services externes.
23. Question : Comment fonctionne un pipeline
GitLab CI/CD ?
Réponse (courte en français) :
Un pipeline GitLab CI/CD automatise les étapes de
construction, de test et de déploiement d'une application.
À chaque push ou merge request, GitLab exécute
les étapes définies dans le fichier .gitlab-ci.yml.
Workflow
typique
Code Push
↓
Build
↓
Tests
↓
Quality Check
↓
Package
↓
Deploy Dev
↓
Deploy Test / Prod
Exemple
simple
stages:
- build
- test
- deploy
build:
stage: build
script:
- dotnet restore
- dotnet build
test:
stage: test
script:
- dotnet test
deploy:
stage: deploy
script:
- kubectl apply -f deployment.yaml
Dans un projet .NET
Je mets généralement en place :
- dotnet
restore
- dotnet
build
- dotnet
test
- Analyse
qualité (SonarQube)
- Création
de l'image Docker
- Push
vers le registry
- Déploiement
Kubernetes ou AWS
Avantages
- Automatisation
complète.
- Détection
rapide des erreurs.
- Déploiements
reproductibles.
- Amélioration
de la qualité du code.
Phrase d’entretien :
Dans mes projets .NET, le pipeline GitLab CI/CD exécute
automatiquement le build, les tests unitaires, les contrôles qualité, la
création des images Docker puis le déploiement sur les différents
environnements. Cela garantit des livraisons fiables et reproductibles.
24. Question : Comment intégreriez-vous les
tests unitaires ?
Réponse courte en français :
J’intègre les tests unitaires dès la couche Application
et Domain, car ce sont les couches les plus importantes métier.
Stack utilisée
- xUnit
: framework de test
- Moq
: mock des dépendances
- FluentAssertions
: assertions lisibles
Exemple
[Fact]
public async Task SubmitContribution_Should_ChangeStatus_ToSubmitted()
{
// Arrange
var contribution = new
Contribution(...);
// Act
contribution.Submit();
// Assert
contribution.Status.Should().Be(ContributionStatus.Submitted);
}
Ce que je teste
- Les
règles métier du domaine.
- Les
transitions de statut.
- Les
validators FluentValidation.
- Les
handlers CQRS.
- Les
cas d’erreur.
- Les
appels aux repositories via mocks.
Intégration CI/CD
Dans GitLab CI/CD, j’ajoute une étape :
test:
stage: test
script:
- dotnet test --configuration Release
Phrase d’entretien :
Je privilégie les tests unitaires sur la logique métier et
les handlers CQRS, avec xUnit, Moq et FluentAssertions. Ensuite, j’intègre leur
exécution automatique dans le pipeline GitLab CI/CD afin de sécuriser chaque
merge request.
25. Question : À quoi servent xUnit, Moq et
FluentAssertions ?
Réponse (courte en français) :
xUnit
xUnit est le framework de tests unitaires.
Il permet d'écrire et d'exécuter les tests.
[Fact]
public void Should_Return_True()
{
Assert.True(true);
}
Moq
Moq permet de créer des objets simulés (Mocks)
pour isoler la classe testée.
var
repositoryMock = new Mock<IContributionRepository>();
repositoryMock
.Setup(r =>
r.GetByIdAsync(It.IsAny<Guid>()))
.ReturnsAsync(contribution);
Ainsi, le test ne dépend pas de la base de données.
FluentAssertions
FluentAssertions rend les assertions plus lisibles.
Au lieu de :
Assert.Equal(
ContributionStatus.Submitted,
contribution.Status);
on écrit :
contribution.Status
.Should()
.Be(ContributionStatus.Submitted);
Ensemble
[Fact]
public async Task Should_Submit_Contribution()
{
// Arrange
var repositoryMock = new
Mock<IContributionRepository>();
// Act
var result = await
service.SubmitAsync(id);
// Assert
result.Status.Should().Be(ContributionStatus.Submitted);
}
Résumé
|
Outil |
Rôle |
|
xUnit |
Exécution des tests |
|
Moq |
Simulation des dépendances |
|
FluentAssertions |
Assertions lisibles |
Phrase d’entretien :
J’utilise généralement xUnit pour écrire les tests, Moq pour
isoler les dépendances externes et FluentAssertions pour rendre les assertions
plus lisibles et maintenables. Cela permet de produire des tests clairs,
fiables et faciles à faire évoluer.
26. Question : Pouvez-vous écrire un test
unitaire simple ?
Réponse (exemple avec xUnit + FluentAssertions) :
Supposons que l'on ait une méthode métier :
public
class Contribution
{
public ContributionStatus Status {
get; private set; }
public void Submit()
{
Status =
ContributionStatus.Submitted;
}
}
Test
unitaire :
using
FluentAssertions;
using Xunit;
public class ContributionTests
{
[Fact]
public void Submit_Should_Change_Status_To_Submitted()
{
// Arrange
var contribution = new
Contribution();
// Act
contribution.Submit();
// Assert
contribution.Status.Should()
.Be(ContributionStatus.Submitted);
}
}
Avec Moq
Si un service dépend d'un Repository :
[Fact]
public async Task GetContribution_Should_Return_Contribution()
{
// Arrange
var contribution = new
Contribution();
var repositoryMock = new Mock<IContributionRepository>();
repositoryMock
.Setup(r =>
r.GetByIdAsync(It.IsAny<Guid>()))
.ReturnsAsync(contribution);
var service = new
ContributionService(repositoryMock.Object);
// Act
var result = await
service.GetByIdAsync(Guid.NewGuid());
// Assert
result.Should().NotBeNull();
}
Phrase d’entretien :
J'utilise généralement le pattern Arrange-Act-Assert avec
xUnit, FluentAssertions pour la lisibilité des assertions et Moq pour isoler
les dépendances externes comme les repositories ou les services.
27. Question : Que sont Bloomberg, Refinitiv
et SIX ?
Réponse (courte en français) :
Bloomberg, Refinitiv et SIX sont des fournisseurs de
données financières utilisés par les banques, les asset managers et les
salles de marché.
Bloomberg
Fournit :
- Données
de marché en temps réel
- Prix
des instruments financiers
- Actualités
financières
- Référentiels
titres
Exemples :
- Bloomberg
Terminal
- Bloomberg
API
- Bloomberg
Data License
Refinitiv
(anciennement Thomson Reuters Financial & Risk)
Fournit :
- Données
de marché
- RIC
(Reuters Instrument Code)
- Actualités
financières
- APIs
et plateformes de trading
Exemple :
RIC : AIR.PA
(Airbus sur Euronext Paris)
SIX Group
Société suisse spécialisée dans :
- Données
de référence financières
- Identifiants
d'instruments
- Données
de marché
- Services
de place boursière
Très utilisée pour :
- ISIN
- Classification
des titres
- Référentiels
financiers
Pourquoi c'est important pour ContribFactory ?
Une contribution de prix doit souvent être associée à un
instrument financier :
ISIN :
FR0000120404
RIC : AIR.PA
Ticker : AIR
L'API peut interroger Bloomberg, Refinitiv ou SIX pour :
- Vérifier
l'existence du titre.
- Récupérer
les données de référence.
- Comparer
les prix de marché.
- Enrichir
les contributions.
Différence entre ISIN, RIC et Ticker
- ISIN
: identifiant international unique d'un instrument.
- RIC
: identifiant utilisé par Refinitiv.
- Ticker
: symbole boursier utilisé sur une place de marché.
Phrase d’entretien :
Bloomberg, Refinitiv et SIX sont des fournisseurs de données
financières. Ils permettent de récupérer des informations de marché et des
référentiels d'instruments tels que les ISIN, RIC ou tickers, qui sont
essentiels pour les applications de pricing et de contribution de prix dans le
secteur bancaire.
28. Question : Que sont l'ISIN, le RIC et le
Ticker ?
Réponse (courte en français) :
Ce sont trois identifiants utilisés pour identifier un
instrument financier.
1. ISIN (International Securities Identification Number)
L'ISIN est un identifiant international unique composé de 12
caractères.
Exemple :
FR0000120404
- FR
: pays (France)
- utilisé
mondialement
- très
utilisé dans les banques et les référentiels titres
2. RIC (Reuters Instrument Code)
Le RIC est un identifiant propriétaire de Refinitiv.
Exemple :
AIR.PA
- AIR
: Airbus
- .PA
: Euronext Paris
Utilisé principalement dans les systèmes Refinitiv.
3. Ticker
Le Ticker est le symbole boursier d'un titre.
Exemple :
AIR
ou
AAPL
- Facile
à lire pour les traders.
- Peut
ne pas être unique au niveau mondial.
Exemple concret
Pour l'action Airbus :
|
Type |
Valeur |
|
ISIN |
FR0000120404 |
|
RIC |
AIR.PA |
|
Ticker |
AIR |
Différence principale
|
ISIN |
RIC |
Ticker |
|
Standard international |
Identifiant Refinitiv |
Symbole boursier |
|
Unique mondialement |
Unique chez Refinitiv |
Pas toujours unique |
|
12 caractères |
Format variable |
Format court |
Phrase d’entretien :
L'ISIN est l'identifiant international officiel d'un
instrument financier, le RIC est l'identifiant utilisé par Refinitiv et le
Ticker est le symbole boursier utilisé sur les marchés. Dans une application de
contribution de prix, ces identifiants permettent de référencer et retrouver
précisément les instruments financiers.
Aucun commentaire:
Enregistrer un commentaire