Communitygithub.com

Aurelian1974/LocalTax - Architecture en tranches verticales .NET

Construction et maintenance d'une architecture en tranches verticales (Vertical Slice Architecture) dans .NET — anatomie des tranches (variantes fichier unique et dossier), distribution directe des handlers sans médiateur, préoccupations transversales, partage entre tranches, interaction avec le domaine et les couches Clean, et tests au niveau des tranches.

Qu'est-ce que LocalTax - Architecture en tranches verticales .NET ?

Ce skill fournit une approche structurée pour implémenter et maintenir une architecture en tranches verticales dans des projets .NET. Il couvre l'anatomie complète d'une tranche, avec deux variantes principales : la version fichier unique (un seul fichier contenant le handler, les commandes, etc.) et la version dossier (organisation par dossiers au sein de la tranche). Le skill insiste sur la distribution directe des handlers sans utiliser de médiateur (comme MediatR), réduisant ainsi la complexité et améliorant la performance. Il explique comment gérer les préoccupations transversales (logging, validation, sécurité) au niveau de la tranche, ce que les tranches peuvent partager (comme des modèles de domaine ou des services partagés) et comment elles interagissent avec une couche domaine traditionnelle ou une architecture Clean. Enfin, il guide sur les tests au niveau de la tranche (tests unitaires et d'intégration) pour garantir que chaque use case est testable de manière isolée. Ce skill est idéal pour les développeurs travaillant sur des projets .NET modulaires où chaque fonctionnalité est organisée en tranches verticales, et qui souhaitent éviter la duplication entre tranches.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Aurelian1974/LocalTax/tree/main/.claude/skills/vertical-slice-architecture

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Que fait LocalTax - Architecture en tranches verticales .NET ?

Comment construire et maintenir une architecture en tranches verticales dans .NET — anatomie des tranches (variantes fichier unique et dossier), distribution directe des handlers sans médiateur, préoccupations transversales, ce que les tranches peuvent partager, comment les tranches interagissent avec un modèle de domaine ou les couches Clean, et tests au niveau de la tranche. Utilisez ce guide lors de l'ajout ou de la modification d'un cas d'utilisation/point de terminaison/fonctionnalité dans tout module organisé par tranches verticales, ou lorsque la duplication entre tranches est discutée.

Anatomie d'une tranche verticale

Chaque tranche verticale encapsule un cas d'utilisation complet. Il existe deux variantes :

  • Variante fichier unique : Tout le code de la tranche (commande, handler, requête réponse, etc.) réside dans un seul fichier (ex. CreateOrderSlice.cs).
  • Variante dossier : Chaque composant est dans un dossier dédié (ex. CreateOrder/Command.cs, CreateOrder/Handler.cs, CreateOrder/Response.cs).

Structure recommandée (variante fichier unique)

// Slice.cs
public class CreateOrderSlice
{
    public record Command(int CustomerId, List<OrderItem> Items);
    public record Result(int OrderId);
    
    public class Handler
    {
        public async Task<Result> HandleAsync(Command command, CancellationToken ct)
        {
            // Logique métier directe
            var order = new Order(command.CustomerId, command.Items);
            await _repository.SaveAsync(order, ct);
            return new Result(order.Id);
        }
    }
}

Distribution directe sans médiateur

Au lieu d'utiliser un médiateur comme MediatR, appelez directement le handler :

// Dans un endpoint minimal API
app.MapPost("/create-order", async (CreateOrderSlice.Command command, CreateOrderSlice.Handler handler) =>
{
    var result = await handler.HandleAsync(command, CancellationToken.None);
    return Results.Ok(result);
});

Préoccupations transversales

Les préoccupations transversales (logging, validation, sécurité) peuvent être gérées de deux manières :

  1. Dans le handler lui-même : Simple mais dupliqué si plusieurs handlers ont besoin de la même logique.
  2. Via un pipeline personnalisé : Utilisez des décorateurs ou un middleware ASP.NET Core pour interceptrer l'appel.
// Exemple de décorateur pour la validation
public class ValidationHandler<TResponse> : IHandler<TResponse> where TResponse : class
{
    private readonly IHandler<TResponse> _innerHandler;
    public ValidationHandler(IHandler<TResponse> innerHandler) => _innerHandler = innerHandler;
    
    public async Task<TResponse> HandleAsync(object command, CancellationToken ct)
    {
        // Validation ici
        return await _innerHandler.HandleAsync(command, ct);
    }
}

Partage entre tranches

Les tranches peuvent partager :

  • Modèles de domaine : Entités partagées (ex. Order, Customer).
  • Services partagés : Repositories, services externes.
  • Infrastructure commune : Connexion à la base de données, logging.

Règle d'or : Ne partagez pas le code spécifique à un cas d'utilisation (ex. logique de validation propre à un use case). Si deux slices utilisent la même logique métier, extrayez-la dans un service partagé ou un domaine commun.

Interaction avec le domaine et les couches Clean

  • Domaine : Les tranches interagissent avec le domaine via des repositories et des entités. Le domaine contient les règles métier fondamentales.
  • Couches Clean : Chaque tranche peut être considérée comme un petit composant horizontal à l'intérieur d'une architecture Clean. Par exemple, le handler est le cas d'utilisation (use case interactor), la commande est le request, et le résultat est le response.
// Exemple d'interaction avec le domaine
public class CreateOrderSlice
{
    public class Handler
    {
        private readonly IOrderRepository _orderRepo;
        private readonly ICustomerRepository _customerRepo;
        public Handler(IOrderRepository orderRepo, ICustomerRepository customerRepo)
        {
            _orderRepo = orderRepo;
            _customerRepo = customerRepo;
        }
        
        public async Task<Result> HandleAsync(Command command, CancellationToken ct)
        {
            var customer = await _customerRepo.GetByIdAsync(command.CustomerId, ct);
            if (customer == null) return new Result(null, "Customer not found");
            
            var order = new Order
            {
                CustomerId = customer.Id,
                Items = command.Items.Select(i => new OrderItem(i.ProductId, i.Quantity)).ToList()
            };
            
            await _orderRepo.SaveAsync(order, ct);
            return new Result(order.Id, null);
        }
    }
}

Tests au niveau de la tranche

Chaque tranche doit être testable de manière isolée :

  • Tests unitaires : Testez le handler avec des mocks des repositories.
  • Tests d'intégration : Testez la tranche entière avec une base de données réelle ou en mémoire.
[Fact]
public async Task CreateOrder_WithValidCommand_ShouldReturnOrderId()
{
    // Arrange
    var repo = new Mock<IOrderRepository>();
    var handler = new CreateOrderSlice.Handler(repo.Object, Mock.Of<ICustomerRepository>());
    var command = new CreateOrderSlice.Command(1, new List<OrderItem> { new(1, 2) });
    
    // Act
    var result = await handler.HandleAsync(command, CancellationToken.None);
    
    // Assert
    Assert.NotNull(result.OrderId);
}

Comment éviter la duplication

Lorsque vous détectez une duplication entre tranches :

  1. Identifiez le code dupliqué : Est-ce une règle métier, une validation, une logique de mapping ?
  2. Extrayez dans un service partagé : Par exemple, une classe OrderService qui contient la logique de création d'une commande.
  3. Référencez ce service dans les handlers : Ne dupliquez pas le code, injectez le service.
// Service partagé
public class OrderService
{
    public async Task CreateOrder(Command command, CancellationToken ct)
    {
        // Logique partagée
    }
}

// Dans le handler
public class CreateOrderSlice
{
    public class Handler
    {
        private readonly OrderService _orderService;
        public Handler(OrderService orderService) => _orderService = orderService;
        
        public async Task<Result> HandleAsync(Command command, CancellationToken ct)
        {
            await _orderService.CreateOrder(command, ct);
            return new Result(command.OrderId);
        }
    }
}

Conclusion

L'architecture en tranches verticales permet de garder chaque cas d'utilisation indépendant, testable et maintenable, tout en réduisant la complexité due aux médiateurs. Suivez ces principes pour construire des systèmes .NET modulaires et évolutifs.

Skills associés