mercredi 12 juin 2024

luyen pv ca

 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

  1. L'utilisateur s'authentifie (login/password).
  2. Le serveur génère un JWT signé.
  3. Le client stocke le token.
  4. À chaque requête, le client envoie :

Authorization: Bearer <token>

  1. 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

luyen pv ca Lượt xem: