[{"data":1,"prerenderedAt":969},["ShallowReactive",2],{"docs-nav:fr":3,"docs:\u002Ffr\u002Fdocs\u002Fbusiness\u002Fports":189},[4],{"title":5,"path":6,"stem":7,"children":8,"page":52},"Fr","\u002Ffr","fr",[9],{"title":10,"path":11,"stem":12,"children":13},"Qu'est-ce que Fougere","\u002Ffr\u002Fdocs","fr\u002Fdocs\u002Findex",[14,15,19,23,27,31,53,78,123,148],{"title":10,"path":11,"stem":12},{"title":16,"path":17,"stem":18},"Démarrer","\u002Ffr\u002Fdocs\u002Fgetting-started","fr\u002Fdocs\u002F01.getting-started",{"title":20,"path":21,"stem":22},"Une app que vous avez déjà","\u002Ffr\u002Fdocs\u002Fexisting-app","fr\u002Fdocs\u002F02.existing-app",{"title":24,"path":25,"stem":26},"Apportez votre schéma","\u002Ffr\u002Fdocs\u002Fbring-your-schema","fr\u002Fdocs\u002F03.bring-your-schema",{"title":28,"path":29,"stem":30},"La CLI","\u002Ffr\u002Fdocs\u002Fcli","fr\u002Fdocs\u002F04.cli",{"title":32,"path":33,"stem":34,"children":35,"page":52},"Concepts","\u002Ffr\u002Fdocs\u002Fconcepts","fr\u002Fdocs\u002F2.concepts",[36,40,44,48],{"title":37,"path":38,"stem":39},"Philosophie","\u002Ffr\u002Fdocs\u002Fconcepts\u002Fphilosophy","fr\u002Fdocs\u002F2.concepts\u002F1.philosophy",{"title":41,"path":42,"stem":43},"La Frond","\u002Ffr\u002Fdocs\u002Fconcepts\u002Ffrond","fr\u002Fdocs\u002F2.concepts\u002F2.frond",{"title":45,"path":46,"stem":47},"Le chemin le plus court","\u002Ffr\u002Fdocs\u002Fconcepts\u002Fshortest-path","fr\u002Fdocs\u002F2.concepts\u002F3.shortest-path",{"title":49,"path":50,"stem":51},"Le socle","\u002Ffr\u002Fdocs\u002Fconcepts\u002Fthe-base","fr\u002Fdocs\u002F2.concepts\u002F4.the-base",false,{"title":54,"path":55,"stem":56,"children":57,"page":52},"Schéma","\u002Ffr\u002Fdocs\u002Fschema","fr\u002Fdocs\u002F3.schema",[58,62,66,70,74],{"title":59,"path":60,"stem":61},"Entités","\u002Ffr\u002Fdocs\u002Fschema\u002Fentities","fr\u002Fdocs\u002F3.schema\u002F1.entities",{"title":63,"path":64,"stem":65},"Vues","\u002Ffr\u002Fdocs\u002Fschema\u002Fviews","fr\u002Fdocs\u002F3.schema\u002F2.views",{"title":67,"path":68,"stem":69},"La carte d'identité","\u002Ffr\u002Fdocs\u002Fschema\u002Fcard","fr\u002Fdocs\u002F3.schema\u002F3.card",{"title":71,"path":72,"stem":73},"Standard Schema","\u002Ffr\u002Fdocs\u002Fschema\u002Fstandard-schema","fr\u002Fdocs\u002F3.schema\u002F4.standard-schema",{"title":75,"path":76,"stem":77},"Comment un schéma bouge","\u002Ffr\u002Fdocs\u002Fschema\u002Fevolution","fr\u002Fdocs\u002F3.schema\u002F5.evolution",{"title":79,"path":80,"stem":81,"children":82,"page":52},"Métier","\u002Ffr\u002Fdocs\u002Fbusiness","fr\u002Fdocs\u002F4.business",[83,87,91,95,99,103,107,111,115,119],{"title":84,"path":85,"stem":86},"Handlers","\u002Ffr\u002Fdocs\u002Fbusiness\u002Fhandlers","fr\u002Fdocs\u002F4.business\u002F1.handlers",{"title":88,"path":89,"stem":90},"Les ports","\u002Ffr\u002Fdocs\u002Fbusiness\u002Fports","fr\u002Fdocs\u002F4.business\u002F10.ports",{"title":92,"path":93,"stem":94},"Presenters","\u002Ffr\u002Fdocs\u002Fbusiness\u002Fpresenters","fr\u002Fdocs\u002F4.business\u002F2.presenters",{"title":96,"path":97,"stem":98},"Collectors","\u002Ffr\u002Fdocs\u002Fbusiness\u002Fcollectors","fr\u002Fdocs\u002F4.business\u002F3.collectors",{"title":100,"path":101,"stem":102},"Erreurs","\u002Ffr\u002Fdocs\u002Fbusiness\u002Ferrors","fr\u002Fdocs\u002F4.business\u002F4.errors",{"title":104,"path":105,"stem":106},"Seeds","\u002Ffr\u002Fdocs\u002Fbusiness\u002Fseeds","fr\u002Fdocs\u002F4.business\u002F5.seeds",{"title":108,"path":109,"stem":110},"Les faits","\u002Ffr\u002Fdocs\u002Fbusiness\u002Ffacts","fr\u002Fdocs\u002F4.business\u002F6.facts",{"title":112,"path":113,"stem":114},"Le port Storage","\u002Ffr\u002Fdocs\u002Fbusiness\u002Fstorage","fr\u002Fdocs\u002F4.business\u002F7.storage",{"title":116,"path":117,"stem":118},"Des écritures qui tiennent ou tombent ensemble","\u002Ffr\u002Fdocs\u002Fbusiness\u002Ftogether","fr\u002Fdocs\u002F4.business\u002F8.together",{"title":120,"path":121,"stem":122},"Les repositories","\u002Ffr\u002Fdocs\u002Fbusiness\u002Frepositories","fr\u002Fdocs\u002F4.business\u002F9.repositories",{"title":124,"path":125,"stem":126,"children":127,"page":52},"Client","\u002Ffr\u002Fdocs\u002Fclient","fr\u002Fdocs\u002F5.client",[128,132,136,140,144],{"title":129,"path":130,"stem":131},"Queries & commands","\u002Ffr\u002Fdocs\u002Fclient\u002Fqueries-commands","fr\u002Fdocs\u002F5.client\u002F1.queries-commands",{"title":133,"path":134,"stem":135},"Formulaires","\u002Ffr\u002Fdocs\u002Fclient\u002Fforms","fr\u002Fdocs\u002F5.client\u002F2.forms",{"title":137,"path":138,"stem":139},"Session","\u002Ffr\u002Fdocs\u002Fclient\u002Fsession","fr\u002Fdocs\u002F5.client\u002F3.session",{"title":141,"path":142,"stem":143},"invoke","\u002Ffr\u002Fdocs\u002Fclient\u002Finvoke","fr\u002Fdocs\u002F5.client\u002F4.invoke",{"title":145,"path":146,"stem":147},"Back-office","\u002Ffr\u002Fdocs\u002Fclient\u002Fadmin","fr\u002Fdocs\u002F5.client\u002F5.admin",{"title":149,"path":150,"stem":151,"children":152,"page":52},"Infrastructure","\u002Ffr\u002Fdocs\u002Finfra","fr\u002Fdocs\u002F6.infra",[153,157,161,165,169,173,177,181,185],{"title":154,"path":155,"stem":156},"Le gradient","\u002Ffr\u002Fdocs\u002Finfra\u002Fgradient","fr\u002Fdocs\u002F6.infra\u002F1.gradient",{"title":158,"path":159,"stem":160},"Surfaces — REST & GraphQL","\u002Ffr\u002Fdocs\u002Finfra\u002Fsurfaces","fr\u002Fdocs\u002F6.infra\u002F2.surfaces",{"title":162,"path":163,"stem":164},"Déploiement","\u002Ffr\u002Fdocs\u002Finfra\u002Fdeployment","fr\u002Fdocs\u002F6.infra\u002F3.deployment",{"title":166,"path":167,"stem":168},"Hôtes","\u002Ffr\u002Fdocs\u002Finfra\u002Fhosts","fr\u002Fdocs\u002F6.infra\u002F4.hosts",{"title":170,"path":171,"stem":172},"Les sources","\u002Ffr\u002Fdocs\u002Finfra\u002Fsources","fr\u002Fdocs\u002F6.infra\u002F5.sources",{"title":174,"path":175,"stem":176},"Le cycle de vie","\u002Ffr\u002Fdocs\u002Finfra\u002Flifecycle","fr\u002Fdocs\u002F6.infra\u002F6.lifecycle",{"title":178,"path":179,"stem":180},"L'identité entre Fronds","\u002Ffr\u002Fdocs\u002Finfra\u002Fidentity","fr\u002Fdocs\u002F6.infra\u002F7.identity",{"title":182,"path":183,"stem":184},"Observabilité","\u002Ffr\u002Fdocs\u002Finfra\u002Fobservability","fr\u002Fdocs\u002F6.infra\u002F8.observability",{"title":186,"path":187,"stem":188},"Tests","\u002Ffr\u002Fdocs\u002Finfra\u002Ftesting","fr\u002Fdocs\u002F6.infra\u002F9.testing",{"id":190,"title":88,"body":191,"description":964,"extension":965,"meta":966,"navigation":300,"path":89,"seo":967,"stem":90,"__hash__":968},"docs_fr\u002Ffr\u002Fdocs\u002F4.business\u002F10.ports.md",{"type":192,"value":193,"toc":957},"minimark",[194,198,207,210,495,514,519,538,556,565,569,584,663,666,674,681,685,688,696,699,831,867,881,885,892,915,940,944,947,953],[195,196,88],"h1",{"id":197},"les-ports",[199,200,201,202,206],"p",{},"Un handler qui écrit ",[203,204,205],"code",{},"constructor(private payment: StripePayment)"," a nommé son\nfournisseur. Tout ce qui suit cette ligne — les tests, le bac à sable, le jour où le PSP\nchange — le paie.",[199,208,209],{},"Déclarer la capacité, et l'étendre :",[211,212,217],"pre",{"className":213,"code":214,"language":215,"meta":216,"style":216},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","\u002F\u002F services\u002FPayment.ts — le port. Il ne nomme aucun fournisseur.\nexport default abstract class Payment {\n  abstract charge(amountCents: number): Charge;\n}\n\n\u002F\u002F services\u002FStripePayment.ts — la réalisation.\nexport default class StripePayment extends Payment {\n  charge(amountCents: number) { return { provider: 'stripe', amountCents }; }\n}\n\n\u002F\u002F handlers\u002FCheckoutHandler.ts — demande le port.\nexport default class CheckoutHandler {\n  constructor(private payment: Payment) {}\n\n  \u002F** Débiter le panier. *\u002F\n  async pay() { return this.payment.charge(4990); }\n}\n","ts","",[203,218,219,228,253,289,295,302,308,327,381,386,391,397,411,434,439,445,490],{"__ignoreMap":216},[220,221,224],"span",{"class":222,"line":223},"line",1,[220,225,227],{"class":226},"sutJx","\u002F\u002F services\u002FPayment.ts — le port. Il ne nomme aucun fournisseur.\n",[220,229,231,235,238,242,245,249],{"class":222,"line":230},2,[220,232,234],{"class":233},"sVHd0","export",[220,236,237],{"class":233}," default",[220,239,241],{"class":240},"sbsja"," abstract",[220,243,244],{"class":240}," class",[220,246,248],{"class":247},"sbgvK"," Payment",[220,250,252],{"class":251},"sP7_E"," {\n",[220,254,256,259,263,266,270,274,278,281,283,286],{"class":222,"line":255},3,[220,257,258],{"class":240},"  abstract",[220,260,262],{"class":261},"sVXei"," charge",[220,264,265],{"class":251},"(",[220,267,269],{"class":268},"s99_P","amountCents",[220,271,273],{"class":272},"smGrS",":",[220,275,277],{"class":276},"sZMiF"," number",[220,279,280],{"class":251},")",[220,282,273],{"class":272},[220,284,285],{"class":247}," Charge",[220,287,288],{"class":251},";\n",[220,290,292],{"class":222,"line":291},4,[220,293,294],{"class":251},"}\n",[220,296,298],{"class":222,"line":297},5,[220,299,301],{"emptyLinePlaceholder":300},true,"\n",[220,303,305],{"class":222,"line":304},6,[220,306,307],{"class":226},"\u002F\u002F services\u002FStripePayment.ts — la réalisation.\n",[220,309,311,313,315,317,320,323,325],{"class":222,"line":310},7,[220,312,234],{"class":233},[220,314,237],{"class":233},[220,316,244],{"class":240},[220,318,319],{"class":247}," StripePayment",[220,321,322],{"class":240}," extends",[220,324,248],{"class":247},[220,326,252],{"class":251},[220,328,330,333,335,337,339,341,343,346,349,351,355,357,361,365,368,371,375,378],{"class":222,"line":329},8,[220,331,332],{"class":261},"  charge",[220,334,265],{"class":251},[220,336,269],{"class":268},[220,338,273],{"class":272},[220,340,277],{"class":276},[220,342,280],{"class":251},[220,344,345],{"class":251}," {",[220,347,348],{"class":233}," return",[220,350,345],{"class":251},[220,352,354],{"class":353},"skxfh"," provider",[220,356,273],{"class":251},[220,358,360],{"class":359},"sjJ54"," '",[220,362,364],{"class":363},"s_sjI","stripe",[220,366,367],{"class":359},"'",[220,369,370],{"class":251},",",[220,372,374],{"class":373},"su5hD"," amountCents",[220,376,377],{"class":251}," };",[220,379,380],{"class":251}," }\n",[220,382,384],{"class":222,"line":383},9,[220,385,294],{"class":251},[220,387,389],{"class":222,"line":388},10,[220,390,301],{"emptyLinePlaceholder":300},[220,392,394],{"class":222,"line":393},11,[220,395,396],{"class":226},"\u002F\u002F handlers\u002FCheckoutHandler.ts — demande le port.\n",[220,398,400,402,404,406,409],{"class":222,"line":399},12,[220,401,234],{"class":233},[220,403,237],{"class":233},[220,405,244],{"class":240},[220,407,408],{"class":247}," CheckoutHandler",[220,410,252],{"class":251},[220,412,414,417,419,422,425,427,429,431],{"class":222,"line":413},13,[220,415,416],{"class":240},"  constructor",[220,418,265],{"class":251},[220,420,421],{"class":240},"private",[220,423,424],{"class":268}," payment",[220,426,273],{"class":272},[220,428,248],{"class":247},[220,430,280],{"class":251},[220,432,433],{"class":251}," {}\n",[220,435,437],{"class":222,"line":436},14,[220,438,301],{"emptyLinePlaceholder":300},[220,440,442],{"class":222,"line":441},15,[220,443,444],{"class":226},"  \u002F** Débiter le panier. *\u002F\n",[220,446,448,451,454,457,459,461,465,468,471,473,477,479,483,485,488],{"class":222,"line":447},16,[220,449,450],{"class":240},"  async",[220,452,453],{"class":261}," pay",[220,455,456],{"class":251},"()",[220,458,345],{"class":251},[220,460,348],{"class":233},[220,462,464],{"class":463},"s39Yj"," this",[220,466,467],{"class":251},".",[220,469,470],{"class":373},"payment",[220,472,467],{"class":251},[220,474,476],{"class":475},"sGLFI","charge",[220,478,265],{"class":353},[220,480,482],{"class":481},"srdBf","4990",[220,484,280],{"class":353},[220,486,487],{"class":251},";",[220,489,380],{"class":251},[220,491,493],{"class":222,"line":492},17,[220,494,294],{"class":251},[199,496,497,500,501,504,505,509,510,513],{},[203,498,499],{},"this.payment"," est un ",[203,502,503],{},"StripePayment",". ",[506,507,508],"strong",{},"Rien ne le déclare"," — ",[203,511,512],{},"extends"," est\nl'enregistrement tout entier, et le boot lit la chaîne de prototypes.",[515,516,518],"h2",{"id":517},"un-port-nest-pas-un-type-de-fichier","Un port n'est pas un type de fichier",[199,520,521,522,525,526,529,530,533,534,537],{},"Il n'y a pas de répertoire ",[203,523,524],{},"ports\u002F"," ni de fabrique ",[203,527,528],{},"Port(...)",". Un port est une classe de\nprovider qu'une autre étend : ",[203,531,532],{},"services\u002F"," et ",[203,535,536],{},"repositories\u002F"," sont scannés comme avant, et\nc'est la relation entre deux de leurs classes qui fait d'une un port. Écrivez la base\nseule, c'est un service ordinaire ; écrivez une classe qui l'étend, et la base devient la\nclé sous laquelle l'implémentation répond.",[199,539,540,541,544,545,548,549,533,552,555],{},"C'est la lecture que le reste du framework applique déjà — le type sur le paramètre nomme\nle ",[506,542,543],{},"sujet",", le conteneur détient la réalisation. C'est ainsi que se résolvent\n",[203,546,547],{},"RepositoryOf\u003CPost>",", ",[203,550,551],{},"Emit\u003CPostPublished>",[203,553,554],{},"Facade\u003CPostHandler>",". Les providers étaient le\nseul cas où le type nommait la réalisation.",[557,558,559],"note",{},[199,560,561,564],{},[203,562,563],{},"abstract"," est un mot-clé TypeScript, effacé au runtime. Il achète la garantie à la\ncompilation qu'une implémentation existe — une base sans sous-classe n'est qu'un service,\net le boot ne peut pas vous dire qu'elle se voulait abstraite.",[515,566,568],{"id":567},"une-classe-du-framework-est-un-port-aussi","Une classe du framework est un port aussi",[199,570,571,572,575,576,579,580,583],{},"La condition tient en une ligne : ",[506,573,574],{},"quelque chose répond déjà sous ce nom",". Les providers\nsont enregistrés dans le scope de la Frond, et les builtins de Fougere vivent dans son\nparent — donc ",[203,577,578],{},"Logger"," est un port comme ",[203,581,582],{},"Payment",", et l'étendre reprend la clé :",[211,585,587],{"className":213,"code":586,"language":215,"meta":216,"style":216},"\u002F\u002F services\u002FAuditLogger.ts\nexport default class AuditLogger extends Logger {\n  info(msg: string) { \u002F* l'envoyer ailleurs *\u002F }\n}\n\n\u002F\u002F n'importe quel handler, inchangé\nconstructor(private logger: Logger) {}   \u002F\u002F reçoit AuditLogger\n",[203,588,589,594,612,636,640,644,649],{"__ignoreMap":216},[220,590,591],{"class":222,"line":223},[220,592,593],{"class":226},"\u002F\u002F services\u002FAuditLogger.ts\n",[220,595,596,598,600,602,605,607,610],{"class":222,"line":230},[220,597,234],{"class":233},[220,599,237],{"class":233},[220,601,244],{"class":240},[220,603,604],{"class":247}," AuditLogger",[220,606,322],{"class":240},[220,608,609],{"class":247}," Logger",[220,611,252],{"class":251},[220,613,614,617,619,622,624,627,629,631,634],{"class":222,"line":255},[220,615,616],{"class":261},"  info",[220,618,265],{"class":251},[220,620,621],{"class":268},"msg",[220,623,273],{"class":272},[220,625,626],{"class":276}," string",[220,628,280],{"class":251},[220,630,345],{"class":251},[220,632,633],{"class":226}," \u002F* l'envoyer ailleurs *\u002F",[220,635,380],{"class":251},[220,637,638],{"class":222,"line":291},[220,639,294],{"class":251},[220,641,642],{"class":222,"line":297},[220,643,301],{"emptyLinePlaceholder":300},[220,645,646],{"class":222,"line":304},[220,647,648],{"class":226},"\u002F\u002F n'importe quel handler, inchangé\n",[220,650,651,654,657,660],{"class":222,"line":310},[220,652,653],{"class":475},"constructor",[220,655,656],{"class":373},"(private logger: Logger) ",[220,658,659],{"class":251},"{}",[220,661,662],{"class":226},"   \u002F\u002F reçoit AuditLogger\n",[199,664,665],{},"Le défaut reste en place pour toute Frond qui ne déclare pas de sous-classe — la surcharge\nest par Frond, parce que les services d'une Frond sont les siens.",[199,667,668,669,673],{},"Un logger construit avant un changement de niveau l'applique quand même, et un enfant\naussi : le niveau est consulté plutôt que recopié dans chaque instance, donc rien n'est\nreconstruit quand il bouge. Ça appartient au ",[670,671,672],"a",{"href":175},"cycle de vie",", pas\nici.",[199,675,676,677,680],{},"C'est la même condition qui exclut un prefab : un repository étend la classe rendue par\n",[203,678,679],{},"Repository(Post)",", et rien ne répond jamais sous ce nom.",[515,682,684],{"id":683},"deux-implémentations","Deux implémentations",[199,686,687],{},"Quelle réalisation un déploiement utilise n'est pas un fait du code — ça change entre le\ndev et la production. Donc le boot refuse de deviner :",[211,689,694],{"className":690,"code":692,"language":693},[691],"language-text","[ports] StripePayment and OgonePayment both extend Payment, and nothing says which one\nanswers it. Which realization a deployment uses is not a fact about the code — state it:\nports: { Payment: 'StripePayment' } in fougere.config.ts.\n","text",[203,695,692],{"__ignoreMap":216},[199,697,698],{},"Refuser plutôt que d'en garder une est délibéré : celle qui gagnerait dépendrait de l'ordre\ndu scan, et le handler débiterait le mauvais fournisseur sans un mot.",[211,700,702],{"className":213,"code":701,"language":215,"meta":216,"style":216},"\u002F\u002F fougere.config.ts\nexport default defineFougere({\n  db: 'sqlite',\n  remotes: { blog: 'http:\u002F\u002F127.0.0.1:4100' },\n  ports: { Payment: process.env.PSP === 'ogone' ? 'OgonePayment' : 'StripePayment' },\n});\n",[203,703,704,709,723,740,764,822],{"__ignoreMap":216},[220,705,706],{"class":222,"line":223},[220,707,708],{"class":226},"\u002F\u002F fougere.config.ts\n",[220,710,711,713,715,718,720],{"class":222,"line":230},[220,712,234],{"class":233},[220,714,237],{"class":233},[220,716,717],{"class":475}," defineFougere",[220,719,265],{"class":373},[220,721,722],{"class":251},"{\n",[220,724,725,728,730,732,735,737],{"class":222,"line":255},[220,726,727],{"class":353},"  db",[220,729,273],{"class":251},[220,731,360],{"class":359},[220,733,734],{"class":363},"sqlite",[220,736,367],{"class":359},[220,738,739],{"class":251},",\n",[220,741,742,745,747,749,752,754,756,759,761],{"class":222,"line":291},[220,743,744],{"class":353},"  remotes",[220,746,273],{"class":251},[220,748,345],{"class":251},[220,750,751],{"class":353}," blog",[220,753,273],{"class":251},[220,755,360],{"class":359},[220,757,758],{"class":363},"http:\u002F\u002F127.0.0.1:4100",[220,760,367],{"class":359},[220,762,763],{"class":251}," },\n",[220,765,766,769,771,773,775,777,780,782,785,787,791,794,796,799,801,804,806,809,811,814,816,818,820],{"class":222,"line":297},[220,767,768],{"class":353},"  ports",[220,770,273],{"class":251},[220,772,345],{"class":251},[220,774,248],{"class":353},[220,776,273],{"class":251},[220,778,779],{"class":373}," process",[220,781,467],{"class":251},[220,783,784],{"class":373},"env",[220,786,467],{"class":251},[220,788,790],{"class":789},"s_hVV","PSP",[220,792,793],{"class":272}," ===",[220,795,360],{"class":359},[220,797,798],{"class":363},"ogone",[220,800,367],{"class":359},[220,802,803],{"class":272}," ?",[220,805,360],{"class":359},[220,807,808],{"class":363},"OgonePayment",[220,810,367],{"class":359},[220,812,813],{"class":272}," :",[220,815,360],{"class":359},[220,817,503],{"class":363},[220,819,367],{"class":359},[220,821,763],{"class":251},[220,823,824,827,829],{"class":222,"line":304},[220,825,826],{"class":251},"}",[220,828,280],{"class":373},[220,830,288],{"class":251},[199,832,833,836,837,840,841,504,844,846,847,548,850,852,853,548,856,858,859,862,863,866],{},[203,834,835],{},"ports:"," se pose à côté de ",[203,838,839],{},"remotes:"," pour la même raison que ",[203,842,843],{},"sources:",[203,845,839],{}," dit où\npart un ",[506,848,849],{},"appel",[203,851,843],{}," où vit une ",[506,854,855],{},"ligne",[203,857,835],{}," qui exécute une ",[506,860,861],{},"action",".\nAucun des trois n'appartient à la Frond, qui se décrit elle-même et non son déploiement —\nune Frond ",[203,864,865],{},"billing"," qui nommerait Stripe serait la seule chose que Fougere refuse.",[199,868,869,870,873,874,876,877,880],{},"Supprimez ",[203,871,872],{},"OgonePayment.ts"," et la ligne ",[203,875,835],{}," disparaît avec lui : une seule\nimplémentation est résolue par convention. ",[506,878,879],{},"La clé n'existe que pour l'exception."," Une\nclé qui ne correspond à aucun port est signalée au boot, plutôt qu'obéie en silence.",[515,882,884],{"id":883},"port-ou-frond","Port ou Frond ?",[199,886,887,888,891],{},"Une ",[670,889,890],{"href":42},"Frond"," distante permet aussi de remplacer ce qui répond, et\nelle remplace le process avec. La ligne entre les deux se vérifie :",[893,894,895,906],"ul",{},[896,897,898,899,902,903,905],"li",{},"Le collaborateur ",[506,900,901],{},"possède de la donnée"," — des entités, des tables, des seeds → c'est\nune Frond, et ",[203,904,839],{}," est sa déclaration.",[896,907,908,909,912,913,905],{},"Il ne fait qu'",[506,910,911],{},"agir sur le dehors"," — un PSP, un envoyeur d'e-mails, un stockage de\nfichiers → c'est un port, et ",[203,914,835],{},[199,916,917,918,921,922,925,926,929,930,933,934,936,937,939],{},"Les ports de Fougere eux-mêmes suivent la règle par l'autre bout : ",[203,919,920],{},"Storage"," et\n",[203,923,924],{},"HttpRouter"," sont déclarés par le framework parce qu'il ",[506,927,928],{},"dérive"," des choses de leur forme\n(",[203,931,932],{},"Crud",", le DDL, la carte d'identité). Rien n'est dérivé de ",[203,935,582],{},", donc ",[203,938,582],{}," est à\nvous.",[515,941,943],{"id":942},"ce-que-ça-ne-fait-pas","Ce que ça ne fait pas",[199,945,946],{},"Un port permet d'exprimer un joint. Il ne dit pas où sont les joints — découper un domaine\nen collaborateurs reste votre décision, et rien ici ne la prend à votre place.",[199,948,949,950,467],{},"Suite : ",[670,951,952],{"href":130},"Requêtes et commandes",[954,955,956],"style",{},"html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sVXei, html code.shiki .sVXei{--shiki-light:#E53935;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":216,"searchDepth":230,"depth":230,"links":958},[959,960,961,962,963],{"id":517,"depth":230,"text":518},{"id":567,"depth":230,"text":568},{"id":683,"depth":230,"text":684},{"id":883,"depth":230,"text":884},{"id":942,"depth":230,"text":943},"Comment un handler dépend d'une capacité sans nommer qui l'exécute — une classe, ses implémentations, et la ligne qui n'existe que s'il y en a deux.","md",{},{"title":88,"description":964},"ek8vqxR3nOWJwZFKgj-Oci-Q-6LGwfYr_J8GdE5eD9E",1788598419784]