SF

01 / Ingénieur logiciel

Soltani Fedi

Développeur Fullstack .NET

Architecture SaaS multi-tenant

Je construis la couche invisible des plateformes SaaS — celle que les utilisateurs ne remarquent jamais, sauf quand elle manque.

Tunis, Tunisie

Ouvert aux opportunités

LinkedIn

02 / Approche

TENANT ATENANT CTENANT BGlobal Query Filter
Requête bloquée à la frontière

Aucune requête ne franchit une frontière de tenant.

Global Query Filters EF Core et claims JWT par tenant, appliqués sur l'ensemble des modules. L'isolation n'est pas un contrôle ajouté à chaque appel : elle est portée par la couche d'accès aux données elle-même.

3+années d'expérience
100+handlers CQRS migrés
.NET 5→10versions livrées
6entreprises

03 / Expertise

Quatre terrains

I

Isolation multi-tenant

Global Query Filters EF Core, claims JWT par tenant, RBAC entity-level sur Admin, Manager et Collaborateur. Aucune donnée ne franchit une frontière de tenant.

EF Core · JWT · RBAC

Lire l’étude de cas
II

Sécurité et intégrité

Rotation atomique des refresh tokens avec détection de réutilisation, révocation de sessions, concurrence optimiste PostgreSQL via xmin et conflits HTTP 409, editing presence temps réel en SignalR.

JWT · xmin · SignalR

Lire l’étude de cas
III

Architecture et contrats

Clean Architecture, CQRS, dispatcher OSS custom remplaçant 100+ handlers, Application Services, contrats *Request et pattern refetch GET.

Clean Architecture · CQRS

Lire l’étude de cas
IV

Qualité et livraison

Serilog avec corrélation multi-tenant, tests d'intégration PostgreSQL sous Testcontainers, quality gates GitHub Actions, déploiement Docker sur Azure App Service.

Serilog · Testcontainers · Docker

Lire l’étude de cas

04 / Contrôle d’accès

Qui voit quoi

Choisissez un rôle et un tenant. La même requête renvoie des lignes différentes, et certains liens deviennent inaccessibles. C'est le modèle d'accès livré chez TeamXtend, reproduit ici côté client.

Rôle
Tenant connecté

Navigation

  • ProjetsOui
  • ÉquipesOui
  • UtilisateursNon
  • ParamètresNon

ÉcritureOui

GET /api/projects

  • PRJ-104Refonte portail client
  • PRJ-118Migration EF Core 10
  • PRJ-131Audit contrats API

3 lignes visibles · 2 hors périmètre

Deux mécanismes distincts sont à l'œuvre. Le Global Query Filter décide quelles lignes existent pour la requête : un Manager du tenant Nord ne peut pas lire une ligne du tenant Sud, même en forgeant l'identifiant. Les flags de navigation, eux, décident de ce qui est cliquable — ils améliorent l'expérience mais ne protègent rien, puisque l'API revérifie systématiquement.

05 / Concurrence

Le conflit qu’on ne voit pas

Deux utilisateurs éditent le même enregistrement. Sans concurrence optimiste, le second écrase le premier sans que personne ne le sache. Essayez.

Utilisateur Axmin 1042
Utilisateur Bxmin 1042

db → "Refonte du portail client" · xmin 1042

PostgreSQL expose un compteur de version système, xmin, incrémenté à chaque écriture. EF Core le compare avant de valider : si la valeur a changé, la transaction est rejetée plutôt que d'écraser. Le bandeau de présence, lui, arrive par SignalR et prévient avant même la collision.

06 / Parcours

Six postes, trois ans

  1. Développeur Fullstack .NETCDI · Tunis

    Time & Performance — plateforme SaaS multi-tenant de gestion des temps et de la performance

    • Identifié un risque de conformité de licence avec MediatR et AutoMapper (dual-license depuis 2025) et piloté la migration vers un dispatcher OSS custom et des Application Services : 100+ handlers CQRS remplacés, 9 profils de mapping migrés vers Mapperly, et séparation Read/Write Repository avec pattern Specification sur 5 agrégats domaine.
    • Durci le cycle de vie JWT : rotation atomique one-time des refresh tokens en compare-and-swap avec détection de réutilisation, révocation de toutes les sessions au rejeu détecté et au changement de mot de passe, et revalidation à chaud des rôles Identity avec invalidation des tokens.
    • Mis en place la concurrence optimiste bout-en-bout : PostgreSQL xmin comme jeton EF Core avec conflits en HTTP 409, jeton renvoyé depuis les écrans détail et modales Angular, et Editing Presence temps réel via SignalR pour prévenir la collision avant qu'elle ne survienne.
    • Conçu la couche d'isolation multi-tenant (Global Query Filters EF Core, claims JWT par tenant) et durci le RBAC multi-rôles : permissions entity-level et flags de navigation (canNavigate / canManage) exposés par l'API et appliqués front comme back.
    • Standardisé les contrats API entrants (*Request) et les DTOs de mutation légers, avec pattern refetch GET pour limiter les payloads sur-enrichis ; audit sur 4 modules ayant livré 11+ correctifs, complété côté frontend par le chargement à la demande des listes et un cache RxJS.
    • Centralisé le logging applicatif avec Serilog sur le host ASP.NET Core (.NET 10) : logs structurés JSON, enrichissement multi-tenant (TenantId, UserId, CorrelationId, header X-Correlation-ID) et configuration pilotée par appsettings.
    • Renforcé la couverture de tests sur toute la stack : tests d'intégration PostgreSQL sous Testcontainers (conflits 409, isolation tenant), tests unitaires API, Application et Infrastructure (xUnit, Moq, FluentAssertions) et Angular (Karma/Jasmine), adossés aux quality gates GitHub Actions.
    • Livré et opéré l'API SaaS multi-tenant déployée en conteneur Docker sur Azure App Service via Azure Container Registry (environnements Dev et Staging).

    Stack.NET 10 · Angular 18 · PostgreSQL · JWT · SignalR · Serilog · Testcontainers · Docker · Azure

  2. Développeur Fullstack .NETFreelance · Tunis

    Plugin AutoCAD pour ouvrages hydrauliques et plateforme de licensing

    • Développé un plugin AutoCAD .NET 8 automatisant la création de dalots et de fiches techniques à partir d'un alignement et d'une surface, avec calculs géométriques et hydrauliques et génération du modèle 3D.
    • Intégré le plugin avec le logiciel HY-8 (FHWA) via export automatique des données calculées, pour vérifier les résultats hydrauliques.
    • Construit une plateforme de licensing custom : identification matérielle, API REST de vérification et mécanisme de heartbeat bloquant l'accès si la licence devient invalide.

    Stack.NET 8 · AutoCAD .NET API · Razor Pages · Dapper · SQL Server

  3. Développeur Fullstack .NETFreelance · Tunis

    GSL-MMM — ERP de gestion des livraisons et clients

    • Développé le module de gestion des décharges liées aux factures, améliorant le suivi financier des livraisons.
    • Développé le module de livraison inter-dépôt (planification, suivi et traçabilité des transferts de stock entre sites) et un template de retour client, en complément de correctifs sur la gestion clients.

    Stack.NET 5 · Entity Framework · SQL Server · jQuery

  4. Développeur Fullstack .NET CoreConsultant Ministère des Transports du Québec

    APVHN — gestion des permis pour convois hors normes

    • Conçu l'architecture applicative d'un système de gestion de permis de niveau gouvernemental et développé les modules métier : expertises, véhicules, génération de permis.
    • Géré les connexions utilisateurs et les contrôles d'accès, et intégré des services externes pour automatiser les échanges de données.
    • Piloté la migration progressive de .NET 6 vers .NET 8 sur l'ensemble de la solution et mis en place les tests unitaires xUnit sur les modules principaux.
    • Développé Studio 44, une application de gestion de centre Pilates : cours, abonnements, inscriptions, avec une refonte UX/UI du parcours d'inscription pour réduire les abandons.

    Stack.NET 6/8 · Razor Pages · SQL Server · xUnit · Azure DevOps

  5. Développeur Fullstack .NETConsultant externe Tunisie Télécom

    Workflow Backbone — plateforme nationale de gestion des inscriptions Internet

    • Identifié et corrigé les goulots d'étranglement via profiling SQL et analyse des plans d'exécution, sur une plateforme à l'échelle nationale.
    • Intégré des services web tiers (REST, SOAP) pour enrichir la qualification et l'acceptation des requêtes, et amélioré les procédures stockées sur SQL Server et PostgreSQL.

    Stack.NET Standard · SOAP · SQL Server · PostgreSQL

  6. Développeur Fullstack .NETTunis

    Logiciel de ticketing multicanal

    • Développé la gestion du parc informatique et automatisé les workflows métier : priorisation, SLA, notifications.
    • Amélioré les performances via mise en cache et optimisation des requêtes SQL, participé à la migration des modules vers .NET Core et optimisé les pipelines CI/CD sur TeamCity.
    • Projet conduit en Agile Scrum, avec mentorat de développeurs juniors via code reviews et sessions de pair programming.

    Stack.NET Framework 4.8 · React · TypeScript · TeamCity

08 / Stack

Stack technique

Cœur

  • C#
  • .NET 10
  • ASP.NET Core
  • EF Core 10
  • TypeScript
  • Angular 18
  • RxJS

Architecture

  • Clean Architecture
  • CQRS
  • Custom Dispatcher
  • Application Services
  • Specification Pattern
  • Mapperly
  • FluentValidation

Données

  • PostgreSQL
  • SQL Server
  • xmin
  • Global Query Filters
  • T-SQL

Sécurité et API

  • ASP.NET Core Identity
  • JWT
  • RBAC
  • SignalR
  • REST
  • Swagger / OpenAPI

Observabilité et tests

  • Serilog
  • xUnit
  • Moq
  • FluentAssertions
  • Testcontainers

Cloud et CI/CD

  • Docker
  • Azure App Service
  • Azure Container Registry
  • GitHub Actions
  • Azure DevOps
  • TeamCity

Notions complémentaires

  • React
  • Next.js
  • Node.js
  • Payload CMS
  • Blazor
  • Dapper
  • SOAP
  • AutoCAD .NET API

Survolez pour le contexte d'usage

10 / Questions

Questions fréquentes

Comment isoler les données entre clients dans une application SaaS .NET ?

En portant l'isolation dans la couche d'accès aux données plutôt que dans chaque requête. Avec EF Core, un Global Query Filter appliqué à toutes les entités concernées ajoute automatiquement la condition sur le tenant à chaque requête LINQ générée. Le tenant provient d'un claim JWT validé, jamais du corps de la requête. Un développeur qui oublie une clause WHERE ne peut alors pas exposer les données d'un autre client.

Étude de cas liéeIsolation multi-tenant : isoler sans ralentir l'API
xmin ou une colonne de version : que choisir pour la concurrence optimiste ?

Sur PostgreSQL, xmin est préférable dans la plupart des cas. C'est une colonne système déjà maintenue par la base à chaque écriture : aucune colonne à ajouter, aucune migration, aucun risque d'oublier de l'incrémenter. Une colonne de version applicative garde son intérêt si vous devez rester portable entre plusieurs moteurs de base de données.

Étude de cas liéeConcurrence optimiste : le conflit qu'on ne voit pas
Faut-il quitter MediatR depuis son passage en licence payante ?

Cela dépend de l'usage réel. Si vous utilisez les pipelines, les notifications et le comportement complet de la bibliothèque, la licence est probablement le choix raisonnable. Si vous n'appelez que Send, comme beaucoup d'équipes, la surface à remplacer tient en une interface et une classe de dispatch — soit une journée de travail contre un coût récurrent.

Étude de cas liéeSortir de MediatR et AutoMapper, en production
Fedi Soltani est-il disponible pour une mission ou un poste ?

Oui, ouvert aux opportunités autour de .NET, du SaaS multi-tenant et de l'architecture backend, en remote comme en relocalisation. Basé à Tunis. Contact par email à soltanifedi68@gmail.com ou via LinkedIn.

11 / Contact

Parlons architecture

Ouvert aux missions et postes autour du .NET, du SaaS multi-tenant et de l'architecture backend.

Écrire un emailLinkedInTélécharger le CV
Téléphone
+216 53 064 275 · +216 55 517 224
Basé à
Tunis, Tunisie — ouvert au remote et à la relocalisation
Formation
Diplôme national d'ingénieur en informatique — ESPRIT, Tunis (2022)
Langues
Arabe (natif) · Français (professionnel) · Anglais (B2)