Expertises:Domain[4]

Un socle technique solide et éprouvé

Nous choisissons des technologies matures et largement documentées. Un produit qui vivra dix ans ne se construit pas sur ce qui est sorti l'an dernier.

Nous ne pratiquons que quatre environnements, et nous les pratiquons tous les jours. C'est un choix qui ferme des portes : nous refusons régulièrement des missions sur des technologies que nous connaissons mal, parce qu'un studio qui prétend tout maîtriser ne maîtrise rien. En échange, sur ces quatre-là, nous savons ce qui casse au bout de trois ans, quelles bibliothèques seront abandonnées, et lesquelles tiendront. Les quatre spécimens de code ci-dessous sont extraits de projets réels, pas écrits pour la vitrine.

.NET & C#

Back-ends métiers, API et traitements de fond. C'est notre terrain d'origine : découpage en cas d'usage, requêtes SQL dont nous lisons le plan d'exécution, et migrations de schéma qui passent sans arrêter le service.

ASP.NET Core 8Minimal APIsEntity Framework CoreCQRS / MediatRSQL ServerPostgreSQLSignalRgRPCAzure Functions
OrderHandler.cs
public sealed class CreateOrderHandler
    : IRequestHandler<CreateOrder, Guid>
{
    private readonly IOrderRepository _repo;

    public async Task<Guid> Handle(CreateOrder cmd, CancellationToken ct)
    {
        var order = Order.Create(cmd.CustomerId, cmd.Lines);
        await _repo.AddAsync(order, ct);
        return order.Id;
    }
}

C'est notre terrain d'origine, et celui où nous sommes le plus difficiles à prendre en défaut. Un back-end .NET, nous en avons écrit, repris, découpé et parfois sauvé assez pour savoir où les problèmes se logent. Rarement dans le langage. Presque toujours à la frontière avec la base de données, et dans ce que l'équipe précédente a cru pouvoir remettre à plus tard.

Le choix de .NET tient à trois propriétés ennuyeuses et décisives. Les montées de version majeures se passent sans drame depuis .NET 6, ce qui n'a pas toujours été vrai. Les performances sont mesurées publiquement à chaque sortie, donc vérifiables par quelqu'un d'autre que l'éditeur. Et on trouve des développeurs à recruter, ce qui décide de la vie d'un logiciel plus sûrement que l'élégance de sa syntaxe.

Ce que nous savons en faire

Le SQL que nous lisons vraiment

Entity Framework Core rend le SQL invisible, et c'est là le piège. Une boucle qui charge des commandes puis leurs lignes une par une produit deux cent une requêtes au lieu de deux, et la page met huit secondes à s'afficher sans qu'aucune ligne de C# ne paraisse fautive. Nous lisons les requêtes générées et leur plan d'exécution, pas seulement les temps de réponse. Un temps de réponse dit qu'il y a un problème ; un plan d'exécution dit lequel.

En pratique cela veut dire savoir quand descendre en SQL écrit à la main, et l'assumer. Sur une requête de rapport avec cinq jointures et une fenêtre glissante, un ORM produit du code illisible et lent. Nous écrivons alors la requête, nous la testons, et nous la commentons. L'inverse est vrai aussi : sur les écritures courantes, EF Core fait mieux et plus sûrement que du SQL recopié dans quinze fichiers.

Les migrations de schéma sans coupure

Renommer une colonne en production casse l'application pendant le déploiement, parce que l'ancienne version du code tourne encore quand la nouvelle structure arrive. Nous procédons donc en deux temps : on ajoute la nouvelle colonne, on écrit dans les deux pendant une version, puis on retire l'ancienne à la version suivante. C'est deux fois plus de déploiements et zéro minute d'interruption.

Cette discipline a un effet secondaire qu'on mesure vite : elle rend les retours en arrière possibles. Une migration qui ne détruit rien peut être annulée en revenant à la version précédente du code, sans restauration de sauvegarde et sans réunion de crise. C'est la différence entre un incident de vingt minutes et une soirée entière.

Traitements de fond, files et idempotence

Tout ce qui est lent doit sortir du cycle de la requête HTTP : envoi de courriels, génération de documents, appels à des services tiers. La règle est simple à énoncer et souvent mal appliquée, parce qu'on oublie la suite : un traitement de fond sera rejoué. Une file de messages garantit qu'un message arrive au moins une fois, jamais exactement une fois, et un serveur qui redémarre au mauvais moment suffit à produire le doublon.

Nous écrivons donc ces traitements pour qu'un rejeu ne fasse pas de dégât : une clé d'idempotence, un état persisté, et la vérification avant l'effet plutôt qu'après. Sans cela, le client reçoit deux factures et personne ne comprend pourquoi, parce que les journaux montrent un seul appel côté métier. Ce genre de bogue coûte plusieurs jours à diagnostiquer et se prévient en une heure de conception.

Quand ce socle est le bon choix

  • Votre métier a des règles écrites quelque part et il faut qu'elles soient appliquées de la même façon partout.
  • Vous exposez ou consommez des API que d'autres équipes utilisent, avec des engagements de compatibilité.
  • Vos volumes de données rendent la moindre requête mal écrite visible sur la facture d'infrastructure.
  • Vous êtes déjà sur du .NET ancien et vous voulez avancer sans tout jeter.

L'arbitrage que nous assumons

L'écosystème .NET est plus verbeux que ses concurrents et démarre plus lentement sur les petits projets. Sur un prototype de trois écrans à sortir en trois semaines, un socle plus léger ira plus vite et nous vous le dirons. L'arbitrage s'inverse dès qu'il y a des règles métier, plusieurs développeurs, et un horizon de plus de deux ans.

Chaque socle en a un. Celui qui prétend le contraire vous le fera payer plus tard.

Mobile

Applications iOS et Android, natives ou cross-platform. Nous concevons d'abord pour la coupure réseau : sur le terrain, la connexion est un bonus, pas une garantie. Synchronisation, résolution de conflits et publication sur les stores comprises.

.NET MAUIReact NativeXamarin migrationSQLite / RealmOffline syncPushApp Store / Play StoreMDM / MAM
SyncService.ts
export async function syncOrders(db: Database) {
  const pending = await db.table("orders").where({ synced: false });

  for (const order of pending) {
    await api.post("/orders", order);
    await db.update(order.id, { synced: true });
  }
}

Le mobile est le seul environnement où l'utilisateur peut vous supprimer d'un geste. Cette asymétrie change tout : la tolérance à la lenteur y est nulle, un bug ne se corrige pas en poussant une version, et une mise à jour n'est installée que si quelqu'un le décide. On ne conçoit pas une application mobile comme un site web qu'on aurait rétréci.

Nous construisons en .NET MAUI et en React Native, en natif quand le projet l'exige, et nous reprenons des applications Xamarin qui ont besoin de sortir de l'impasse. Le cross-platform n'est pas un compromis honteux : sur une application de saisie ou de consultation, il divise le coût par deux sans que l'utilisateur voie la différence.

Ce que nous savons en faire

La synchronisation, et les conflits qu'elle produit

Concevoir pour la coupure réseau ne consiste pas à ajouter un cache à la fin. L'application écrit d'abord en local, puis synchronise, et l'interface ne bloque jamais en attendant une réponse. Sur le terrain, cette différence est celle entre un technicien qui saisit son intervention dans un sous-sol et un technicien qui la ressaisit le soir, de mémoire, avec les erreurs que cela suppose.

La partie difficile n'est pas le stockage local, elle est dans les conflits. Deux personnes modifient la même fiche hors ligne : qui gagne ? Nous tranchons cette question au cadrage, avec vos utilisateurs métier, parce que la réponse dépend de votre activité. Sur un inventaire, la dernière écriture peut l'emporter. Sur un dossier de soin, sûrement pas, et il faut alors présenter les deux versions à un humain. C'est une décision qu'on ne rattrape pas après la mise en production.

Cross-platform ou natif : où passe la ligne

Nous plaçons la frontière sur trois critères, et ils se vérifient sur les maquettes avant d'écrire du code. Le premier est l'accès matériel : caméra en temps réel, Bluetooth basse consommation, capteurs à haute fréquence. Le second est l'animation : si l'interface repose sur des transitions continues pilotées au doigt, le natif reste devant. Le troisième est la durée de vie prévue, parce qu'une base cross-platform vieillit moins bien qu'une base native sur dix ans.

Hors de ces trois cas, le cross-platform gagne, et il gagne largement. Une seule base de code, une seule logique métier, un seul jeu de tests. Les écarts qui restent sont réels mais localisés : on écrit une poignée de fichiers spécifiques par plateforme et on documente pourquoi. Prétendre qu'il n'y en a jamais serait faux, et vous le découvririez au pire moment.

Ce que les stores imposent, et ce qu'on prépare

Une première revue Apple prend entre deux jours et deux semaines, et un premier refus est la norme plutôt que l'exception. Les motifs que nous rencontrons le plus souvent ne sont pas techniques : politique de confidentialité absente, compte de test qui ne fonctionne pas, fonctionnalité que le validateur n'arrive pas à atteindre. Préparer ces éléments avant de soumettre transforme trois allers-retours en un seul.

Nous mettons aussi en place la distribution interne dès le premier jour, TestFlight côté iOS et pistes fermées côté Play. Vingt collègues qui utilisent l'application pendant trois semaines avant la sortie publique, cela coûte quelques heures de configuration et cela évite la version corrective publiée en urgence le lendemain du lancement, celle qui laisse une note d'une étoile en permanence sur la fiche.

Quand ce socle est le bon choix

  • Vos équipes travaillent dans des lieux où le réseau tombe : sous-sols, sites industriels, zones rurales.
  • Vous avez besoin de notifications ciblées et de savoir ce qu'elles rapportent réellement.
  • Une première version a été sous-traitée et vous n'avez ni les comptes des stores ni les clés de signature.
  • L'usage se fait debout, à une main, avec des gants ou en extérieur au soleil.

L'arbitrage que nous assumons

Une application mobile impose deux plateformes à maintenir, des revues de store à chaque version, et un parc d'utilisateurs qui ne mettent pas tous à jour. Si votre besoin se résume à consulter des informations en ligne, sans capteur, sans hors-ligne et sans notification, un site web bien conçu fera le même travail sans aucune de ces contraintes. La vraie question n'est pas technique : avez-vous besoin d'une icône sur leur écran d'accueil ?

Chaque socle en a un. Celui qui prétend le contraire vous le fera payer plus tard.

Angular

Applications d'entreprise structurées pour durer et pour être reprises. Signals plutôt que RxJS partout, composants autonomes, et un design system qui évite que chaque écran réinvente son bouton.

Angular 17+SignalsRxJSNgRxStandalone ComponentsNx monorepoStorybookJest / Cypress
orders.store.ts
@Injectable({ providedIn: "root" })
export class OrdersStore {
  private readonly api = inject(OrdersApi);
  readonly orders = signal<Order[]>([]);

  async load() {
    const data = await firstValueFrom(this.api.list());
    this.orders.set(data);
  }
}

Angular a mauvaise réputation auprès de ceux qui l'ont pratiqué entre 2016 et 2020, et cette réputation était méritée. Elle ne l'est plus. Les composants autonomes ont supprimé la cérémonie des modules, les signaux ont rendu la réactivité lisible, et le compilateur attrape aujourd'hui dans les gabarits des erreurs qui partaient autrefois en production.

Nous le choisissons pour les applications d'entreprise, celles que plusieurs développeurs se passent sur plusieurs années. Ses conventions imposées sont exactement ce qu'on reproche à Angular quand on est seul, et exactement ce qu'on lui demande quand on est huit : personne n'invente sa propre structure, donc personne n'a besoin de la déchiffrer.

Ce que nous savons en faire

Signals plutôt que RxJS partout

RxJS reste irremplaçable pour ce qu'il fait bien : les flux d'événements, les annulations, les combinaisons temporelles. Le problème n'a jamais été RxJS, il a été son usage pour représenter un simple état local. Un compteur enveloppé dans un `BehaviorSubject` avec son `asObservable`, son `async` dans le gabarit et sa désinscription à gérer, c'est quinze lignes et trois occasions de se tromper pour ce qu'un signal exprime en une.

Notre règle est devenue simple : signaux pour l'état, RxJS pour les événements. Le tri se fait en une question, est-ce que cette valeur EST quelque chose ou est-ce qu'elle ARRIVE. Un utilisateur connecté est. Un clic arrive. Cette distinction supprime la plupart des débats d'architecture front que nous voyions revenir tous les trimestres, et elle s'explique à un nouvel arrivant en dix minutes.

Composants autonomes et monorepo

Les composants autonomes ont supprimé la question « dans quel module je déclare ça », qui n'apportait rien et coûtait une réunion par trimestre. Un composant déclare ce dont il a besoin, et on lit ses dépendances en haut de son propre fichier. Sur une reprise de code, c'est la différence entre comprendre un écran en deux minutes et remonter trois niveaux de modules pour trouver d'où vient une directive.

Le monorepo Nx devient utile à partir de deux applications qui partagent du code, pas avant. Son apport réel n'est pas le rangement des dossiers mais le graphe de dépendances : il sait quels projets sont touchés par un changement, donc il ne teste et ne reconstruit que ceux-là. Sur une base de taille moyenne, cela ramène une intégration de vingt minutes à moins de cinq, et c'est ce chiffre qui décide si l'équipe lance les tests avant de pousser.

Tester sans y passer sa vie

Nous testons peu de composants et beaucoup de logique. Un test qui monte un composant pour vérifier qu'un bouton existe casse au premier changement de maquette et n'a jamais rien détecté d'utile. Un test qui vérifie qu'un panier refuse une quantité négative garde sa valeur pendant des années, parce qu'il porte sur une règle et pas sur un rendu.

Au-dessus, quelques parcours de bout en bout suffisent : connexion, action principale, paiement s'il y en a un. Cinq parcours bien choisis attrapent plus de régressions que deux cents tests de composants, et coûtent moins cher à maintenir. Le piège classique est d'en écrire cinquante, de les voir échouer de façon intermittente, et de finir par les désactiver tous.

Quand ce socle est le bon choix

  • L'application sera reprise par une autre équipe que celle qui l'écrit.
  • Vous avez plus de quatre développeurs front, ou un renouvellement régulier.
  • Des règles d'accessibilité vous sont imposées par un client public ou un grand compte.
  • L'application vivra plus de cinq ans et devra suivre les montées de version sans réécriture.

L'arbitrage que nous assumons

Angular impose sa structure, et cette structure pèse sur les petits projets. À deux développeurs sur une application de six écrans, elle apporte des contraintes sans les bénéfices qui les justifient. Nous partons alors sur React, ou sur un socle plus léger encore. Le seuil se situe autour de trois développeurs ou de deux ans de vie prévue, et nous le disons au cadrage plutôt qu'au troisième mois.

Chaque socle en a un. Celui qui prétend le contraire vous le fera payer plus tard.

React

Interfaces publiques et produits grand public, là où l'écosystème React apporte plus qu'il ne coûte. Rendu serveur quand le référencement compte, et mesure des performances réelles plutôt que des scores de laboratoire.

React 19TanStack RouterTanStack QueryNext.jsViteTailwind CSSshadcn/uiPlaywright
useOrders.ts
export function useOrders() {
  return useQuery({
    queryKey: ["orders"],
    queryFn: () => api.get<Order[]>("/orders"),
    staleTime: 30_000,
  });
}

React est l'environnement où l'on trouve le plus de bibliothèques, le plus de développeurs, et le plus de mauvaises décisions déjà prises par d'autres. Sa liberté est un avantage réel entre les mains d'une équipe qui sait ce qu'elle veut, et un piège pour celle qui découvre. La différence ne tient pas au talent, elle tient au nombre de décisions qu'il faut prendre avant d'écrire la première ligne utile.

Nous l'utilisons pour les interfaces publiques et les produits grand public, là où l'écosystème rend plus qu'il ne coûte : rendu serveur mûr, outillage d'images et de polices, animations, et une communauté qui a documenté à peu près tous les cas limites. Le prix de cette liberté est qu'il faut la contraindre soi-même, et c'est la première chose que nous faisons.

Ce que nous savons en faire

Le rendu serveur, quand il sert vraiment

Le rendu serveur répond à deux besoins, et à deux seulement : être lu par un moteur de recherche, et afficher quelque chose d'utile avant que le JavaScript soit arrivé. Sur une application derrière une authentification, il n'apporte presque rien et ajoute une infrastructure à exploiter. Nous commençons donc par demander qui doit lire ces pages, et la réponse tranche.

Quand le référencement compte, la génération statique bat le rendu à la demande dans la majorité des cas. Une page construite à l'avance se sert en quelques millisecondes depuis un cache, sans serveur à surveiller ni pic de charge à absorber. C'est le choix que nous avons fait pour ce site : les quatre cent soixante-dix-sept pages publiques sont prérendues, y compris chaque article et chaque fiche.

L'état serveur n'est pas de l'état client

La confusion entre les deux a produit une génération entière d'applications où l'on recopiait à la main dans un magasin global des données qui appartenaient au serveur, avec le chargement, l'erreur et la péremption à gérer à chaque fois. TanStack Query règle ce problème en le nommant : les données distantes ont un propriétaire ailleurs, elles se périment, et le rôle du client est de les mettre en cache correctement, pas de les posséder.

Ce qui reste après ce tri est minuscule. Un thème, un panneau ouvert ou fermé, le contenu d'un formulaire en cours : c'est du véritable état client, et il tient dans les outils les plus simples. Nous voyons régulièrement des applications où le magasin global disparaît presque entièrement une fois cette séparation faite, et où la quantité de code chute d'un tiers.

La fatigue de l'écosystème, et comment on la limite

Le reproche fait à React n'est pas infondé : cinq façons de router, quatre de styler, trois de tester, et un consensus qui se déplace tous les dix-huit mois. Sur un projet qui doit durer, ce n'est pas une richesse, c'est une dette de décision qui se paie à chaque recrutement et à chaque montée de version.

Nous figeons donc les choix structurants au cadrage, nous les écrivons dans un document d'une page, et nous n'en changeons pas en cours de route sans une raison qu'on puisse défendre. Une bibliothèque nouvelle et séduisante n'est pas une raison. Chaque dépendance ajoutée est évaluée sur trois points : qui la maintient, ce qu'elle coûterait à retirer, et si le problème qu'elle résout est vraiment le nôtre.

Quand ce socle est le bon choix

  • Vos pages doivent être trouvées par un moteur de recherche et se charger vite sur mobile.
  • L'interface est le produit, avec des attentes visuelles élevées et des itérations fréquentes.
  • Votre équipe est petite, expérimentée, et a déjà ses habitudes dans cet écosystème.
  • Vous devez recruter vite, sur un marché où React reste la compétence la plus répandue.

L'arbitrage que nous assumons

La liberté de React se paie en décisions, et une équipe qui change souvent finit par en prendre de contradictoires. Si votre effectif front tourne beaucoup, ou si le code sera repris par une équipe que vous ne connaissez pas encore, les conventions imposées d'Angular protègent mieux. Nous le disons même quand React nous conviendrait mieux à nous : ce n'est pas nous qui vivrons dans ce code dans quatre ans.

Chaque socle en a un. Celui qui prétend le contraire vous le fera payer plus tard.

Approche:Principle[3]

Ce que nous refusons de négocier

Livrer vite et livrer quelque chose qui tienne ne sont pas la même compétence. La différence se joue sur trois points, et ce sont ceux sur lesquels nous ne cédons pas, y compris quand le calendrier serre. Ils valent pour les quatre stacks : le langage change, pas la façon de travailler.

Tests systématiques

Unitaires, intégration et bout en bout. Le code non testé n'entre pas en production, et cette phrase n'est pas une posture : la chaîne d'intégration refuse la fusion quand la couverture baisse. Les tests sont écrits pendant le développement, jamais rattrapés après coup, car un test écrit après le code se contente de décrire les bugs existants.

CI/CD complète

Pipelines Git, environnements éphémères montés par branche, revues de code obligatoires y compris sur les correctifs d'une ligne, et déploiements traçables jusqu'au commit. Le déploiement devient un non-événement, ce qui est exactement le but : on ne devrait jamais avoir peur de livrer un vendredi.

Sécurité & qualité

Analyse statique à chaque commit, dépendances scannées et mises à jour en continu, secrets sortis du dépôt, RGPD pris en compte à la conception plutôt qu'ajouté après coup. Les vulnérabilités connues sont corrigées dans la semaine, pas au prochain trimestre.

Un projet en tête ? Donnons-lui vie.

Dites-nous ce que vous cherchez à construire ou à reprendre. Réponse sous 24 heures ouvrées, sans engagement, et un avis franc sur la faisabilité même si la réponse ne nous arrange pas.