[{"data":1,"prerenderedAt":8877},["ShallowReactive",2],{"category-craft":3},[4,1212,1554,2953,3403,4294,4516,4938,5526,5673,5897,6362,6834,6984,8592],{"id":5,"title":6,"alt":7,"authors":8,"body":15,"date":1176,"description":1177,"extension":1178,"image":1179,"meta":1180,"navigation":102,"ogImage":1179,"path":1181,"published":102,"reviewers":1182,"seo":1205,"stem":1206,"tags":1207,"__hash__":1211},"blogs\u002Fblogs\u002F2024-09-27-sauvegarder-ltat-final-dun-agrgat-par-ses-vnements\u002Findex.md","Sauvegarder l’état final d’un agrégat par ses événements","Des aggregats imaginés sous la forme de cellules organiques",[9],{"id":10,"name":11,"image":12,"linkedin":13,"x":14},"02c620f8-3576-4943-b5cf-6117f99220a2","Edouard Cattez",".\u002Fassets\u002Fauthor-edouard-cattez.webp","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fedouard-cattez-865794133\u002F","https:\u002F\u002Fx.com\u002Fecattez",{"type":16,"value":17,"toc":1169},"minimark",[18,22,32,35,38,43,46,57,467,470,478,484,488,491,505,510,513,518,581,586,589,592,975,992,1155,1158,1162,1165],[19,20,21],"p",{},"Pré-requis pour ne pas être trop perdu",[23,24,25,29],"ul",{},[26,27,28],"li",{},"quelques bases en Domain Driven Design (DDD)",[26,30,31],{},"quelques bases en JPA \u002F Hibernate",[19,33,34],{},"La notion d’agrégat du Domain Driven Design désigne un ensemble cohérent d’objets du domaine qui doit être manipulé comme une seule unité métier. Dans cette logique, les décisions métiers sont prises par l'agrégat et prennent la forme d'événements (Domain Event). L'événement est donc la représentation d'une mutation: l'agrégat a changé d'état.",[19,36,37],{},"Je vous propose ainsi de voir comme l'on peut persister l'état final d'un agrégat en ne se basant que sur les décisions qu'il a prises.",[39,40,42],"h2",{"id":41},"lagrégat","L'agrégat",[19,44,45],{},"L'exemple que nous allons prendre parle de l'accès d'un utilisateur dont les règles de gestion sont les suivantes:",[23,47,48,51,54],{},[26,49,50],{},"l'accès peut être suspendu",[26,52,53],{},"si l'accès est suspendu, il ne peut pas être suspendu à nouveau (erreur)",[26,55,56],{},"l'accès peut être réactivé",[58,59,64],"pre",{"className":60,"code":61,"language":62,"meta":63,"style":63},"language-java shiki shiki-themes github-dark-default","@Aggregate \u002F\u002F Annotation personnalisée à des fins de documentation\nclass UserAccess {\n\n    \u002F\u002F Le mot clé \"transient\" est faculatif, il me sert surtout à indiquer que les événements ne seront pas persistés en tant que tel\n    private final transient List\u003CUserAccessEvent> occurredEvents = new ArrayList\u003C>();\n\n    private final UserId id;\n    private Instant suspendedAt;\n\n    public List\u003CUserEvent> occurredEvents() {\n        return List.copyOf(occurredEvents);\n    }\n\n    public void suspend(Clock now) {\n        if (suspendedAt != null) {\n            throw new UserAccessIsAlreadySuspended(id);\n        }\n\n        UserAccessSuspended event = UserAccessSuspended.builder()\n                .userId(id)\n                .suspendedAt(now.instant())\n                .build();\n\n        \u002F\u002F La décision prise par l'aggrégat est stockée\n        this.occurredEvents.add(event);\n\n        \u002F\u002F L'état interne de l'aggrégat est muté\n        this.apply(event);\n    }\n\n    private void apply(UserAccessSuspended event) {\n        this.suspendedAt = event.suspendedAt();\n    }\n\n    \u002F\u002F ... d'autres règles ici ...\n}\n","java","",[65,66,67,84,97,104,110,146,151,164,175,180,200,215,221,226,246,264,278,284,289,309,321,338,349,354,360,375,380,386,399,404,409,427,445,450,455,461],"code",{"__ignoreMap":63},[68,69,72,76,80],"span",{"class":70,"line":71},"line",1,[68,73,75],{"class":74},"sZEs4","@",[68,77,79],{"class":78},"suJrU","Aggregate",[68,81,83],{"class":82},"sH3jZ"," \u002F\u002F Annotation personnalisée à des fins de documentation\n",[68,85,87,90,94],{"class":70,"line":86},2,[68,88,89],{"class":78},"class",[68,91,93],{"class":92},"sQhOw"," UserAccess",[68,95,96],{"class":74}," {\n",[68,98,100],{"class":70,"line":99},3,[68,101,103],{"emptyLinePlaceholder":102},true,"\n",[68,105,107],{"class":70,"line":106},4,[68,108,109],{"class":82},"    \u002F\u002F Le mot clé \"transient\" est faculatif, il me sert surtout à indiquer que les événements ne seront pas persistés en tant que tel\n",[68,111,113,116,119,122,125,128,131,134,137,140,143],{"class":70,"line":112},5,[68,114,115],{"class":78},"    private",[68,117,118],{"class":78}," final",[68,120,121],{"class":78}," transient",[68,123,124],{"class":74}," List",[68,126,127],{"class":92},"\u003C",[68,129,130],{"class":78},"UserAccessEvent",[68,132,133],{"class":92},"> ",[68,135,136],{"class":74},"occurredEvents",[68,138,139],{"class":78}," =",[68,141,142],{"class":78}," new",[68,144,145],{"class":74}," ArrayList\u003C>();\n",[68,147,149],{"class":70,"line":148},6,[68,150,103],{"emptyLinePlaceholder":102},[68,152,154,156,158,161],{"class":70,"line":153},7,[68,155,115],{"class":78},[68,157,118],{"class":78},[68,159,160],{"class":74}," UserId",[68,162,163],{"class":74}," id;\n",[68,165,167,169,172],{"class":70,"line":166},8,[68,168,115],{"class":78},[68,170,171],{"class":74}," Instant",[68,173,174],{"class":74}," suspendedAt;\n",[68,176,178],{"class":70,"line":177},9,[68,179,103],{"emptyLinePlaceholder":102},[68,181,183,186,189,192,194,197],{"class":70,"line":182},10,[68,184,185],{"class":78},"    public",[68,187,188],{"class":74}," List\u003C",[68,190,191],{"class":78},"UserEvent",[68,193,133],{"class":74},[68,195,136],{"class":196},"sc3cj",[68,198,199],{"class":74},"() {\n",[68,201,203,206,209,212],{"class":70,"line":202},11,[68,204,205],{"class":78},"        return",[68,207,208],{"class":74}," List.",[68,210,211],{"class":196},"copyOf",[68,213,214],{"class":74},"(occurredEvents);\n",[68,216,218],{"class":70,"line":217},12,[68,219,220],{"class":74},"    }\n",[68,222,224],{"class":70,"line":223},13,[68,225,103],{"emptyLinePlaceholder":102},[68,227,229,231,234,237,240,243],{"class":70,"line":228},14,[68,230,185],{"class":78},[68,232,233],{"class":78}," void",[68,235,236],{"class":196}," suspend",[68,238,239],{"class":74},"(Clock ",[68,241,242],{"class":92},"now",[68,244,245],{"class":74},") {\n",[68,247,249,252,255,258,262],{"class":70,"line":248},15,[68,250,251],{"class":78},"        if",[68,253,254],{"class":74}," (suspendedAt ",[68,256,257],{"class":78},"!=",[68,259,261],{"class":260},"sFSAA"," null",[68,263,245],{"class":74},[68,265,267,270,272,275],{"class":70,"line":266},16,[68,268,269],{"class":78},"            throw",[68,271,142],{"class":78},[68,273,274],{"class":196}," UserAccessIsAlreadySuspended",[68,276,277],{"class":74},"(id);\n",[68,279,281],{"class":70,"line":280},17,[68,282,283],{"class":74},"        }\n",[68,285,287],{"class":70,"line":286},18,[68,288,103],{"emptyLinePlaceholder":102},[68,290,292,295,298,300,303,306],{"class":70,"line":291},19,[68,293,294],{"class":74},"        UserAccessSuspended",[68,296,297],{"class":74}," event",[68,299,139],{"class":78},[68,301,302],{"class":74}," UserAccessSuspended.",[68,304,305],{"class":196},"builder",[68,307,308],{"class":74},"()\n",[68,310,312,315,318],{"class":70,"line":311},20,[68,313,314],{"class":74},"                .",[68,316,317],{"class":196},"userId",[68,319,320],{"class":74},"(id)\n",[68,322,324,326,329,332,335],{"class":70,"line":323},21,[68,325,314],{"class":74},[68,327,328],{"class":196},"suspendedAt",[68,330,331],{"class":74},"(now.",[68,333,334],{"class":196},"instant",[68,336,337],{"class":74},"())\n",[68,339,341,343,346],{"class":70,"line":340},22,[68,342,314],{"class":74},[68,344,345],{"class":196},"build",[68,347,348],{"class":74},"();\n",[68,350,352],{"class":70,"line":351},23,[68,353,103],{"emptyLinePlaceholder":102},[68,355,357],{"class":70,"line":356},24,[68,358,359],{"class":82},"        \u002F\u002F La décision prise par l'aggrégat est stockée\n",[68,361,363,366,369,372],{"class":70,"line":362},25,[68,364,365],{"class":260},"        this",[68,367,368],{"class":74},".occurredEvents.",[68,370,371],{"class":196},"add",[68,373,374],{"class":74},"(event);\n",[68,376,378],{"class":70,"line":377},26,[68,379,103],{"emptyLinePlaceholder":102},[68,381,383],{"class":70,"line":382},27,[68,384,385],{"class":82},"        \u002F\u002F L'état interne de l'aggrégat est muté\n",[68,387,389,391,394,397],{"class":70,"line":388},28,[68,390,365],{"class":260},[68,392,393],{"class":74},".",[68,395,396],{"class":196},"apply",[68,398,374],{"class":74},[68,400,402],{"class":70,"line":401},29,[68,403,220],{"class":74},[68,405,407],{"class":70,"line":406},30,[68,408,103],{"emptyLinePlaceholder":102},[68,410,412,414,416,419,422,425],{"class":70,"line":411},31,[68,413,115],{"class":78},[68,415,233],{"class":78},[68,417,418],{"class":196}," apply",[68,420,421],{"class":74},"(UserAccessSuspended ",[68,423,424],{"class":92},"event",[68,426,245],{"class":74},[68,428,430,432,435,438,441,443],{"class":70,"line":429},32,[68,431,365],{"class":260},[68,433,434],{"class":74},".suspendedAt ",[68,436,437],{"class":78},"=",[68,439,440],{"class":74}," event.",[68,442,328],{"class":196},[68,444,348],{"class":74},[68,446,448],{"class":70,"line":447},33,[68,449,220],{"class":74},[68,451,453],{"class":70,"line":452},34,[68,454,103],{"emptyLinePlaceholder":102},[68,456,458],{"class":70,"line":457},35,[68,459,460],{"class":82},"    \u002F\u002F ... d'autres règles ici ...\n",[68,462,464],{"class":70,"line":463},36,[68,465,466],{"class":74},"}\n",[19,468,469],{},"La décision métier est représentée ici de deux manières:",[23,471,472,475],{},[26,473,474],{},"l'exception UserAccessIsAlreadySuspended",[26,476,477],{},"l'événement UserAccessSuspended",[479,480,481],"blockquote",{},[19,482,483],{},"Bon à savoir, un événement s'étant déjà produit, une bonne manière de le nommer est de l'écrire au passé. L'exception quant à elle est écrite avec la convention IsXX pour décrire un fait.",[39,485,487],{"id":486},"persister-lagrégat","Persister l'agrégat",[19,489,490],{},"L'agrégat sera manipulé dans la couche application par un service qui couvrira la totalité du geste métier:",[23,492,493,496,499,502],{},[26,494,495],{},"Récupération de l'agrégat",[26,497,498],{},"Prise de décision",[26,500,501],{},"Gestion des erreurs",[26,503,504],{},"Persistance de l'agrégat",[479,506,507],{},[19,508,509],{},"Dans la clean architecture, ce service prend souvent le nom de Use case.",[19,511,512],{},"L'agrégat sera récupéré et persisté au travers d'une interface qui va abstraire toute la logique d'infrastructure.",[479,514,515],{},[19,516,517],{},"Dans l'architecture hexagonale, cette interface porte le nom de port.",[58,519,521],{"className":60,"code":520,"language":62,"meta":63,"style":63},"interface UserAccessPort {\n\n    UserAccess getById(UserId userId);\n\n    void save(UserAccess access);\n\n}\n",[65,522,523,533,537,553,557,573,577],{"__ignoreMap":63},[68,524,525,528,531],{"class":70,"line":71},[68,526,527],{"class":78},"interface",[68,529,530],{"class":92}," UserAccessPort",[68,532,96],{"class":74},[68,534,535],{"class":70,"line":86},[68,536,103],{"emptyLinePlaceholder":102},[68,538,539,542,545,548,550],{"class":70,"line":99},[68,540,541],{"class":74},"    UserAccess ",[68,543,544],{"class":196},"getById",[68,546,547],{"class":74},"(UserId ",[68,549,317],{"class":92},[68,551,552],{"class":74},");\n",[68,554,555],{"class":70,"line":106},[68,556,103],{"emptyLinePlaceholder":102},[68,558,559,562,565,568,571],{"class":70,"line":112},[68,560,561],{"class":78},"    void",[68,563,564],{"class":196}," save",[68,566,567],{"class":74},"(UserAccess ",[68,569,570],{"class":92},"access",[68,572,552],{"class":74},[68,574,575],{"class":70,"line":148},[68,576,103],{"emptyLinePlaceholder":102},[68,578,579],{"class":70,"line":153},[68,580,466],{"class":74},[582,583,585],"h3",{"id":584},"implémentation","Implémentation",[19,587,588],{},"Nous pourrions exposer un getter pour la propriété suspendedAt. Le problème s'il en est de cette méthode réside dans le fait que l'agrégat se mettrait à exposer tout ou partie de son état interne. D'une certaine manière, la modélisation de notre agrégat serait dépendante de la manière de la consommer. C'est là où les événements jouent un rôle important: ils sont faits pour être consommés.",[19,590,591],{},"Pour sauvegarder l'agrégat, nous allons parcourir chaque événement qu'il aura pu créer. Pour chaque événement, nous allons effectuer une opération JPA. A contrario, récupérer l'agrégat depuis la persistance se fera par un mapping direct des propriétés.",[58,593,595],{"className":60,"code":594,"language":62,"meta":63,"style":63},"class UserAccessAdapter implements UserAccessPort {\n\n    private final JpaUserAccesses jpaUserAccesses;\n\n    @Override\n    public UserAccess getById(UserId userId) {\n        return jpaUserAccesses.findById(userId)\n                .map(UserAccessAdapter::toUserAccess)\n                .orElseThrow(() -> new UserAccessNotFound(userId));\n    }\n\n    private static UserAccess toUserAccess(UserAccessEntity entity) {\n        \u002F\u002F Ici, on recrée l'agrégat à partir d'un pattern builder\n        \u002F\u002F Demain, nous pourrions peut être le récréer à partir des événements\n        return UserAccess.builder()\n                .id(UserId.from(entity.getId()))\n                .suspendedAt(entity.getSuspendedAt())\n                .build();\n    }\n\n    @Override\n    public void save(User user) {\n        user.occurredEvents().forEach(event -> {\n            switch(event) {\n                case UserAccessSuspended e -> apply(e);\n                case UserAccessUnlocked e -> apply(e);\n                \u002F\u002F ... other events ...\n            }\n        });\n    }\n\n    private void apply(UserAccessSuspended event) {\n        jpaUserAccesses.apply(event);\n    }\n\n    private void apply(UserAccessUnlocked event) {\n        jpaUserAccesses.apply(event);\n    }\n\n}\n",[65,596,597,611,615,627,631,639,654,667,683,704,708,712,732,737,742,753,775,788,796,800,804,810,826,846,854,869,882,887,892,897,901,905,919,928,932,936,951,960,965,970],{"__ignoreMap":63},[68,598,599,601,604,607,609],{"class":70,"line":71},[68,600,89],{"class":78},[68,602,603],{"class":92}," UserAccessAdapter",[68,605,606],{"class":78}," implements",[68,608,530],{"class":260},[68,610,96],{"class":74},[68,612,613],{"class":70,"line":86},[68,614,103],{"emptyLinePlaceholder":102},[68,616,617,619,621,624],{"class":70,"line":99},[68,618,115],{"class":78},[68,620,118],{"class":78},[68,622,623],{"class":74}," JpaUserAccesses",[68,625,626],{"class":74}," jpaUserAccesses;\n",[68,628,629],{"class":70,"line":106},[68,630,103],{"emptyLinePlaceholder":102},[68,632,633,636],{"class":70,"line":112},[68,634,635],{"class":74},"    @",[68,637,638],{"class":78},"Override\n",[68,640,641,643,646,648,650,652],{"class":70,"line":148},[68,642,185],{"class":78},[68,644,645],{"class":74}," UserAccess ",[68,647,544],{"class":196},[68,649,547],{"class":74},[68,651,317],{"class":92},[68,653,245],{"class":74},[68,655,656,658,661,664],{"class":70,"line":153},[68,657,205],{"class":78},[68,659,660],{"class":74}," jpaUserAccesses.",[68,662,663],{"class":196},"findById",[68,665,666],{"class":74},"(userId)\n",[68,668,669,671,674,677,680],{"class":70,"line":166},[68,670,314],{"class":74},[68,672,673],{"class":196},"map",[68,675,676],{"class":74},"(UserAccessAdapter",[68,678,679],{"class":78},"::",[68,681,682],{"class":74},"toUserAccess)\n",[68,684,685,687,690,693,696,698,701],{"class":70,"line":177},[68,686,314],{"class":74},[68,688,689],{"class":196},"orElseThrow",[68,691,692],{"class":74},"(() ",[68,694,695],{"class":78},"->",[68,697,142],{"class":78},[68,699,700],{"class":196}," UserAccessNotFound",[68,702,703],{"class":74},"(userId));\n",[68,705,706],{"class":70,"line":182},[68,707,220],{"class":74},[68,709,710],{"class":70,"line":202},[68,711,103],{"emptyLinePlaceholder":102},[68,713,714,716,719,721,724,727,730],{"class":70,"line":217},[68,715,115],{"class":78},[68,717,718],{"class":78}," static",[68,720,645],{"class":74},[68,722,723],{"class":196},"toUserAccess",[68,725,726],{"class":74},"(UserAccessEntity ",[68,728,729],{"class":92},"entity",[68,731,245],{"class":74},[68,733,734],{"class":70,"line":223},[68,735,736],{"class":82},"        \u002F\u002F Ici, on recrée l'agrégat à partir d'un pattern builder\n",[68,738,739],{"class":70,"line":228},[68,740,741],{"class":82},"        \u002F\u002F Demain, nous pourrions peut être le récréer à partir des événements\n",[68,743,744,746,749,751],{"class":70,"line":248},[68,745,205],{"class":78},[68,747,748],{"class":74}," UserAccess.",[68,750,305],{"class":196},[68,752,308],{"class":74},[68,754,755,757,760,763,766,769,772],{"class":70,"line":266},[68,756,314],{"class":74},[68,758,759],{"class":196},"id",[68,761,762],{"class":74},"(UserId.",[68,764,765],{"class":196},"from",[68,767,768],{"class":74},"(entity.",[68,770,771],{"class":196},"getId",[68,773,774],{"class":74},"()))\n",[68,776,777,779,781,783,786],{"class":70,"line":280},[68,778,314],{"class":74},[68,780,328],{"class":196},[68,782,768],{"class":74},[68,784,785],{"class":196},"getSuspendedAt",[68,787,337],{"class":74},[68,789,790,792,794],{"class":70,"line":286},[68,791,314],{"class":74},[68,793,345],{"class":196},[68,795,348],{"class":74},[68,797,798],{"class":70,"line":291},[68,799,220],{"class":74},[68,801,802],{"class":70,"line":311},[68,803,103],{"emptyLinePlaceholder":102},[68,805,806,808],{"class":70,"line":323},[68,807,635],{"class":74},[68,809,638],{"class":78},[68,811,812,814,816,818,821,824],{"class":70,"line":340},[68,813,185],{"class":78},[68,815,233],{"class":78},[68,817,564],{"class":196},[68,819,820],{"class":74},"(User ",[68,822,823],{"class":92},"user",[68,825,245],{"class":74},[68,827,828,831,833,836,839,842,844],{"class":70,"line":351},[68,829,830],{"class":74},"        user.",[68,832,136],{"class":196},[68,834,835],{"class":74},"().",[68,837,838],{"class":196},"forEach",[68,840,841],{"class":74},"(event ",[68,843,695],{"class":78},[68,845,96],{"class":74},[68,847,848,851],{"class":70,"line":356},[68,849,850],{"class":78},"            switch",[68,852,853],{"class":74},"(event) {\n",[68,855,856,859,862,864,866],{"class":70,"line":362},[68,857,858],{"class":78},"                case",[68,860,861],{"class":74}," UserAccessSuspended e ",[68,863,695],{"class":78},[68,865,418],{"class":196},[68,867,868],{"class":74},"(e);\n",[68,870,871,873,876,878,880],{"class":70,"line":377},[68,872,858],{"class":78},[68,874,875],{"class":74}," UserAccessUnlocked e ",[68,877,695],{"class":78},[68,879,418],{"class":196},[68,881,868],{"class":74},[68,883,884],{"class":70,"line":382},[68,885,886],{"class":82},"                \u002F\u002F ... other events ...\n",[68,888,889],{"class":70,"line":388},[68,890,891],{"class":74},"            }\n",[68,893,894],{"class":70,"line":401},[68,895,896],{"class":74},"        });\n",[68,898,899],{"class":70,"line":406},[68,900,220],{"class":74},[68,902,903],{"class":70,"line":411},[68,904,103],{"emptyLinePlaceholder":102},[68,906,907,909,911,913,915,917],{"class":70,"line":429},[68,908,115],{"class":78},[68,910,233],{"class":78},[68,912,418],{"class":196},[68,914,421],{"class":74},[68,916,424],{"class":92},[68,918,245],{"class":74},[68,920,921,924,926],{"class":70,"line":447},[68,922,923],{"class":74},"        jpaUserAccesses.",[68,925,396],{"class":196},[68,927,374],{"class":74},[68,929,930],{"class":70,"line":452},[68,931,220],{"class":74},[68,933,934],{"class":70,"line":457},[68,935,103],{"emptyLinePlaceholder":102},[68,937,938,940,942,944,947,949],{"class":70,"line":463},[68,939,115],{"class":78},[68,941,233],{"class":78},[68,943,418],{"class":196},[68,945,946],{"class":74},"(UserAccessUnlocked ",[68,948,424],{"class":92},[68,950,245],{"class":74},[68,952,954,956,958],{"class":70,"line":953},37,[68,955,923],{"class":74},[68,957,396],{"class":196},[68,959,374],{"class":74},[68,961,963],{"class":70,"line":962},38,[68,964,220],{"class":74},[68,966,968],{"class":70,"line":967},39,[68,969,103],{"emptyLinePlaceholder":102},[68,971,973],{"class":70,"line":972},40,[68,974,466],{"class":74},[19,976,977,978,985,986,991],{},"L’association de ",[979,980,984],"a",{"href":981,"rel":982},"https:\u002F\u002Fdocs.jboss.org\u002Fhibernate\u002Form\u002F3.5\u002Freference\u002Ffr\u002Fhtml\u002Fqueryhql.html",[983],"nofollow","HQL"," et de ",[979,987,990],{"href":988,"rel":989},"https:\u002F\u002Fdocs.spring.io\u002Fspring-framework\u002Freference\u002Fcore\u002Fexpressions.html",[983],"SPEL"," nous permet ainsi d'écrire nos requêtes SQL à partir de nos événements.",[58,993,995],{"className":60,"code":994,"language":62,"meta":63,"style":63},"@Repository\ninterface JpaUserAccesses extends JpaRepository\u003CUserAccessEntity, String> {\n\n    @Modifying\n    @Query(\"\"\"\n        UPDATE UserAccess u\n        SET u.suspendedAt = :#{#event.suspendedAt()}\n        WHERE u.id = :#{#event.userId().value()}\n    \"\"\")\n    void apply(UserAccessSuspended event);\n\n    @Modifying\n    @Query(\"\"\"\n        UPDATE UserAccess u\n        SET u.suspendedAt = null\n        WHERE u.id = :#{#event.userId().value()}\n        \"\"\")\n    void apply(UserAccessUnlocked event);\n\n    \u002F\u002F ... apply other events ...\n}\n",[65,996,997,1004,1030,1034,1041,1055,1060,1065,1070,1078,1090,1094,1100,1110,1114,1119,1123,1130,1142,1146,1151],{"__ignoreMap":63},[68,998,999,1001],{"class":70,"line":71},[68,1000,75],{"class":74},[68,1002,1003],{"class":78},"Repository\n",[68,1005,1006,1008,1010,1013,1016,1018,1021,1024,1027],{"class":70,"line":86},[68,1007,527],{"class":78},[68,1009,623],{"class":92},[68,1011,1012],{"class":78}," extends",[68,1014,1015],{"class":260}," JpaRepository",[68,1017,127],{"class":74},[68,1019,1020],{"class":78},"UserAccessEntity",[68,1022,1023],{"class":74},", ",[68,1025,1026],{"class":78},"String",[68,1028,1029],{"class":74},"> {\n",[68,1031,1032],{"class":70,"line":99},[68,1033,103],{"emptyLinePlaceholder":102},[68,1035,1036,1038],{"class":70,"line":106},[68,1037,635],{"class":74},[68,1039,1040],{"class":78},"Modifying\n",[68,1042,1043,1045,1048,1051],{"class":70,"line":112},[68,1044,635],{"class":74},[68,1046,1047],{"class":78},"Query",[68,1049,1050],{"class":74},"(",[68,1052,1054],{"class":1053},"s9uIt","\"\"\"\n",[68,1056,1057],{"class":70,"line":148},[68,1058,1059],{"class":1053},"        UPDATE UserAccess u\n",[68,1061,1062],{"class":70,"line":153},[68,1063,1064],{"class":1053},"        SET u.suspendedAt = :#{#event.suspendedAt()}\n",[68,1066,1067],{"class":70,"line":166},[68,1068,1069],{"class":1053},"        WHERE u.id = :#{#event.userId().value()}\n",[68,1071,1072,1075],{"class":70,"line":177},[68,1073,1074],{"class":1053},"    \"\"\"",[68,1076,1077],{"class":74},")\n",[68,1079,1080,1082,1084,1086,1088],{"class":70,"line":182},[68,1081,561],{"class":78},[68,1083,418],{"class":196},[68,1085,421],{"class":74},[68,1087,424],{"class":92},[68,1089,552],{"class":74},[68,1091,1092],{"class":70,"line":202},[68,1093,103],{"emptyLinePlaceholder":102},[68,1095,1096,1098],{"class":70,"line":217},[68,1097,635],{"class":74},[68,1099,1040],{"class":78},[68,1101,1102,1104,1106,1108],{"class":70,"line":223},[68,1103,635],{"class":74},[68,1105,1047],{"class":78},[68,1107,1050],{"class":74},[68,1109,1054],{"class":1053},[68,1111,1112],{"class":70,"line":228},[68,1113,1059],{"class":1053},[68,1115,1116],{"class":70,"line":248},[68,1117,1118],{"class":1053},"        SET u.suspendedAt = null\n",[68,1120,1121],{"class":70,"line":266},[68,1122,1069],{"class":1053},[68,1124,1125,1128],{"class":70,"line":280},[68,1126,1127],{"class":1053},"        \"\"\"",[68,1129,1077],{"class":74},[68,1131,1132,1134,1136,1138,1140],{"class":70,"line":286},[68,1133,561],{"class":78},[68,1135,418],{"class":196},[68,1137,946],{"class":74},[68,1139,424],{"class":92},[68,1141,552],{"class":74},[68,1143,1144],{"class":70,"line":291},[68,1145,103],{"emptyLinePlaceholder":102},[68,1147,1148],{"class":70,"line":311},[68,1149,1150],{"class":82},"    \u002F\u002F ... apply other events ...\n",[68,1152,1153],{"class":70,"line":323},[68,1154,466],{"class":74},[19,1156,1157],{},"Notez qu’il est bien sûr possible d’appliquer les événements en utilisant pleinement l’entity manager de Hibernate plutôt que d’exécuter des UPDATE directement sur la base.",[39,1159,1161],{"id":1160},"conclusion","Conclusion",[19,1163,1164],{},"Il n'est pas très compliqué de modéliser les décisions métiers sous forme d'événements. Au travers d'une histoire, nous sommes capables de sauvegarder un état de fait.\nSerions-nous maintenant capables de reconstruire l'agrégat à partir de ses décisions ? L'event sourcing attendra une prochaine fois.",[1166,1167,1168],"style",{},"html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}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 pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}",{"title":63,"searchDepth":86,"depth":86,"links":1170},[1171,1172,1175],{"id":41,"depth":86,"text":42},{"id":486,"depth":86,"text":487,"children":1173},[1174],{"id":584,"depth":99,"text":585},{"id":1160,"depth":86,"text":1161},"2024-09-27T09:33:34.264Z","Pré-requis pour ne pas être trop perdu  - quelques bases en Domain Driven Design (DDD) - quelques bases en JPA \u002F Hibernate   La notion d’agrégat du Domain Driven Design désigne un ensemble cohérent d’","md",".\u002Fassets\u002Fcover-image.webp",{},"\u002Fblogs\u002F2024-09-27-sauvegarder-ltat-final-dun-agrgat-par-ses-vnements",[1183,1189,1194,1200],{"id":1184,"name":1185,"image":1186,"linkedin":1187,"x":1188},"0bb914a6-f882-4951-bee6-53e8e8abb807","Emmanuelle Gouvart","https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc88f5dfa-16db-4e6f-acf1-34dd80ee8766\u002Femma_hoppr.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45HZZMZUHI%2F20240927%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240927T093334Z&X-Amz-Expires=3600&X-Amz-Signature=79fc8167b197f482c4bd87d3d40b54e1c53d1b4fe8c478ea78dbaf9250fdb750&X-Amz-SignedHeaders=host&x-id=GetObject","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Femmanuellegouvart-182b6ab2\u002F",null,{"id":1190,"name":1191,"image":1192,"linkedin":1193,"x":1188},"67adfd77-4b84-4496-b55d-3391541f59c5","Michaël Bernasinski","https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F82ebd0fe-de28-43f3-ab7b-0431af41baad\u002FPhoto_HoppR.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45HZZMZUHI%2F20240927%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240927T093333Z&X-Amz-Expires=3600&X-Amz-Signature=7a4418996d6bb7aac5876de858fe01b816d0b62f1f1c7b761f697d218080dac3&X-Amz-SignedHeaders=host&x-id=GetObject","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmichael-bernasinski",{"id":1195,"name":1196,"image":1197,"linkedin":1198,"x":1199},"838dec96-f9fc-404f-a302-07719225d785","Maxime Deroullers","https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc69d0b59-558d-4e48-879f-bea3fec1fdef\u002FLinkedin_Profile.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45HZZMZUHI%2F20240927%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240927T093333Z&X-Amz-Expires=3600&X-Amz-Signature=824e4bf8f3ec04e004c2c1869033ea8cb59a4b4592fa9a25af8b59caa28bdbbd&X-Amz-SignedHeaders=host&x-id=GetObject","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmaxime-deroullers-1b5791137\u002F","https:\u002F\u002Fx.com\u002Fmderoullers",{"id":1201,"name":1202,"image":1203,"linkedin":1204,"x":1188},"45c76823-ab7d-4c1f-84b3-0bad16ab91e1","Paul-Alexandre Chrétien","https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc4f79dcc-a6ed-4a79-9947-416b33e5b90a\u002FPhoto_Profil_CV_1200px_%2813%29.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45HZZMZUHI%2F20240927%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240927T093333Z&X-Amz-Expires=3600&X-Amz-Signature=03af5b1a4f1831ca5b0e776cfe5fe96613027c1589f760f3089fa5ac4db41e79&X-Amz-SignedHeaders=host&x-id=GetObject","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fpaulalexandrechretien\u002F",{"title":6,"description":1177},"blogs\u002F2024-09-27-sauvegarder-ltat-final-dun-agrgat-par-ses-vnements\u002Findex",[1208,1209,1210],"craft","ddd","jpa","SJfbGcyVyeYxrrRAKoiMst-Ec0DGTOkPu-7NJSgTF6Q",{"id":1213,"title":1214,"alt":1215,"authors":1216,"body":1222,"date":1532,"description":1533,"extension":1178,"image":1179,"meta":1534,"navigation":102,"ogImage":1179,"path":1535,"published":102,"reviewers":1536,"seo":1546,"stem":1547,"tags":1548,"__hash__":1553},"blogs\u002Fblogs\u002F2024-10-31-lobservabilit-un-pilier-essentiel-dans-ladoption-du-cloud-et-des-architectures-modernes\u002Findex.md","L'Observabilité : Un Pilier Essentiel dans l’adoption du Cloud et des architectures modernes","Observabilité et stratégie",[1217],{"id":1218,"name":1219,"image":1220,"linkedin":1221,"x":1188},"4f37105b-9f64-4dcc-8a19-a1f4d5489826","Samuel Bally",".\u002Fassets\u002Fauthor-samuel-bally.webp","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsamuel-bally\u002F",{"type":16,"value":1223,"toc":1519},[1224,1227,1244,1247,1250,1254,1257,1283,1286,1295,1298,1302,1305,1339,1343,1346,1349,1352,1355,1359,1368,1371,1378,1382,1385,1414,1418,1421,1424,1427,1447,1453,1456,1460,1463,1467,1470,1473,1477,1480,1494,1498,1501,1509,1513,1516],[19,1225,1226],{},"Avec l'essor des technologies, notamment de l’IA, les grands groupes comme les start-up se tournent vers des plateformes modernes, parfois complexes (microservices, kubernetes, NoSQL, kafka, …), ainsi que vers des environnements Clouds tels que GCP, AWS ou Azure . Cette utilisation n’est pas toujours encapsulée dans un écosystème optimal dont voici les principaux organes:",[23,1228,1229,1232,1235,1238,1241],{},[26,1230,1231],{},"Le DevOps\u002FSRE",[26,1233,1234],{},"L’Agilité",[26,1236,1237],{},"Les bonnes pratiques d’architecture",[26,1239,1240],{},"Les bonnes pratiques de développement (Software Craftsmanship)",[26,1242,1243],{},"L’Observabilité",[19,1245,1246],{},"Souvent traité dans un second temps, l’Observabilité est pourtant essentielle à la réussite et au suivi de votre projet. Comment pouvons-nous améliorer notre application si nous ne sommes pas capables de mesurer ? L’observation des applications dans leur ensemble permet de valider que vous vous dirigez dans la bonne direction (ou la mauvaise).",[19,1248,1249],{},"Dans cet article, nous allons explorer comment l'Observabilité est incontournable pour maitriser votre transformation et comment Datadog a su proposer une des solutions d’Observabilité les plus avancées du marché.",[39,1251,1253],{"id":1252},"comprendre-lobservabilité-et-ses-enjeux","Comprendre l’Observabilité et ses Enjeux",[19,1255,1256],{},"L'Observabilité est un concept qui découle du monitoring (infrastructures, applications). Elle est d’ailleurs souvent confondue avec la supervision. Cependant, l’Observabilité est plus complexe et apporte un grand nombre de nouveaux concepts:",[23,1258,1259,1262,1265,1268,1271,1274,1277,1280],{},[26,1260,1261],{},"Accessible à tous (métier et technique)",[26,1263,1264],{},"Corrélation des données",[26,1266,1267],{},"Propose une vue d’ensemble (expérience utilisateur, sécurité, application, Cloud, ...)",[26,1269,1270],{},"Accepte plus de type de données comme le coûts d’utilisation des services Cloud",[26,1272,1273],{},"Intègre une gestion des incidents",[26,1275,1276],{},"Propose des services d’aide à la décision basé sur l‘IA",[26,1278,1279],{},"L’approche proactive (détection avant l’incident)",[26,1281,1282],{},"vision holistique de vos produits (applications)",[19,1284,1285],{},"En termes plus simples, c'est la capacité à comprendre ce qui se passe dans un système complexe en observant ses métriques, ses logs, ses traces et toutes autres données pertinentes. Dans le principe, plus vous ingérez de types de données, plus l’observation sera efficace. L’origine d’un événement (incident, changement de performance, volumétrie, …) est instantanément compréhensible et nous le voyons avec toutes ses adhérences. Par exemple, une perte de qualité sur l’application mobile peut-être identifiée par une trace (ligne de code), une métrique Cloud, à un journal et à un autre évènement tel qu’une mise en production.",[19,1287,1288,1289,1294],{},"Cette évolution de la supervision, permet également des démarches FinOps et ",[979,1290,1293],{"href":1291,"rel":1292},"https:\u002F\u002Fblog.hoppr.tech\u002Fblogs\u002F2024-09-26-low-carbon-dans-le-cloud-partie-1",[983],"GreenOps",", très utiles avec les services Cloud.",[19,1296,1297],{},"En raison de la nature distribuée des architectures Cloud modernes, il devient essentiel d’avoir une visibilité complète et en temps réel sur les applications pour détecter les problèmes potentiels avant qu'ils n'affectent les utilisateurs.",[39,1299,1301],{"id":1300},"pourquoi-lobservabilité-est-cruciale","Pourquoi l'Observabilité est cruciale",[19,1303,1304],{},"Lors de l’adoption ou la migration vers le Cloud Public, plusieurs éléments doivent être surveillés de près :",[1306,1307,1308,1315,1321,1327,1333],"ol",{},[26,1309,1310,1314],{},[1311,1312,1313],"strong",{},"Performance des Applications :","  Plus votre application sera performante, plus vous serez en capacité d’optimiser votre utilisation du Cloud, donc votre facture. De plus, la latence, le temps de réponse des applications peuvent fluctuer au cours de la migration. Il est important d’avoir une vue très détaillée (à la ligne de code) des performances pendant la phase d’adoption ou de migration.",[26,1316,1317,1320],{},[1311,1318,1319],{},"Disponibilité des Services :"," La transition vers le Cloud implique souvent le découpage des applications en microservices. Cette réorganisation entraîne une multiplication des points de défaillance potentiels. Une vue d’ensemble permet de suivre la santé de chaque service et d’éviter de se perdre dans des applications, notamment avec le FaaS (Function-as-a-Service).",[26,1322,1323,1326],{},[1311,1324,1325],{},"Coût :"," Le Cloud Public offre une flexibilité exceptionnelle, mais peut aussi générer des coûts imprévus. L'Observabilité peut mettre en évidence les ressources sous-utilisées ou sur-provisionnées, aidant ainsi à optimiser les coûts.",[26,1328,1329,1332],{},[1311,1330,1331],{},"Sécurité :"," Il est important de ne pas délaisser la sécurité qui doit faire partie intégrante de l’observation de vos applications.",[26,1334,1335,1338],{},[1311,1336,1337],{},"Ressenti utilisateur :"," Une migration peut, même dans le cas de meilleures performances backend, avoir un impact significatif sur vos utilisateurs. S’il est positif, cette donnée justifiera votre décision de migrer.",[39,1340,1342],{"id":1341},"comment-aborder-lobservabilité","Comment aborder l’Observabilité",[19,1344,1345],{},"L’Observabilité ne doit pas être considérée comme solution à un problème technique. Son succès repose dans des fondations basées sur vos besoins métiers et son acceptation par tous.",[19,1347,1348],{},"A contrario de la supervision, qui n’est qu’un moyen technique de répondre à un besoin technique, il est essentiel de se poser les bonnes questions: ce dont vous avez réellement besoin doit être identifié.",[19,1350,1351],{},"Prenons l’exemple d’une Fintech:  elle doit répondre à des critères stricts comme la qualité de son service, la sécurité des données ou encore répondre à une forte croissance. Dans ce contexte, il parait évident que ses critères ne sont pas réservés aux équipes de développement. Il est préférable que les équipes directions ou clientèles connaissent l’état de leur solution et qu’un incident soit détecté rapidement, voire avant qu’il ne se produise. Et dans le cas d’un dysfonctionnement, il est impératif d’identifier le plus rapidement possible l’origine du problème. C’est ce que doit permettre l’Observabilité.",[19,1353,1354],{},"Une solution trop technique et complexe, ne servira que les équipes techniques et ne permettra pas forcément de comprendre l’impact pour les clients ou sur le CA de l’entreprise. De plus, un outil qui ne rassemble pas et n’exploite pas l’ensemble des données utiles à cette tâche ne sera pas efficace.",[39,1356,1358],{"id":1357},"datadog-lincontournable","Datadog, l’incontournable",[19,1360,1361,1362,1367],{},"Malgré un marché établi dans le domaine du monitoring et de la supervision, ",[979,1363,1366],{"href":1364,"rel":1365},"https:\u002F\u002Fwww.datadoghq.com\u002Ffr",[983],"Datadog"," a su se faire une place de leader avec l’émergence du Cloud Public.",[19,1369,1370],{},"Ayant accompagné de nombreuses sociétés avec Datadog notamment un leader de l’immobilier, une filiale d’un des plus gros fournisseurs mondiaux de services de restauration ou encore une Fintech; je vous propose mes retours d’expériences.",[19,1372,1373],{},[1374,1375],"img",{"alt":1376,"src":1377},"Gartner, Magic Quadrant for Observability Platforms, Gregg Siegfried, Padraig Byrne, Mrudula Bangera, Matt Crossley, 12 August 2024","\u002Fcontent-assets\u002F2024-10-31-lobservabilit-un-pilier-essentiel-dans-ladoption-du-cloud-et-des-architectures-modernes\u002Fassets\u002Fimg1.webp",[39,1379,1381],{"id":1380},"démarrer-de-zéro","Démarrer de zéro",[19,1383,1384],{},"Partons sur cette Startup bancaire. Elle disposait malgré ses liens avec une des principales banques française d’une indépendance technologique. Plus exactement, nous avons développé de zéro une solution bancaire en nous appuyant uniquement de technologies Cloud Public et sur Datadog bien évidement. Nous avions pris en compte les critères Fintech, cités plus haut, mais nous avions un objectif plus urgent: le Time to Market. Avec une disponibilité de l’application en quelques mois et des responsabilité bancaires, la question de l’Observabilité a été intégrée dès l’origine. Datadog donnait une vision :",[23,1386,1387,1396,1399,1402,1405,1408,1411],{},[26,1388,1389,1390,1395],{},"des tests métiers (approche ",[979,1391,1394],{"href":1392,"rel":1393},"https:\u002F\u002Fblog.hoppr.tech\u002Ftags\u002Fddd",[983],"DDD",")",[26,1397,1398],{},"la qualité de l’expérience utilisateur",[26,1400,1401],{},"la surveillance de la sécurité de l’intégralité de l’application et de son développement",[26,1403,1404],{},"le taux de disponibilité, dont les SLA du Core Banking",[26,1406,1407],{},"l’impact utilisateur des mises en production (jusqu’à 300\u002Fsemaine dans les premiers mois)",[26,1409,1410],{},"des performances",[26,1412,1413],{},"des KPI métier, technique, FinOps, etc…\nCependant son implémentation était relativement simple car intégrée dès l’origine. Maintenant, évoquons le déploiement à l’échelle d’une entreprise de promotion immobilière cotée, avec des centaines de projets historiques comme novateurs.",[39,1415,1417],{"id":1416},"intégrer-lobservabilité-à-léchelle","Intégrer l’Observabilité à l’échelle",[19,1419,1420],{},"Dans cette situation, il ne faut pas que la solution soit portée par des objectifs purement techniques. Il y a également des enjeux d’échelle qui impliquent une organisation de l’Observabilité et des enjeux financiers et écologiques (CSRD).",[19,1422,1423],{},"Dans un premier temps, il faut prendre en compte l’ensemble des besoins de tous les acteurs : du DSI au développeur en passant par les responsables métiers. Pensez également à intégrer les responsables FinOps et GreenOps. Il est également indispensable d’avoir un inventaire des projets avec leurs priorités et criticités. Une étude permettra de choisir le meilleur outil mais aussi les étapes nécessaires à son adoption. Dans mon cas où la migration Cloud Public était finalisée, Datadog remporta haut la main la première place.",[19,1425,1426],{},"La seconde étape passe par la définitions des rôles des différents utilisateurs et la segmentation des données. Heureusement, Datadog propose un bon nombre de solutions:",[23,1428,1429,1432,1435,1438,1441,1444],{},[26,1430,1431],{},"Organisation sur plusieurs comptes",[26,1433,1434],{},"Droits et RBAC",[26,1436,1437],{},"Index par données",[26,1439,1440],{},"Visibilité des Dashbords",[26,1442,1443],{},"Filtres",[26,1445,1446],{},"Etc…\nUne fois la question de l’organisation traitée, nous pouvons réaliser un premier MVP (Produit minimum viable) d’un échantillon de projets représentatifs basé sur: leurs criticités, leurs aspects technologiques mais aussi leurs contextes fonctionnels.",[19,1448,1449],{},[1374,1450],{"alt":1451,"src":1452},"Datadog propose une vision Map","\u002Fcontent-assets\u002F2024-10-31-lobservabilit-un-pilier-essentiel-dans-ladoption-du-cloud-et-des-architectures-modernes\u002Fassets\u002Fimg2.webp",[19,1454,1455],{},"Aujourd’hui, l’entreprise utilise Datadog sur l’ensemble de son spectre de projet (de l’agence immobilière à l’application de gestion des ventes) avec l’ensemble des équipes (technique ou non). Les rituels intègrent les rapports, l’infogérance est traitée via la solution, le pilotage FinOps du groupe est assuré par Datadog.",[39,1457,1459],{"id":1458},"passer-au-niveau-supérieur-en-alliant-méthodologie-et-observabilité","Passer au niveau supérieur en alliant méthodologie et Observabilité",[19,1461,1462],{},"S’appuyer sur les piliers d’une approche moderne augmentera l’efficacité de l’Observabilité. Il est essentiel que tous les acteurs de vos projets interagissent avec votre outil d’Observabilité, en consomment et en intégrant leurs données. Ces outils sont un investissement financier qu’il est important de rentabiliser dans une démarche ouverte.",[39,1464,1466],{"id":1465},"software-craftsmanship-pour-une-meilleure-intégration-de-lobservabilité","Software Craftsmanship : Pour une meilleure intégration de l’Observabilité",[19,1468,1469],{},"Le software craftsmanship est un mouvement qui prône l’excellence dans le développement logiciel, avec une attention particulière à la qualité du code, la testabilité et la maintenabilité.",[19,1471,1472],{},"Un code bien structuré, découplé et respectant les bonnes pratiques de software craftsmanship est plus facilement observable. Par exemple, les logs sont standardisés, les métriques sont instrumentées de manière cohérente et les erreurs sont gérées proprement.",[39,1474,1476],{"id":1475},"devops-une-observabilité-industrialisée","DevOps: Une Observabilité industrialisée",[19,1478,1479],{},"L’approche DevOps améliore encore la perspective d’Observabilité. En effet, nous pouvons industrialiser un ensemble de nouveaux éléments:",[23,1481,1482,1485,1488,1491],{},[26,1483,1484],{},"Déployer votre Observabilité en mode infrastructure as code (données, dashboard, alarme, …)",[26,1486,1487],{},"Les tests dans vos pipelines de déploiements intégrés avec votre outil d’Observabilité.",[26,1489,1490],{},"Automatiser des tests notamment dans vos déploiements sans indisponibilité (canary, blue and green, …)",[26,1492,1493],{},"Visualiser l’impact financier ou green d’une nouvelle fonctionnalité",[39,1495,1497],{"id":1496},"agilité-lobservabilité-mesure-vos-kpi","Agilité: L’Observabilité mesure vos KPI",[19,1499,1500],{},"Afin de suivre l’avancée de votre projet et remonter des données pertinentes pendant vos rituels, un outil d’Observabilité sera un grand allié. C’est dans ces circonstances que l’on comprend l’intérêt de l’accessibilité lié à l’Observabilité, car ces données sont récupérables directement par le chef de projet, le scrum master, etc.",[19,1502,1503,1504,393],{},"N’oubliez pas d’intégrer des KPI impacts environnementaux à vos projets (consommation carbone, valeur énergétique par utilisateur, …). Vous pouvez vous inspirez de notre article ",[979,1505,1508],{"href":1506,"rel":1507},"https:\u002F\u002Fblog.hoppr.tech\u002Fblogs\u002F2024-09-26-low-carbon-dans-le-cloud-partie-2",[983],"Low Carbon dans le Cloud - Partie 2",[39,1510,1512],{"id":1511},"pour-conclure","Pour conclure",[19,1514,1515],{},"Que vous soyez DSI, CTO ou encore CPTO, vous avez une vision stratégique de l’Observabilité. C’est pour vous un élément concurrentiel, un gain de qualité de vos produits, un gain d’argent des fois conséquent. Cependant, l’appropriation d’une solution avec la bonne méthodologie par vos équipes n’est pas une chose simple. Vous faire accompagner dans cette démarche sera certainement un gain de temps et de réussite, mais soyez vigilant de choisir un partenaire qui sera capable d’aborder ce chantier à votre niveau.",[19,1517,1518],{},"Nous avons mis en avant Dadadog car, dans 9 fois sur 10, il ressort gagnant de nos études et benchmark, depuis déjà quelques années. Aujourd’hui convaincu par notre expérience dans l’accompagnement à l’Observabilité, HoppR a décidé de mettre en place un partenariat unique avec Datadog : HoppR et Datadog pour une Observabilité comme pilier stratégique.",{"title":63,"searchDepth":86,"depth":86,"links":1520},[1521,1522,1523,1524,1525,1526,1527,1528,1529,1530,1531],{"id":1252,"depth":86,"text":1253},{"id":1300,"depth":86,"text":1301},{"id":1341,"depth":86,"text":1342},{"id":1357,"depth":86,"text":1358},{"id":1380,"depth":86,"text":1381},{"id":1416,"depth":86,"text":1417},{"id":1458,"depth":86,"text":1459},{"id":1465,"depth":86,"text":1466},{"id":1475,"depth":86,"text":1476},{"id":1496,"depth":86,"text":1497},{"id":1511,"depth":86,"text":1512},"2024-10-31T16:21:44.196Z","Avec l'essor des technologies, notamment de l’IA, les grands groupes comme les start-up se tournent vers des plateformes modernes, parfois complexes (microservices, kubernetes, NoSQL, kafka, …), ainsi",{},"\u002Fblogs\u002F2024-10-31-lobservabilit-un-pilier-essentiel-dans-ladoption-du-cloud-et-des-architectures-modernes",[1537,1539,1541],{"id":1190,"name":1191,"image":1538,"linkedin":1193,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F82ebd0fe-de28-43f3-ab7b-0431af41baad\u002FPhoto_HoppR.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45GO43JXI4%2F20241031%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20241031T162144Z&X-Amz-Expires=3600&X-Amz-Signature=575fafdda7bf9cfe162a2357d2b8c4fd07170d9f398f28e2958fb9d613a531d5&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":1184,"name":1185,"image":1540,"linkedin":1187,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc88f5dfa-16db-4e6f-acf1-34dd80ee8766\u002Femma_hoppr.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45GO43JXI4%2F20241031%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20241031T162144Z&X-Amz-Expires=3600&X-Amz-Signature=c3ec652c8438443b244fcc323f7d69cb3a6689d16e46ccd7512dd616836f78b1&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":1542,"name":1543,"image":1544,"linkedin":1545,"x":1188},"e8163b24-7e01-41c5-adbf-0dc655f929d0","Nicolas Zago","https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Ff8f82a79-9d41-4302-b1a5-37882985167f\u002Fnicoz_hoppr.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45GO43JXI4%2F20241031%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20241031T162144Z&X-Amz-Expires=3600&X-Amz-Signature=df02b8ec0091aabb844ccd87f5ecc73a9d04622d0ecf0cd9ca81d722f3ca1c8f&X-Amz-SignedHeaders=host&x-id=GetObject","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fnicolaszago\u002F",{"title":1214,"description":1533},"blogs\u002F2024-10-31-lobservabilit-un-pilier-essentiel-dans-ladoption-du-cloud-et-des-architectures-modernes\u002Findex",[1549,1550,1551,1552,1208],"cloud-platform","architecture","observabilité","devops","IHNFVwmNOBdYB2cwFLg5qdqDGTksVCSw5EHmKp_U_kQ",{"id":1555,"title":1556,"alt":1557,"authors":1558,"body":1561,"date":2936,"description":2937,"extension":1178,"image":1179,"meta":2938,"navigation":102,"ogImage":1179,"path":2939,"published":102,"reviewers":2940,"seo":2947,"stem":2948,"tags":2949,"__hash__":2952},"blogs\u002Fblogs\u002F2024-11-14-crer-une-cli-pour-un-projet-modulaire-avec-commanderjs\u002Findex.md","Créer une CLI pour un projet modulaire avec Commander.js","Lignes de code utilisant Commander.js pour créer une CLI, tirées du projet Dragee.io. ",[1559],{"id":1190,"name":1191,"image":1560,"linkedin":1193,"x":1188},".\u002Fassets\u002Fauthor-michal-bernasinski.webp",{"type":16,"value":1562,"toc":2927},[1563,1589,1609,1617,1623,1627,1630,1646,1653,1665,1683,1688,1692,1704,1720,1936,1942,1945,1951,2012,2024,2090,2096,2197,2409,2412,2470,2474,2478,2500,2507,2642,2656,2660,2673,2676,2783,2786,2895,2897,2915,2924],[479,1564,1565],{},[19,1566,1567,1568,1571,1572,1579,1580,1583,1584],{},"ℹ️ ",[1311,1569,1570],{},"Note :"," Dragee.io a grandi depuis cet article, et est devenu ",[1311,1573,1574],{},[979,1575,1578],{"href":1576,"rel":1577},"https:\u002F\u002Fapp.fixentropy.io",[983],"Fixentropy.io",". ",[1581,1582],"br",{},"Vous souhaitez combattre votre entropie logicielle ? N'hésitez pas à ",[979,1585,1588],{"href":1586,"rel":1587},"https:\u002F\u002Fcal.com\u002Fschedule-your-call\u002Fschedule-your-call",[983],"prendre rendez-vous pour une démonstration.",[19,1590,1591,1596,1597,1602,1603,1608],{},[979,1592,1595],{"href":1593,"rel":1594},"https:\u002F\u002Fgithub.com\u002Fdragee-io",[983],"Dragee.io"," est un nouveau projet lié à l'architecture logicielle, permettant entre autres l'analyse d'une architecture dans la vision ",[979,1598,1601],{"href":1599,"rel":1600},"https:\u002F\u002Fblog.hoppr.tech\u002Ftags\u002Fcraft",[983],"Craft"," portée par ",[979,1604,1607],{"href":1605,"rel":1606},"https:\u002F\u002Fwww.hoppr.tech\u002F",[983],"HoppR",". Nos ambitions pour ce projet sont fortes, celui-ci se voulant une solution complète et constituée de nombreuses fonctionnalités. Nous aurons d’ailleurs d’autres occasions de vous partager son avancée.",[19,1610,1611,1612,1616],{},"Les fonctionnalités allant être ajoutées de manière itérative, nous avons étudié la meilleure façon de faire grandir le projet. Nous sommes donc partis sur une CLI (",[1613,1614,1615],"em",{},"Command-Line Interface",") présentant des commandes simples à l’utilisateur. Elle sera le cœur de notre solution, auquel viendront se greffer les futurs nouveaux modules.",[19,1618,1619,1620,1622],{},"Dans cet article, je vais vous présenter notre manière d’intégrer ces modules dans l’écosystème ",[1311,1621,1595],{},", en créant des commandes qui leurs sont propres, et en les ajoutant à celles déjà existantes dans la CLI centrale.",[39,1624,1626],{"id":1625},"présentation-de-commanderjs","Présentation de Commander.js",[19,1628,1629],{},"Tout d’abord, nous avons besoin d’un outil pour développer cette CLI.",[19,1631,1632,1633,1638,1639,1023,1641,393],{},"Nous avons pour cela choisi le projet ",[979,1634,1637],{"href":1635,"rel":1636},"https:\u002F\u002Fgithub.com\u002Ftj\u002Fcommander.js\u002Ftree\u002Fmaster",[983],"Commander.js",", qui permet d’en créer une facilement, sur des environnements d’exécution JS\u002FTS tels que Node.js, ou, dans le cas du projet ",[1311,1640,1595],{},[979,1642,1645],{"href":1643,"rel":1644},"https:\u002F\u002Fbun.sh\u002F",[983],"Bun",[19,1647,1648,1649,1652],{},"Cela nous permet de concevoir ",[979,1650,1595],{"href":1593,"rel":1651},[983]," avec une stack où nos développeurs, principalement issus du monde du web, seront à l’aise, plutôt que d’investir dans une solution non maîtrisée (par exemple Go) ou pas encore assez mature.",[19,1654,1655,1656,1661,1662,1664],{},"Un ",[979,1657,1660],{"href":1658,"rel":1659},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fcommander",[983],"package npm"," étant disponible, nous allons ici pouvoir installer ",[1311,1663,1637],{}," en dépendance de nos projets :",[58,1666,1670],{"className":1667,"code":1668,"language":1669,"meta":63,"style":63},"language-shell shiki shiki-themes github-dark-default","bun add commander\n","shell",[65,1671,1672],{"__ignoreMap":63},[68,1673,1674,1677,1680],{"class":70,"line":71},[68,1675,1676],{"class":92},"bun",[68,1678,1679],{"class":1053}," add",[68,1681,1682],{"class":1053}," commander\n",[19,1684,1685,1687],{},[1311,1686,1637],{}," va nous permettre de gérer le parsing des arguments, de traiter les cas d’erreurs et d’afficher une documentation (description, aide, etc.) pour chaque commande. Cet outil contient beaucoup d’options de configuration, nous resterons ici sur une utilisation simple.",[39,1689,1691],{"id":1690},"création-dune-commande","Création d’une commande",[19,1693,1694,1695,1698,1699,393],{},"Dans la galaxie des projets ",[979,1696,1595],{"href":1593,"rel":1697},[983],", nous disposons donc d’un projet central, celui exposant cette fameuse CLI, le bien nommé ",[979,1700,1703],{"href":1701,"rel":1702},"https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fdragee-cli",[983],"dragee-cli",[19,1705,1706,1707,1710,1711,1714,1715,393],{},"Il contient de base deux commandes pour nos besoins fonctionnels : une pour générer des rapports (nommée ",[1613,1708,1709],{},"report","), l’autre pour générer des modélisations d’architecture (",[1613,1712,1713],{},"draw","). Écrivons la première, et plaçons-là dans notre ",[979,1716,1719],{"href":1717,"rel":1718},"https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fdragee-cli\u002Fblob\u002Fmain\u002Findex.ts",[983],"index.ts",[58,1721,1725],{"className":1722,"code":1723,"language":1724,"meta":63,"style":63},"language-typescript shiki shiki-themes github-dark-default","import { Command } from 'commander';\nimport { reportCommandhandler } from '.\u002Fsrc\u002Fcommands\u002Freport-command.handler.ts';\n\nconst report = new Command('report')\n    .alias('r')\n    .summary('builds asserters rules report')\n    .description(\n        'Builds asserters rules report.\\n' +\n            '- Lookups dragees in [--from-dir] directory\\n' +\n            '- Downloads asserters for dragees namespaces\\n' +\n            '- Executes rules from asserters\\n' +\n            '- Builds reports in [--to-dir] directory'\n    )\n    .option('--from, --from-dir \u003Cpath-to-dir>', 'directory in where to lookup for dragees', '.')\n    .option('--to --to-dir \u003Cpath-to-dir>', 'directory in where to store reports', '.\u002Fdragee\u002Freports')\n    .action(reportCommandhandler);\n","typescript",[65,1726,1727,1743,1757,1761,1783,1798,1812,1822,1836,1847,1858,1869,1874,1879,1903,1926],{"__ignoreMap":63},[68,1728,1729,1732,1735,1737,1740],{"class":70,"line":71},[68,1730,1731],{"class":78},"import",[68,1733,1734],{"class":74}," { Command } ",[68,1736,765],{"class":78},[68,1738,1739],{"class":1053}," 'commander'",[68,1741,1742],{"class":74},";\n",[68,1744,1745,1747,1750,1752,1755],{"class":70,"line":86},[68,1746,1731],{"class":78},[68,1748,1749],{"class":74}," { reportCommandhandler } ",[68,1751,765],{"class":78},[68,1753,1754],{"class":1053}," '.\u002Fsrc\u002Fcommands\u002Freport-command.handler.ts'",[68,1756,1742],{"class":74},[68,1758,1759],{"class":70,"line":99},[68,1760,103],{"emptyLinePlaceholder":102},[68,1762,1763,1766,1769,1771,1773,1776,1778,1781],{"class":70,"line":106},[68,1764,1765],{"class":78},"const",[68,1767,1768],{"class":260}," report",[68,1770,139],{"class":78},[68,1772,142],{"class":78},[68,1774,1775],{"class":196}," Command",[68,1777,1050],{"class":74},[68,1779,1780],{"class":1053},"'report'",[68,1782,1077],{"class":74},[68,1784,1785,1788,1791,1793,1796],{"class":70,"line":112},[68,1786,1787],{"class":74},"    .",[68,1789,1790],{"class":196},"alias",[68,1792,1050],{"class":74},[68,1794,1795],{"class":1053},"'r'",[68,1797,1077],{"class":74},[68,1799,1800,1802,1805,1807,1810],{"class":70,"line":148},[68,1801,1787],{"class":74},[68,1803,1804],{"class":196},"summary",[68,1806,1050],{"class":74},[68,1808,1809],{"class":1053},"'builds asserters rules report'",[68,1811,1077],{"class":74},[68,1813,1814,1816,1819],{"class":70,"line":153},[68,1815,1787],{"class":74},[68,1817,1818],{"class":196},"description",[68,1820,1821],{"class":74},"(\n",[68,1823,1824,1827,1830,1833],{"class":70,"line":166},[68,1825,1826],{"class":1053},"        'Builds asserters rules report.",[68,1828,1829],{"class":78},"\\n",[68,1831,1832],{"class":1053},"'",[68,1834,1835],{"class":78}," +\n",[68,1837,1838,1841,1843,1845],{"class":70,"line":177},[68,1839,1840],{"class":1053},"            '- Lookups dragees in [--from-dir] directory",[68,1842,1829],{"class":78},[68,1844,1832],{"class":1053},[68,1846,1835],{"class":78},[68,1848,1849,1852,1854,1856],{"class":70,"line":182},[68,1850,1851],{"class":1053},"            '- Downloads asserters for dragees namespaces",[68,1853,1829],{"class":78},[68,1855,1832],{"class":1053},[68,1857,1835],{"class":78},[68,1859,1860,1863,1865,1867],{"class":70,"line":202},[68,1861,1862],{"class":1053},"            '- Executes rules from asserters",[68,1864,1829],{"class":78},[68,1866,1832],{"class":1053},[68,1868,1835],{"class":78},[68,1870,1871],{"class":70,"line":217},[68,1872,1873],{"class":1053},"            '- Builds reports in [--to-dir] directory'\n",[68,1875,1876],{"class":70,"line":223},[68,1877,1878],{"class":74},"    )\n",[68,1880,1881,1883,1886,1888,1891,1893,1896,1898,1901],{"class":70,"line":228},[68,1882,1787],{"class":74},[68,1884,1885],{"class":196},"option",[68,1887,1050],{"class":74},[68,1889,1890],{"class":1053},"'--from, --from-dir \u003Cpath-to-dir>'",[68,1892,1023],{"class":74},[68,1894,1895],{"class":1053},"'directory in where to lookup for dragees'",[68,1897,1023],{"class":74},[68,1899,1900],{"class":1053},"'.'",[68,1902,1077],{"class":74},[68,1904,1905,1907,1909,1911,1914,1916,1919,1921,1924],{"class":70,"line":248},[68,1906,1787],{"class":74},[68,1908,1885],{"class":196},[68,1910,1050],{"class":74},[68,1912,1913],{"class":1053},"'--to --to-dir \u003Cpath-to-dir>'",[68,1915,1023],{"class":74},[68,1917,1918],{"class":1053},"'directory in where to store reports'",[68,1920,1023],{"class":74},[68,1922,1923],{"class":1053},"'.\u002Fdragee\u002Freports'",[68,1925,1077],{"class":74},[68,1927,1928,1930,1933],{"class":70,"line":266},[68,1929,1787],{"class":74},[68,1931,1932],{"class":196},"action",[68,1934,1935],{"class":74},"(reportCommandhandler);\n",[19,1937,1938,1939],{},"Cette commande contient un certain nombre d’informations (description, options), et surtout l’action à réaliser, une fonction passée en paramètre, ici nommée ",[1613,1940,1941],{},"reportCommandhandler.",[19,1943,1944],{},"La bonne pratique est ici de l’importer d’un fichier à part. En effet, la commande est à la CLI ce que le contrôleur est à nos applications web. L’action est équivalente à un service métier, placée ailleurs et appelée par notre commande, dont seule la définition est décrite dans l’index.",[19,1946,1947,1948,1950],{},"Nous allons ensuite, sur le même modèle, créer la commande ",[1613,1949,1713],{},". Pour finaliser notre CLI, nous devons également créer la commande de plus haut niveau, incorporant ces deux commandes :",[58,1952,1954],{"className":1722,"code":1953,"language":1724,"meta":63,"style":63},"new Command()\n    .addCommand(report)\n    .addCommand(draw)\n    .showHelpAfterError()\n    .showSuggestionAfterError()\n    .parse(process.argv);\n",[65,1955,1956,1965,1975,1984,1993,2002],{"__ignoreMap":63},[68,1957,1958,1961,1963],{"class":70,"line":71},[68,1959,1960],{"class":78},"new",[68,1962,1775],{"class":196},[68,1964,308],{"class":74},[68,1966,1967,1969,1972],{"class":70,"line":86},[68,1968,1787],{"class":74},[68,1970,1971],{"class":196},"addCommand",[68,1973,1974],{"class":74},"(report)\n",[68,1976,1977,1979,1981],{"class":70,"line":99},[68,1978,1787],{"class":74},[68,1980,1971],{"class":196},[68,1982,1983],{"class":74},"(draw)\n",[68,1985,1986,1988,1991],{"class":70,"line":106},[68,1987,1787],{"class":74},[68,1989,1990],{"class":196},"showHelpAfterError",[68,1992,308],{"class":74},[68,1994,1995,1997,2000],{"class":70,"line":112},[68,1996,1787],{"class":74},[68,1998,1999],{"class":196},"showSuggestionAfterError",[68,2001,308],{"class":74},[68,2003,2004,2006,2009],{"class":70,"line":148},[68,2005,1787],{"class":74},[68,2007,2008],{"class":196},"parse",[68,2010,2011],{"class":74},"(process.argv);\n",[19,2013,2014,2015,2018,2019,393],{},"L’exécutable de ",[979,2016,1595],{"href":1593,"rel":2017},[983]," est ensuite construit à l’aide d’une ",[979,2020,2023],{"href":2021,"rel":2022},"https:\u002F\u002Fbun.sh\u002Fdocs\u002Fbundler\u002Fexecutables",[983],"commande native de Bun",[58,2025,2027],{"className":1667,"code":2026,"language":1669,"meta":63,"style":63},"> bun run build\n$ bun build index.ts --target bun --compile --outfile dist\u002Fdragee-cli\n [237ms]  bundle  151 modules\n  [51ms] compile  dist\u002Fdragee-cli.exe\n",[65,2028,2029,2037,2065,2079],{"__ignoreMap":63},[68,2030,2031,2034],{"class":70,"line":71},[68,2032,2033],{"class":78},">",[68,2035,2036],{"class":74}," bun run build\n",[68,2038,2039,2042,2045,2048,2051,2054,2056,2059,2062],{"class":70,"line":86},[68,2040,2041],{"class":92},"$",[68,2043,2044],{"class":1053}," bun",[68,2046,2047],{"class":1053}," build",[68,2049,2050],{"class":1053}," index.ts",[68,2052,2053],{"class":260}," --target",[68,2055,2044],{"class":1053},[68,2057,2058],{"class":260}," --compile",[68,2060,2061],{"class":260}," --outfile",[68,2063,2064],{"class":1053}," dist\u002Fdragee-cli\n",[68,2066,2067,2070,2073,2076],{"class":70,"line":99},[68,2068,2069],{"class":74}," [237ms]  ",[68,2071,2072],{"class":92},"bundle",[68,2074,2075],{"class":260},"  151",[68,2077,2078],{"class":1053}," modules\n",[68,2080,2081,2084,2087],{"class":70,"line":106},[68,2082,2083],{"class":74},"  [51ms] ",[68,2085,2086],{"class":92},"compile",[68,2088,2089],{"class":1053},"  dist\u002Fdragee-cli.exe\n",[19,2091,2092,2093,2095],{},"Et voici ce que tout cela donne, avec les options de documentation (help) sur la commande parent, puis sur la commande ",[1613,2094,1713],{}," :",[58,2097,2099],{"className":1667,"code":2098,"language":1669,"meta":63,"style":63},"> .\u002Fdragee-cli --help\nUsage: index [options] [command]\n\nOptions:\n  -h, --help          display help for command\n\nCommands:\n  report|r [options]  builds asserters rules report\n  draw|d [options]    builds graphers graphs models\n  help [command]      display help for command\n",[65,2100,2101,2108,2119,2123,2128,2148,2152,2157,2171,2184],{"__ignoreMap":63},[68,2102,2103,2105],{"class":70,"line":71},[68,2104,2033],{"class":78},[68,2106,2107],{"class":74}," .\u002Fdragee-cli --help\n",[68,2109,2110,2113,2116],{"class":70,"line":86},[68,2111,2112],{"class":92},"Usage:",[68,2114,2115],{"class":1053}," index",[68,2117,2118],{"class":74}," [options] [command]\n",[68,2120,2121],{"class":70,"line":99},[68,2122,103],{"emptyLinePlaceholder":102},[68,2124,2125],{"class":70,"line":106},[68,2126,2127],{"class":92},"Options:\n",[68,2129,2130,2133,2136,2139,2142,2145],{"class":70,"line":112},[68,2131,2132],{"class":92},"  -h,",[68,2134,2135],{"class":260}," --help",[68,2137,2138],{"class":1053},"          display",[68,2140,2141],{"class":1053}," help",[68,2143,2144],{"class":1053}," for",[68,2146,2147],{"class":1053}," command\n",[68,2149,2150],{"class":70,"line":148},[68,2151,103],{"emptyLinePlaceholder":102},[68,2153,2154],{"class":70,"line":153},[68,2155,2156],{"class":92},"Commands:\n",[68,2158,2159,2162,2165,2168],{"class":70,"line":166},[68,2160,2161],{"class":92},"  report",[68,2163,2164],{"class":78},"|",[68,2166,2167],{"class":92},"r",[68,2169,2170],{"class":74}," [options]  builds asserters rules report\n",[68,2172,2173,2176,2178,2181],{"class":70,"line":177},[68,2174,2175],{"class":92},"  draw",[68,2177,2164],{"class":78},[68,2179,2180],{"class":92},"d",[68,2182,2183],{"class":74}," [options]    builds graphers graphs models\n",[68,2185,2186,2189,2192,2195],{"class":70,"line":182},[68,2187,2188],{"class":92},"  help",[68,2190,2191],{"class":74}," [command]      display help ",[68,2193,2194],{"class":78},"for",[68,2196,2147],{"class":74},[58,2198,2200],{"className":1667,"code":2199,"language":1669,"meta":63,"style":63},"> .\u002Fdragee-cli report --help\nUsage: index report|r [options]\n\nBuilds asserters rules report.\n- Lookups dragees in [--from-dir] directory\n- Downloads asserters for dragees namespaces\n- Executes rules from asserters\n- Builds reports in [--to-dir] directory\n\nOptions:\n  --from, --from-dir \u003Cpath-to-dir>  directory in where to lookup for dragees (default: \".\")\n  --to --to-dir \u003Cpath-to-dir>       directory in where to store reports (default: \".\u002Fdragee\u002Freports\")\n  -h, --help                        display help for command\n",[65,2201,2202,2209,2224,2228,2242,2259,2275,2290,2305,2309,2313,2357,2394],{"__ignoreMap":63},[68,2203,2204,2206],{"class":70,"line":71},[68,2205,2033],{"class":78},[68,2207,2208],{"class":74}," .\u002Fdragee-cli report --help\n",[68,2210,2211,2213,2215,2217,2219,2221],{"class":70,"line":86},[68,2212,2112],{"class":92},[68,2214,2115],{"class":1053},[68,2216,1768],{"class":1053},[68,2218,2164],{"class":78},[68,2220,2167],{"class":92},[68,2222,2223],{"class":74}," [options]\n",[68,2225,2226],{"class":70,"line":99},[68,2227,103],{"emptyLinePlaceholder":102},[68,2229,2230,2233,2236,2239],{"class":70,"line":106},[68,2231,2232],{"class":92},"Builds",[68,2234,2235],{"class":1053}," asserters",[68,2237,2238],{"class":1053}," rules",[68,2240,2241],{"class":1053}," report.\n",[68,2243,2244,2247,2250,2253,2256],{"class":70,"line":112},[68,2245,2246],{"class":92},"-",[68,2248,2249],{"class":1053}," Lookups",[68,2251,2252],{"class":1053}," dragees",[68,2254,2255],{"class":1053}," in",[68,2257,2258],{"class":74}," [--from-dir] directory\n",[68,2260,2261,2263,2266,2268,2270,2272],{"class":70,"line":148},[68,2262,2246],{"class":92},[68,2264,2265],{"class":1053}," Downloads",[68,2267,2235],{"class":1053},[68,2269,2144],{"class":1053},[68,2271,2252],{"class":1053},[68,2273,2274],{"class":1053}," namespaces\n",[68,2276,2277,2279,2282,2284,2287],{"class":70,"line":153},[68,2278,2246],{"class":92},[68,2280,2281],{"class":1053}," Executes",[68,2283,2238],{"class":1053},[68,2285,2286],{"class":1053}," from",[68,2288,2289],{"class":1053}," asserters\n",[68,2291,2292,2294,2297,2300,2302],{"class":70,"line":166},[68,2293,2246],{"class":92},[68,2295,2296],{"class":1053}," Builds",[68,2298,2299],{"class":1053}," reports",[68,2301,2255],{"class":1053},[68,2303,2304],{"class":74}," [--to-dir] directory\n",[68,2306,2307],{"class":70,"line":177},[68,2308,103],{"emptyLinePlaceholder":102},[68,2310,2311],{"class":70,"line":182},[68,2312,2127],{"class":92},[68,2314,2315,2318,2321,2324,2327,2329,2331,2334,2336,2339,2342,2345,2347,2349,2352,2355],{"class":70,"line":202},[68,2316,2317],{"class":92},"  --from,",[68,2319,2320],{"class":260}," --from-dir",[68,2322,2323],{"class":78}," \u003C",[68,2325,2326],{"class":1053},"path-to-di",[68,2328,2167],{"class":74},[68,2330,2033],{"class":78},[68,2332,2333],{"class":1053},"  directory",[68,2335,2255],{"class":1053},[68,2337,2338],{"class":1053}," where",[68,2340,2341],{"class":1053}," to",[68,2343,2344],{"class":1053}," lookup",[68,2346,2144],{"class":1053},[68,2348,2252],{"class":1053},[68,2350,2351],{"class":74}," (default: ",[68,2353,2354],{"class":1053},"\".\"",[68,2356,1077],{"class":74},[68,2358,2359,2362,2365,2367,2369,2371,2373,2376,2378,2380,2382,2385,2387,2389,2392],{"class":70,"line":217},[68,2360,2361],{"class":92},"  --to",[68,2363,2364],{"class":260}," --to-dir",[68,2366,2323],{"class":78},[68,2368,2326],{"class":1053},[68,2370,2167],{"class":74},[68,2372,2033],{"class":78},[68,2374,2375],{"class":1053},"       directory",[68,2377,2255],{"class":1053},[68,2379,2338],{"class":1053},[68,2381,2341],{"class":1053},[68,2383,2384],{"class":1053}," store",[68,2386,2299],{"class":1053},[68,2388,2351],{"class":74},[68,2390,2391],{"class":1053},"\".\u002Fdragee\u002Freports\"",[68,2393,1077],{"class":74},[68,2395,2396,2398,2400,2403,2405,2407],{"class":70,"line":223},[68,2397,2132],{"class":92},[68,2399,2135],{"class":260},[68,2401,2402],{"class":1053},"                        display",[68,2404,2141],{"class":1053},[68,2406,2144],{"class":1053},[68,2408,2147],{"class":1053},[19,2410,2411],{},"Ce qui donne à l’usage :",[58,2413,2415],{"className":1667,"code":2414,"language":1669,"meta":63,"style":63},"> .\u002Fdragee-cli report --from-dir .\u002Ftest\u002Fapproval\u002Fsample\u002F --to-dir .\u002Foutput\nLooking up for dragees in directory: .\u002Ftest\u002Fapproval\u002Fsample\u002F\nLooking up for namespaces\nLooking up for projects\n...\n",[65,2416,2417,2424,2444,2454,2465],{"__ignoreMap":63},[68,2418,2419,2421],{"class":70,"line":71},[68,2420,2033],{"class":78},[68,2422,2423],{"class":74}," .\u002Fdragee-cli report --from-dir .\u002Ftest\u002Fapproval\u002Fsample\u002F --to-dir .\u002Foutput\n",[68,2425,2426,2429,2432,2434,2436,2438,2441],{"class":70,"line":86},[68,2427,2428],{"class":92},"Looking",[68,2430,2431],{"class":1053}," up",[68,2433,2144],{"class":1053},[68,2435,2252],{"class":1053},[68,2437,2255],{"class":1053},[68,2439,2440],{"class":1053}," directory:",[68,2442,2443],{"class":1053}," .\u002Ftest\u002Fapproval\u002Fsample\u002F\n",[68,2445,2446,2448,2450,2452],{"class":70,"line":99},[68,2447,2428],{"class":92},[68,2449,2431],{"class":1053},[68,2451,2144],{"class":1053},[68,2453,2274],{"class":1053},[68,2455,2456,2458,2460,2462],{"class":70,"line":106},[68,2457,2428],{"class":92},[68,2459,2431],{"class":1053},[68,2461,2144],{"class":1053},[68,2463,2464],{"class":1053}," projects\n",[68,2466,2467],{"class":70,"line":112},[68,2468,2469],{"class":260},"...\n",[39,2471,2473],{"id":2472},"architecture-modulaire","Architecture modulaire",[582,2475,2477],{"id":2476},"module-enfant","Module enfant",[19,2479,2480,2481,2486,2487,2490,2491,2493,2494,2499],{},"Maintenant que notre projet CLI est créé et fonctionnel, nous allons nous attaquer à un nouveau module, ",[979,2482,2485],{"href":2483,"rel":2484},"https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fdragee-asserter-generator",[983],"dragee-asserter-generator",". Celui-ci a pour mission de générer un squelette d’",[1613,2488,2489],{},"asserter"," pour le projet ",[1311,2492,1595],{},", comme ",[979,2495,2498],{"href":2496,"rel":2497},"https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fddd-asserter",[983],"ddd-asserter ","par exemple.",[19,2501,2502,2503,2095],{},"Après ajout de la dépendance Commander à ce second projet, on crée la commande nécessaire dans ",[979,2504,2485],{"href":2505,"rel":2506},"https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fdragee-asserter-generator\u002Fblob\u002Fmain\u002Findex.ts",[983],[58,2508,2510],{"className":1722,"code":2509,"language":1724,"meta":63,"style":63},"import { Command } from 'commander';\n\nexport const generateAsserter = new Command('generate-asserter')\n    .alias('ga')\n    .summary('generates a new asserter project')\n    .description('Generates a new asserter project.\\nBased on a standard dragee asserter template, with mandatory dependancies and a sample rule.')\n    .requiredOption('-n, --name \u003Cstring>', 'name of the new asserter project')\n    .requiredOption('-od, --output-dir \u003Cstring>', 'output dir for the new asserter project')\n    .action(generatorHandler);\n",[65,2511,2512,2524,2528,2552,2565,2578,2596,2615,2633],{"__ignoreMap":63},[68,2513,2514,2516,2518,2520,2522],{"class":70,"line":71},[68,2515,1731],{"class":78},[68,2517,1734],{"class":74},[68,2519,765],{"class":78},[68,2521,1739],{"class":1053},[68,2523,1742],{"class":74},[68,2525,2526],{"class":70,"line":86},[68,2527,103],{"emptyLinePlaceholder":102},[68,2529,2530,2533,2536,2539,2541,2543,2545,2547,2550],{"class":70,"line":99},[68,2531,2532],{"class":78},"export",[68,2534,2535],{"class":78}," const",[68,2537,2538],{"class":260}," generateAsserter",[68,2540,139],{"class":78},[68,2542,142],{"class":78},[68,2544,1775],{"class":196},[68,2546,1050],{"class":74},[68,2548,2549],{"class":1053},"'generate-asserter'",[68,2551,1077],{"class":74},[68,2553,2554,2556,2558,2560,2563],{"class":70,"line":106},[68,2555,1787],{"class":74},[68,2557,1790],{"class":196},[68,2559,1050],{"class":74},[68,2561,2562],{"class":1053},"'ga'",[68,2564,1077],{"class":74},[68,2566,2567,2569,2571,2573,2576],{"class":70,"line":112},[68,2568,1787],{"class":74},[68,2570,1804],{"class":196},[68,2572,1050],{"class":74},[68,2574,2575],{"class":1053},"'generates a new asserter project'",[68,2577,1077],{"class":74},[68,2579,2580,2582,2584,2586,2589,2591,2594],{"class":70,"line":148},[68,2581,1787],{"class":74},[68,2583,1818],{"class":196},[68,2585,1050],{"class":74},[68,2587,2588],{"class":1053},"'Generates a new asserter project.",[68,2590,1829],{"class":78},[68,2592,2593],{"class":1053},"Based on a standard dragee asserter template, with mandatory dependancies and a sample rule.'",[68,2595,1077],{"class":74},[68,2597,2598,2600,2603,2605,2608,2610,2613],{"class":70,"line":153},[68,2599,1787],{"class":74},[68,2601,2602],{"class":196},"requiredOption",[68,2604,1050],{"class":74},[68,2606,2607],{"class":1053},"'-n, --name \u003Cstring>'",[68,2609,1023],{"class":74},[68,2611,2612],{"class":1053},"'name of the new asserter project'",[68,2614,1077],{"class":74},[68,2616,2617,2619,2621,2623,2626,2628,2631],{"class":70,"line":166},[68,2618,1787],{"class":74},[68,2620,2602],{"class":196},[68,2622,1050],{"class":74},[68,2624,2625],{"class":1053},"'-od, --output-dir \u003Cstring>'",[68,2627,1023],{"class":74},[68,2629,2630],{"class":1053},"'output dir for the new asserter project'",[68,2632,1077],{"class":74},[68,2634,2635,2637,2639],{"class":70,"line":177},[68,2636,1787],{"class":74},[68,2638,1932],{"class":196},[68,2640,2641],{"class":74},"(generatorHandler);\n",[19,2643,2644,2645,2648,2649,2652,2653,2655],{},"Notez ici que l’on ",[1311,2646,2647],{},"exporte"," la commande ",[1613,2650,2651],{},"generate-asserter",". Cela va nous permettre de la rendre accessible pour ",[1613,2654,1703],{},", et c’est justement ce que nous allons faire à présent.",[582,2657,2659],{"id":2658},"module-parent","Module parent",[19,2661,2662,2663,2665,2666,2668,2669,2672],{},"Nous allons ajouter la dépendance à ",[1613,2664,2485],{}," dans ",[1613,2667,1703],{},". Nous ferons ainsi pour chaque autre module à rattacher à notre CLI, comme ",[1613,2670,2671],{},"dragee-grapher-generator"," par exemple.",[19,2674,2675],{},"Une fois cela fait, nous ajoutons tout simplement ces commandes au CLI central, après import :",[58,2677,2679],{"className":1722,"code":2678,"language":1724,"meta":63,"style":63},"import { generateAsserter } from '@dragee-io\u002Fasserter-generator';\nimport { generateGrapher } from '@dragee-io\u002Fgrapher-generator';\n\n...\nnew Command()\n    .addCommand(generateAsserter)\n    .addCommand(generateGrapher)\n    .addCommand(report)\n    .addCommand(draw)\n    .showHelpAfterError()\n    .showSuggestionAfterError()\n    .parse(process.argv);\n",[65,2680,2681,2695,2709,2713,2717,2725,2734,2743,2751,2759,2767,2775],{"__ignoreMap":63},[68,2682,2683,2685,2688,2690,2693],{"class":70,"line":71},[68,2684,1731],{"class":78},[68,2686,2687],{"class":74}," { generateAsserter } ",[68,2689,765],{"class":78},[68,2691,2692],{"class":1053}," '@dragee-io\u002Fasserter-generator'",[68,2694,1742],{"class":74},[68,2696,2697,2699,2702,2704,2707],{"class":70,"line":86},[68,2698,1731],{"class":78},[68,2700,2701],{"class":74}," { generateGrapher } ",[68,2703,765],{"class":78},[68,2705,2706],{"class":1053}," '@dragee-io\u002Fgrapher-generator'",[68,2708,1742],{"class":74},[68,2710,2711],{"class":70,"line":99},[68,2712,103],{"emptyLinePlaceholder":102},[68,2714,2715],{"class":70,"line":106},[68,2716,2469],{"class":78},[68,2718,2719,2721,2723],{"class":70,"line":112},[68,2720,1960],{"class":78},[68,2722,1775],{"class":196},[68,2724,308],{"class":74},[68,2726,2727,2729,2731],{"class":70,"line":148},[68,2728,1787],{"class":74},[68,2730,1971],{"class":196},[68,2732,2733],{"class":74},"(generateAsserter)\n",[68,2735,2736,2738,2740],{"class":70,"line":153},[68,2737,1787],{"class":74},[68,2739,1971],{"class":196},[68,2741,2742],{"class":74},"(generateGrapher)\n",[68,2744,2745,2747,2749],{"class":70,"line":166},[68,2746,1787],{"class":74},[68,2748,1971],{"class":196},[68,2750,1974],{"class":74},[68,2752,2753,2755,2757],{"class":70,"line":177},[68,2754,1787],{"class":74},[68,2756,1971],{"class":196},[68,2758,1983],{"class":74},[68,2760,2761,2763,2765],{"class":70,"line":182},[68,2762,1787],{"class":74},[68,2764,1990],{"class":196},[68,2766,308],{"class":74},[68,2768,2769,2771,2773],{"class":70,"line":202},[68,2770,1787],{"class":74},[68,2772,1999],{"class":196},[68,2774,308],{"class":74},[68,2776,2777,2779,2781],{"class":70,"line":217},[68,2778,1787],{"class":74},[68,2780,2008],{"class":196},[68,2782,2011],{"class":74},[19,2784,2785],{},"Et voilà !",[58,2787,2789],{"className":1667,"code":2788,"language":1669,"meta":63,"style":63},"> .\u002Fdragee-cli --help\nUsage: index [options] [command]\n\nOptions:\n  -h, --help                      display help for command\n\nCommands:\n  generate-asserter|ga [options]  generates a new asserter project\n  generate-grapher|gg [options]   generates a new grapher project\n  report|r [options]              builds asserters rules report\n  draw|d [options]                builds graphers graphs models\n  help [command]                  display help for command\n",[65,2790,2791,2797,2805,2809,2813,2828,2832,2836,2849,2862,2873,2884],{"__ignoreMap":63},[68,2792,2793,2795],{"class":70,"line":71},[68,2794,2033],{"class":78},[68,2796,2107],{"class":74},[68,2798,2799,2801,2803],{"class":70,"line":86},[68,2800,2112],{"class":92},[68,2802,2115],{"class":1053},[68,2804,2118],{"class":74},[68,2806,2807],{"class":70,"line":99},[68,2808,103],{"emptyLinePlaceholder":102},[68,2810,2811],{"class":70,"line":106},[68,2812,2127],{"class":92},[68,2814,2815,2817,2819,2822,2824,2826],{"class":70,"line":112},[68,2816,2132],{"class":92},[68,2818,2135],{"class":260},[68,2820,2821],{"class":1053},"                      display",[68,2823,2141],{"class":1053},[68,2825,2144],{"class":1053},[68,2827,2147],{"class":1053},[68,2829,2830],{"class":70,"line":148},[68,2831,103],{"emptyLinePlaceholder":102},[68,2833,2834],{"class":70,"line":153},[68,2835,2156],{"class":92},[68,2837,2838,2841,2843,2846],{"class":70,"line":166},[68,2839,2840],{"class":92},"  generate-asserter",[68,2842,2164],{"class":78},[68,2844,2845],{"class":92},"ga",[68,2847,2848],{"class":74}," [options]  generates a new asserter project\n",[68,2850,2851,2854,2856,2859],{"class":70,"line":177},[68,2852,2853],{"class":92},"  generate-grapher",[68,2855,2164],{"class":78},[68,2857,2858],{"class":92},"gg",[68,2860,2861],{"class":74}," [options]   generates a new grapher project\n",[68,2863,2864,2866,2868,2870],{"class":70,"line":182},[68,2865,2161],{"class":92},[68,2867,2164],{"class":78},[68,2869,2167],{"class":92},[68,2871,2872],{"class":74}," [options]              builds asserters rules report\n",[68,2874,2875,2877,2879,2881],{"class":70,"line":202},[68,2876,2175],{"class":92},[68,2878,2164],{"class":78},[68,2880,2180],{"class":92},[68,2882,2883],{"class":74}," [options]                builds graphers graphs models\n",[68,2885,2886,2888,2891,2893],{"class":70,"line":217},[68,2887,2188],{"class":92},[68,2889,2890],{"class":74}," [command]                  display help ",[68,2892,2194],{"class":78},[68,2894,2147],{"class":74},[39,2896,1161],{"id":1160},[19,2898,2899,2900,2904,2905,2908,2909,2914],{},"La CLI comme cœur de notre système va nous permettre d’accroître les capacités de ",[979,2901,1595],{"href":2902,"rel":2903},"http:\u002F\u002Fdragee.io\u002F",[983]," au fur et à mesure. La modularité alliée à Commander.js nous permet de développer, tester et valider indépendamment chaque fonctionnalité. L’intégration des commandes dans la CLI centrale est simple, comme nous l’avons vu dans cet article. Enfin, le runtime de ",[979,2906,1645],{"href":1643,"rel":2907},[983]," nous permet une exécution rapide des commandes (",[979,2910,2913],{"href":2911,"rel":2912},"https:\u002F\u002Fbun.sh\u002Fblog\u002Fbun-v1.0",[983],"démarrage 4 fois plus rapide que Node.js par exemple",").",[19,2916,2917,2918,2923],{},"N’hésitez pas à consulter ",[979,2919,2922],{"href":2920,"rel":2921},"https:\u002F\u002Fblog.hoppr.tech\u002Ftags\u002FDragee.io",[983],"les autres articles en lien avec Dragee.io",", pour découvrir d’autres solutions techniques à des problématiques posées lors de son développement, et voir avec nous ce beau projet grandir.",[1166,2925,2926],{},"html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}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 pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}",{"title":63,"searchDepth":86,"depth":86,"links":2928},[2929,2930,2931,2935],{"id":1625,"depth":86,"text":1626},{"id":1690,"depth":86,"text":1691},{"id":2472,"depth":86,"text":2473,"children":2932},[2933,2934],{"id":2476,"depth":99,"text":2477},{"id":2658,"depth":99,"text":2659},{"id":1160,"depth":86,"text":1161},"2024-11-14T09:03:58.106Z","[Dragee.io](https:\u002F\u002Fgithub.com\u002Fdragee-io) est un nouveau projet lié à l'architecture logicielle, permettant entre autres l'analyse d'une architecture dans la vision [Craft](https:\u002F\u002Fblog.hoppr.tech\u002Ftag",{},"\u002Fblogs\u002F2024-11-14-crer-une-cli-pour-un-projet-modulaire-avec-commanderjs",[2941,2943,2945],{"id":1184,"name":1185,"image":2942,"linkedin":1187,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc88f5dfa-16db-4e6f-acf1-34dd80ee8766\u002Femma_hoppr.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45GO43JXI4%2F20241114%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20241114T090357Z&X-Amz-Expires=3600&X-Amz-Signature=a05938aa617f8ff2268f50065cb89ede0f8055125c3fb418afa4079e6bc62396&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":1195,"name":1196,"image":2944,"linkedin":1198,"x":1199},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc69d0b59-558d-4e48-879f-bea3fec1fdef\u002FLinkedin_Profile.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45GO43JXI4%2F20241114%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20241114T090358Z&X-Amz-Expires=3600&X-Amz-Signature=8cee0f8d165f64e2d0a1bcb832c3252b4c9584435828cd62b42463e76370ed10&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":10,"name":11,"image":2946,"linkedin":13,"x":14},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F02dd23b5-238a-4713-ad54-432f3fa5119b\u002Fecattez_profile.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45GO43JXI4%2F20241114%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20241114T090357Z&X-Amz-Expires=3600&X-Amz-Signature=be9d313f6a07aecbdf9651e0563914178ae2cbcdc1751cfe0b6b1c23e8e8d089&X-Amz-SignedHeaders=host&x-id=GetObject",{"title":1556,"description":2937},"blogs\u002F2024-11-14-crer-une-cli-pour-un-projet-modulaire-avec-commanderjs\u002Findex",[1724,1208,2950,2951],"fixentropy.io","dragee.io","dtDKDCV_WylgtltXZyaGMrtG3ZqITs2ouwO40hZ-jho",{"id":2954,"title":2955,"alt":2956,"authors":2957,"body":2959,"date":3384,"description":3385,"extension":1178,"image":1179,"meta":3386,"navigation":102,"ogImage":1179,"path":3387,"published":102,"reviewers":3388,"seo":3398,"stem":3399,"tags":3400,"__hash__":3402},"blogs\u002Fblogs\u002F2024-12-10-gnration-agrgation-et-dploiement-de-documentation-tsdoc-avec-docusaurus-et-vercel\u002Findex.md","Génération, agrégation et déploiement de documentation TSDoc avec Docusaurus et Vercel","Page de documentation Dragee.io en fond, avec les logos de Docusaurus, Bun et Vercel",[2958],{"id":1190,"name":1191,"image":1560,"linkedin":1193,"x":1188},{"type":16,"value":2960,"toc":3377},[2961,2977,2988,2995,3005,3008,3019,3022,3026,3029,3043,3142,3151,3160,3166,3170,3178,3186,3193,3243,3251,3255,3262,3271,3277,3284,3297,3303,3310,3314,3322,3325,3331,3338,3344,3351,3354,3356,3366,3369,3374],[479,2962,2963],{},[19,2964,1567,2965,1571,2967,1579,2972,1583,2974],{},[1311,2966,1570],{},[1311,2968,2969],{},[979,2970,1578],{"href":1576,"rel":2971},[983],[1581,2973],{},[979,2975,1588],{"href":1586,"rel":2976},[983],[19,2978,2979,2980,2984,2985,393],{},"Suite à l’article ",[979,2981,1556],{"href":2982,"rel":2983},"https:\u002F\u002Fblog.hoppr.tech\u002Fblogs\u002F2024-11-14-crer-une-cli-pour-un-projet-modulaire-avec-commanderjs",[983],", nous avons maintenant une CLI efficace et fiable pour notre projet ",[979,2986,1595],{"href":1593,"rel":2987},[983],[19,2989,2990,2991,2994],{},"Nous pouvons grâce à elle écrire nos ",[1613,2992,2993],{},"asserters"," facilement, et les règles d’architecture qui y sont associées, ce qui nous permettra d’analyser le respect de ces règles par un projet à tester.",[19,2996,2997,2998,3001,3002,3004],{},"Cependant, à l’image de n’importe quel ",[1613,2999,3000],{},"linter",", il nous devient nécessaire d’avoir un descriptif de chaque règle et comment la respecter. De plus, nous souhaiterions avoir un catalogue référentiel des ",[1613,3003,2993],{}," et règles qui existent, en plus d’une documentation générale sur le projet Dragee.",[19,3006,3007],{},"Nous avons besoin que ce catalogue soit :",[23,3009,3010,3013,3016],{},[26,3011,3012],{},"dynamique (1 commit ⇒ 1 déploiement)",[26,3014,3015],{},"facile à paramétrer et personnaliser",[26,3017,3018],{},"s’adapte facilement à des modifications sur n’importe quelle règle. Hors de question de venir modifier un site à la main à chaque nouvelle version de notre asserter DDD !",[19,3020,3021],{},"Creusons alors le sujet.",[39,3023,3025],{"id":3024},"documentation-tsdoctypedoc","Documentation TSDoc\u002FTypeDoc",[19,3027,3028],{},"Quel est le meilleur endroit pour poser de la documentation sur une règle donnée ? Dans le code, en commentaire de la dite règle.",[19,3030,3031,3032,3037,3038,2095],{},"Nous allons nous appuyer sur le standard de documentation ",[979,3033,3036],{"href":3034,"rel":3035},"https:\u002F\u002Ftsdoc.org\u002F",[983],"TSDoc"," pour TypeScript. Voici un exemple, sur ",[979,3039,3042],{"href":3040,"rel":3041},"https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fddd-asserter\u002Fblob\u002Fmain\u002Fsrc\u002Frules\u002Faggregates-allowed-dependencies.rule.ts",[983],"une des règles de notre asserter DDD",[58,3044,3046],{"className":1722,"code":3045,"language":1724,"meta":63,"style":63},"\u002F**\n * **aggregates-allowed-dependencies :**\n * Aggregates can only have dependencies of types \"ddd\u002Fvalue_object\", \"ddd\u002Fentity\" and \"ddd\u002Fevent\"\n *\n * ## Examples\n * Example of incorrect dragees for this rule:\n * ```json\n * {\n *     \"name\": \"AService\",\n *     \"profile\": \"ddd\u002Fservice\"\n * },\n\n...\n\n *\n * @module Aggregates Allowed Dependencies\n *\n *\u002F\n",[65,3047,3048,3053,3058,3063,3068,3073,3078,3083,3088,3093,3098,3103,3107,3111,3115,3119,3133,3137],{"__ignoreMap":63},[68,3049,3050],{"class":70,"line":71},[68,3051,3052],{"class":82},"\u002F**\n",[68,3054,3055],{"class":70,"line":86},[68,3056,3057],{"class":82}," * **aggregates-allowed-dependencies :**\n",[68,3059,3060],{"class":70,"line":99},[68,3061,3062],{"class":82}," * Aggregates can only have dependencies of types \"ddd\u002Fvalue_object\", \"ddd\u002Fentity\" and \"ddd\u002Fevent\"\n",[68,3064,3065],{"class":70,"line":106},[68,3066,3067],{"class":82}," *\n",[68,3069,3070],{"class":70,"line":112},[68,3071,3072],{"class":82}," * ## Examples\n",[68,3074,3075],{"class":70,"line":148},[68,3076,3077],{"class":82}," * Example of incorrect dragees for this rule:\n",[68,3079,3080],{"class":70,"line":153},[68,3081,3082],{"class":82}," * ```json\n",[68,3084,3085],{"class":70,"line":166},[68,3086,3087],{"class":82}," * {\n",[68,3089,3090],{"class":70,"line":177},[68,3091,3092],{"class":82}," *     \"name\": \"AService\",\n",[68,3094,3095],{"class":70,"line":182},[68,3096,3097],{"class":82}," *     \"profile\": \"ddd\u002Fservice\"\n",[68,3099,3100],{"class":70,"line":202},[68,3101,3102],{"class":82}," * },\n",[68,3104,3105],{"class":70,"line":217},[68,3106,103],{"emptyLinePlaceholder":102},[68,3108,3109],{"class":70,"line":223},[68,3110,2469],{"class":82},[68,3112,3113],{"class":70,"line":228},[68,3114,103],{"emptyLinePlaceholder":102},[68,3116,3117],{"class":70,"line":248},[68,3118,3067],{"class":82},[68,3120,3121,3124,3127,3130],{"class":70,"line":266},[68,3122,3123],{"class":82}," * ",[68,3125,3126],{"class":78},"@module",[68,3128,3129],{"class":92}," Aggregates",[68,3131,3132],{"class":82}," Allowed Dependencies\n",[68,3134,3135],{"class":70,"line":280},[68,3136,3067],{"class":82},[68,3138,3139],{"class":70,"line":286},[68,3140,3141],{"class":82}," *\u002F\n",[19,3143,3144,3145,3150],{},"Vous pouvez ici noter que nous sommes partis sur un commentaire écrit en ",[979,3146,3149],{"href":3147,"rel":3148},"https:\u002F\u002Fwww.markdownguide.org\u002Fgetting-started\u002F",[983],"Markdown",", souple, efficace, facilement lisible ici et nous permettant d’utiliser TypeDoc (et Docusaurus) ensuite.",[19,3152,3153,3154,3159],{},"En local dans le projet de l’asserter, nous avons pu installer l’outil ",[979,3155,3158],{"href":3156,"rel":3157},"https:\u002F\u002Ftypedoc.org\u002F",[983],"TypeDoc"," nous permettant de générer du HTML grâce à ces commentaires. Cela nous donne un premier résultat intéressant, mais encore peu souple et surtout uniquement dédié au projet en cours.",[19,3161,3162],{},[1374,3163],{"alt":3164,"src":3165},"Capture d’écran de la documentation d’un asserter, générée en HTML par TypeDoc.","\u002Fcontent-assets\u002F2024-12-10-gnration-agrgation-et-dploiement-de-documentation-tsdoc-avec-docusaurus-et-vercel\u002Fassets\u002Fimg1.webp",[39,3167,3169],{"id":3168},"git-submodules","Git submodules",[19,3171,3172,3173,3177],{},"Nous avons créé un nouveau projet, ",[979,3174,3175],{"href":3175,"rel":3176},"https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fdragee-vercel-doc",[983],", qui sera celui contenant les outils nous permettant de générer le résultat de notre agrégation de documentation. L’objectif est de centraliser ce processus, en impactant le moins possible les projets à documenter.",[19,3179,3180,3181,393],{},"Mais comment faire le lien entre le projet de génération de documentation et les projets à documenter ? Nous avons choisi d’utiliser les ",[979,3182,3185],{"href":3183,"rel":3184},"https:\u002F\u002Fgit-scm.com\u002Fbook\u002Ffr\u002Fv2\u002FUtilitaires-Git-Sous-modules",[983],"sous-modules (submodules) Git",[19,3187,3188,3189,3192],{},"Les ",[1311,3190,3191],{},"git submodules"," sont une fonctionnalité de Git permettant d'intégrer un dépôt Git distinct au sein d’un autre dépôt en tant que sous-dossier.",[58,3194,3196],{"className":1667,"code":3195,"language":1669,"meta":63,"style":63},"# Installation d'un sous-module ddd-asserter dans un dossier du projet\ngit submodule add https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fddd-asserter projects\u002Fddd-asserter\n\n# Mise à jour des sous-modules du projet\ngit submodule update --recursive --remote\n",[65,3197,3198,3203,3219,3223,3228],{"__ignoreMap":63},[68,3199,3200],{"class":70,"line":71},[68,3201,3202],{"class":82},"# Installation d'un sous-module ddd-asserter dans un dossier du projet\n",[68,3204,3205,3208,3211,3213,3216],{"class":70,"line":86},[68,3206,3207],{"class":92},"git",[68,3209,3210],{"class":1053}," submodule",[68,3212,1679],{"class":1053},[68,3214,3215],{"class":1053}," https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fddd-asserter",[68,3217,3218],{"class":1053}," projects\u002Fddd-asserter\n",[68,3220,3221],{"class":70,"line":99},[68,3222,103],{"emptyLinePlaceholder":102},[68,3224,3225],{"class":70,"line":106},[68,3226,3227],{"class":82},"# Mise à jour des sous-modules du projet\n",[68,3229,3230,3232,3234,3237,3240],{"class":70,"line":112},[68,3231,3207],{"class":92},[68,3233,3210],{"class":1053},[68,3235,3236],{"class":1053}," update",[68,3238,3239],{"class":260}," --recursive",[68,3241,3242],{"class":260}," --remote\n",[19,3244,3245,3246,3250],{},"Cette approche est d’ailleurs employée sur l’exemple ",[979,3247,3248],{"href":3248,"rel":3249},"https:\u002F\u002Fgithub.com\u002Ftypedoc2md\u002Fdocusaurus-plugin-typedoc-example",[983],", pour Docusaurus et son plugin TypeDoc, que nous allons maintenant utiliser.",[39,3252,3254],{"id":3253},"docusaurus","Docusaurus",[19,3256,3257,3261],{},[979,3258,3254],{"href":3259,"rel":3260},"https:\u002F\u002Fdocusaurus.io\u002F",[983]," est un framework open-source permettant de créer facilement des sites web statiques de documentation technique. Basé sur React, il permet de gérer du contenu en Markdown, d’intégrer des plugins et des options de personnalisation pour un site propre et optimisé.",[19,3263,3264,3265,3270],{},"Parmi les plugins à disposition, un nous intéresse particulièrement dans notre cas : ",[979,3266,3269],{"href":3267,"rel":3268},"https:\u002F\u002Ftypedoc-plugin-markdown.org\u002Fplugins\u002Fdocusaurus",[983],"docusaurus-plugin-typedoc",". Comme son nom l’indique, ce plugin nous permet d’intégrer TypeDoc dans le cycle de Docusaurus. C’est lui qui portera ainsi la transformation TSDoc → Markdown. Avec la configuration souhaitée, la documentation s’intègrera ainsi automatiquement dans notre site Docusaurus.",[19,3272,3273],{},[1374,3274],{"alt":3275,"src":3276},"Capture d’écran d’une page de documentation de règle d’asserter du site Dragee, généré par Docusarus","\u002Fcontent-assets\u002F2024-12-10-gnration-agrgation-et-dploiement-de-documentation-tsdoc-avec-docusaurus-et-vercel\u002Fassets\u002Fimg2.webp",[19,3278,3279,3280,393],{},"D’autres plugins utiles existent pour améliorer l’expérience, comme par exemple une barre de recherche avec ",[979,3281,3282],{"href":3282,"rel":3283},"https:\u002F\u002Fgithub.com\u002Fcmfcmf\u002Fdocusaurus-search-local",[983],[19,3285,3286,3287,3291,3292,3296],{},"Il est également possible d’ajouter des ressources statiques (pour la première page par exemple), des liens (vers le ",[979,3288,3290],{"href":1593,"rel":3289},[983],"code source de Dragee",", ou les liens ",[979,3293,1607],{"href":3294,"rel":3295},"https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fhopprtech",[983],") et de personnaliser le thème du site.",[19,3298,3299],{},[1374,3300],{"alt":3301,"src":3302},"Capture d’écran de la page d’accueil du site Dragee, généré par Docusaurus","\u002Fcontent-assets\u002F2024-12-10-gnration-agrgation-et-dploiement-de-documentation-tsdoc-avec-docusaurus-et-vercel\u002Fassets\u002Fimg3.webp",[19,3304,3305,3306,3309],{},"Nous avons maintenant un site qui répond à nos attentes, avec une documentation centralisée dans notre projet ",[979,3307,3175],{"href":3175,"rel":3308},[983],". Il ne reste plus qu’à le déployer.",[39,3311,3313],{"id":3312},"vercel","Vercel",[19,3315,3316,3317,3321],{},"Pour présenter brièvement ",[979,3318,3313],{"href":3319,"rel":3320},"https:\u002F\u002Fvercel.com\u002F",[983],", c’est une plateforme optimisée pour déployer rapidement des applications web front-end. Elle offre un déploiement continu et des outils intégrés pour gérer des sites statiques ou dynamiques.",[19,3323,3324],{},"Vercel est un outil que nous connaissons bien chez HoppR, puisque c’est ce qui nous permet par exemple de déployer le blog que vous êtes en train de lire. Compatible avec Docusaurus, et permettant un déploiement couplé à Git, c’est l’outil idéal ici.",[19,3326,3327,3328,393],{},"Reprenons notre projet de documentation  ",[979,3329,3175],{"href":3175,"rel":3330},[983],[19,3332,3333,3334,3337],{},"Après configuration de Vercel, celui-ci va écouter les modifications apportées à la branche ",[1613,3335,3336],{},"main"," du dépôt Git. A chaque nouveau commit sur cette branche, Vercel va automatiquement builder et redéployer la nouvelle version du site.",[19,3339,3340],{},[1374,3341],{"alt":3342,"src":3343},"Capture d’écran du projet dragee-vercel-doc dans Vercel","\u002Fcontent-assets\u002F2024-12-10-gnration-agrgation-et-dploiement-de-documentation-tsdoc-avec-docusaurus-et-vercel\u002Fassets\u002Fimg4.webp",[19,3345,3346,3347],{},"Et voilà le résultat : ",[979,3348,3349],{"href":3349,"rel":3350},"https:\u002F\u002Fdragee-vercel-doc.vercel.app\u002F",[983],[19,3352,3353],{},"A noter que Vercel comprend toute une suite d’outils, permettant notamment l’analyse détaillée du trafic sur le site, une configuration simple, et même des environnements de preview déployés automatiquement pour tester chaque modification.",[39,3355,1161],{"id":1160},[19,3357,3358,3359,3361,3362,3365],{},"Maintenant le projet ",[1311,3360,1595],{}," possède sa page, contenant une présentation succincte du projet, de la documentation et surtout un catalogue dynamique des projets ",[1613,3363,3364],{},"asserters\u002Fgraphers"," utilisables.",[19,3367,3368],{},"L’utilisation couplée de TypeDoc, Docusaurus et Vercel nous permet d’avoir une documentation simple et dynamique. Saisir les informations afférentes à une règle d’utilisation créé la documentation en lien dans notre catalogue de règles en ligne. Pratique !",[19,3370,2917,3371,2923],{},[979,3372,2922],{"href":2920,"rel":3373},[983],[1166,3375,3376],{},"html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}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 pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}",{"title":63,"searchDepth":86,"depth":86,"links":3378},[3379,3380,3381,3382,3383],{"id":3024,"depth":86,"text":3025},{"id":3168,"depth":86,"text":3169},{"id":3253,"depth":86,"text":3254},{"id":3312,"depth":86,"text":3313},{"id":1160,"depth":86,"text":1161},"2024-12-10T14:06:41.238Z","Suite à l’article [Créer une CLI pour un projet modulaire avec Commander.js](https:\u002F\u002Fblog.hoppr.tech\u002Fblogs\u002F2024-11-14-crer-une-cli-pour-un-projet-modulaire-avec-commanderjs), nous avons maintenant une",{},"\u002Fblogs\u002F2024-12-10-gnration-agrgation-et-dploiement-de-documentation-tsdoc-avec-docusaurus-et-vercel",[3389,3391,3396],{"id":1195,"name":1196,"image":3390,"linkedin":1198,"x":1199},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc69d0b59-558d-4e48-879f-bea3fec1fdef\u002FLinkedin_Profile.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45FSPPWI6X%2F20241210%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20241210T140640Z&X-Amz-Expires=3600&X-Amz-Signature=1717ec06fe9281a6c6b23e68d1fd207de01aa4d0cec6dd9b0636ee1190e866e1&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":3392,"name":3393,"image":3394,"linkedin":3395,"x":1188},"70a8663a-742d-4937-a6d4-5cef079b12c8","Théo Lanord","https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F53946b9e-3bb9-45bd-a8b4-429c51156179\u002FT04PC176TGB-U05EW3YF61Z-5e129f612df3-512.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45FSPPWI6X%2F20241210%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20241210T140640Z&X-Amz-Expires=3600&X-Amz-Signature=0872fb5dcb83126c48893f920e3646ae9781de7422ef2c6f6a650328b58a220a&X-Amz-SignedHeaders=host&x-id=GetObject","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fth%C3%A9o-lanord\u002F",{"id":1542,"name":1543,"image":3397,"linkedin":1545,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Ff8f82a79-9d41-4302-b1a5-37882985167f\u002Fnicoz_hoppr.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45FSPPWI6X%2F20241210%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20241210T140641Z&X-Amz-Expires=3600&X-Amz-Signature=db471f714fdcc59e94e41fb243834ba490d9e08146460424a8c63b6361cd4928&X-Amz-SignedHeaders=host&x-id=GetObject",{"title":2955,"description":3385},"blogs\u002F2024-12-10-gnration-agrgation-et-dploiement-de-documentation-tsdoc-avec-docusaurus-et-vercel\u002Findex",[1724,3401,1208,2950,2951],"documentation","d4yFBeCY6STzyff7xYFckttIk9SB5MaSL-wqdtWxygE",{"id":3404,"title":3405,"alt":3406,"authors":3407,"body":3409,"date":4279,"description":4280,"extension":1178,"image":1179,"meta":4281,"navigation":102,"ogImage":1179,"path":4282,"published":102,"reviewers":4283,"seo":4290,"stem":4291,"tags":4292,"__hash__":4293},"blogs\u002Fblogs\u002F2025-01-07-tlchargement-et-gestion-de-packages-npm-avec-bun-et-axios\u002Findex.md","Téléchargement et gestion de packages npm avec Bun et Axios","Image d’illustration représentant un ordinateur téléchargeant et installant, entouré des logos de Bun, npm et Axios",[3408],{"id":1190,"name":1191,"image":1560,"linkedin":1193,"x":1188},{"type":16,"value":3410,"toc":4271},[3411,3427,3453,3459,3468,3474,3478,3494,3506,3512,3522,3536,3540,3543,3559,3574,3710,3713,3728,3738,3742,3748,3751,3770,3783,3786,3824,3832,3876,3890,4130,4133,4144,4148,4151,4197,4205,4211,4215,4229,4237,4247,4249,4257,4263,4268],[479,3412,3413],{},[19,3414,1567,3415,1571,3417,1579,3422,1583,3424],{},[1311,3416,1570],{},[1311,3418,3419],{},[979,3420,1578],{"href":1576,"rel":3421},[983],[1581,3423],{},[979,3425,1588],{"href":1586,"rel":3426},[983],[19,3428,3429,3430,3433,3434,3438,3442,3446,3447,3452],{},"Nouvelle étape pour notre projet ",[979,3431,1595],{"href":1593,"rel":3432},[983],". Nous avons déjà vu ",[979,3435,3437],{"href":2982,"rel":3436},[983],"comment créer ",[979,3439,3441],{"href":2982,"rel":3440},[983],"la ",[979,3443,3445],{"href":2982,"rel":3444},[983],"CLI qui est au cœur du projet",", et également comment ",[979,3448,3451],{"href":3449,"rel":3450},"https:\u002F\u002Fblog.hoppr.tech\u002Fblogs\u002F2024-12-10-gnration-agrgation-et-dploiement-de-documentation-tsdoc-avec-docusaurus-et-vercel",[983],"documenter de manière dynamique nos asserters",", porteurs des règles d’architecture.",[19,3454,3455,3456,3458],{},"C’est un excellent début, mais il manque quand même le point le plus important : maintenant, il s’agit de récupérer et d’installer ces ",[1613,3457,2993],{}," en vue de leur utilisation.",[19,3460,3461,3462,3464,3465,3467],{},"En effet, ",[1311,3463,1595],{}," a pour mission, entre autres, de nous permettre de vérifier le respect de règles d’architecture au sein de notre projet. Pour cela, des ",[1613,3466,2993],{}," liés à la typologie d’architecture vont entrer en jeu.",[19,3469,3470,3471,3473],{},"Ces ",[1613,3472,2993],{}," ne sont pas directement importés dans la CLI de Dragee, ils sont téléchargés à la volée, au besoin. Et nous allons maintenant nous demander comment.",[39,3475,3477],{"id":3476},"anatomie-dun-asserter-et-extraction-du-namespace","Anatomie d’un asserter et extraction du namespace",[19,3479,3480,3481,3483,3484,3487,3488,3490,3491,3493],{},"Pour rappel, un ",[1311,3482,2489],{}," est un module comprenant des règles d’architecture. Il est basé sur un ",[1311,3485,3486],{},"namespace"," correspondant à une typologie d’architecture (DDD, clean, etc.). A chaque ",[1613,3489,3486],{}," correspond un ",[1613,3492,2489],{}," différent.",[19,3495,3496,3497,3501,3502,393],{},"Dans cet article, nous prendrons pour exemple notre tout premier asserter, ",[979,3498,3500],{"href":2496,"rel":3499},[983],"ddd-asserter",", qui comme son nom l’indique comprend un set de règles concernant le ",[979,3503,3505],{"href":1392,"rel":3504},[983],"Domain-Driven Design",[19,3507,3508,3509,3511],{},"A la construction d’un rapport d’architecture, notre CLI va détecter le ",[1613,3510,3486],{}," des dragées, et va automatiquement télécharger l’asserter correspondant auprès de npm. Cet asserter va ensuite être installé en local, et leurs règles jouées sur les dragées à analyser.",[479,3513,3514],{},[19,3515,3516,3517,393],{},"📄 Les règles d’architecture de l’asserter DDD sont disponibles ",[979,3518,3521],{"href":3519,"rel":3520},"https:\u002F\u002Fdragee-vercel-doc.vercel.app\u002Fdocs\u002Fasserters\u002Fddd-asserter\u002F",[983],"sur le site de Dragee",[479,3523,3524],{},[19,3525,3526,3527,3530,3531,393],{},"📌 Nous avons également des ",[1613,3528,3529],{},"graphers"," (modélisateurs d’architecture) qui sont traités de la même manière, par exemple ",[979,3532,3535],{"href":3533,"rel":3534},"https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fddd-grapher",[983],"ddd-grapher",[39,3537,3539],{"id":3538},"téléchargement-de-lasserter","Téléchargement de l’asserter",[19,3541,3542],{},"Le téléchargement d’un asserter va se faire via deux appels à l’API npm :",[23,3544,3545,3553],{},[26,3546,3547,3548],{},"Appel de récupération des ",[979,3549,3552],{"href":3550,"rel":3551},"https:\u002F\u002Fgithub.com\u002Fnpm\u002Fregistry\u002Fblob\u002Fmain\u002Fdocs\u002Fresponses\u002Fpackage-metadata.md",[983],"métadonnées sur le package npm",[26,3554,3555,3556,1395],{},"Appel de téléchargement du package (",[1613,3557,3558],{},"tarball",[19,3560,3561,3562,3567,3568,3570,3571,2095],{},"Pour effectuer ces appels, nous allons utiliser ",[979,3563,3566],{"href":3564,"rel":3565},"https:\u002F\u002Faxios-http.com\u002F",[983],"Axios",", un client HTTP travaillant avec des promesses, très simple à utiliser. Ainsi, cela donne, en simplifiant la construction de l’URL d’appel, pour l’",[1613,3569,2489],{}," ",[979,3572,3500],{"href":2496,"rel":3573},[983],[58,3575,3577],{"className":1722,"code":3576,"language":1724,"meta":63,"style":63},"import axios from 'axios';\n\n\u002F\u002F Appel 1 : Métadonnées packages\nconst projectArchiveUrl = 'https:\u002F\u002Fregistry.npmjs.org\u002F@dragee-io\u002Fddd-asserter\u002Flatest';\nconst downloadInfo = await axios.get(projectArchiveUrl, {\n    headers: { Accept: 'application\u002Fjson' }\n});\n\n\u002F\u002F Appel 2 : Téléchargement package\nconst tarball = downloadInfo.data.dist.tarball;\nconst { data } = await axios.get(tarball, {\n    responseType: 'arraybuffer'\n});\n",[65,3578,3579,3593,3597,3602,3616,3637,3648,3653,3657,3662,3674,3698,3706],{"__ignoreMap":63},[68,3580,3581,3583,3586,3588,3591],{"class":70,"line":71},[68,3582,1731],{"class":78},[68,3584,3585],{"class":74}," axios ",[68,3587,765],{"class":78},[68,3589,3590],{"class":1053}," 'axios'",[68,3592,1742],{"class":74},[68,3594,3595],{"class":70,"line":86},[68,3596,103],{"emptyLinePlaceholder":102},[68,3598,3599],{"class":70,"line":99},[68,3600,3601],{"class":82},"\u002F\u002F Appel 1 : Métadonnées packages\n",[68,3603,3604,3606,3609,3611,3614],{"class":70,"line":106},[68,3605,1765],{"class":78},[68,3607,3608],{"class":260}," projectArchiveUrl",[68,3610,139],{"class":78},[68,3612,3613],{"class":1053}," 'https:\u002F\u002Fregistry.npmjs.org\u002F@dragee-io\u002Fddd-asserter\u002Flatest'",[68,3615,1742],{"class":74},[68,3617,3618,3620,3623,3625,3628,3631,3634],{"class":70,"line":112},[68,3619,1765],{"class":78},[68,3621,3622],{"class":260}," downloadInfo",[68,3624,139],{"class":78},[68,3626,3627],{"class":78}," await",[68,3629,3630],{"class":74}," axios.",[68,3632,3633],{"class":196},"get",[68,3635,3636],{"class":74},"(projectArchiveUrl, {\n",[68,3638,3639,3642,3645],{"class":70,"line":148},[68,3640,3641],{"class":74},"    headers: { Accept: ",[68,3643,3644],{"class":1053},"'application\u002Fjson'",[68,3646,3647],{"class":74}," }\n",[68,3649,3650],{"class":70,"line":153},[68,3651,3652],{"class":74},"});\n",[68,3654,3655],{"class":70,"line":166},[68,3656,103],{"emptyLinePlaceholder":102},[68,3658,3659],{"class":70,"line":177},[68,3660,3661],{"class":82},"\u002F\u002F Appel 2 : Téléchargement package\n",[68,3663,3664,3666,3669,3671],{"class":70,"line":182},[68,3665,1765],{"class":78},[68,3667,3668],{"class":260}," tarball",[68,3670,139],{"class":78},[68,3672,3673],{"class":74}," downloadInfo.data.dist.tarball;\n",[68,3675,3676,3678,3681,3684,3687,3689,3691,3693,3695],{"class":70,"line":202},[68,3677,1765],{"class":78},[68,3679,3680],{"class":74}," { ",[68,3682,3683],{"class":260},"data",[68,3685,3686],{"class":74}," } ",[68,3688,437],{"class":78},[68,3690,3627],{"class":78},[68,3692,3630],{"class":74},[68,3694,3633],{"class":196},[68,3696,3697],{"class":74},"(tarball, {\n",[68,3699,3700,3703],{"class":70,"line":217},[68,3701,3702],{"class":74},"    responseType: ",[68,3704,3705],{"class":1053},"'arraybuffer'\n",[68,3707,3708],{"class":70,"line":223},[68,3709,3652],{"class":74},[19,3711,3712],{},"Les deux appels effectués ici sont :",[23,3714,3715,3722],{},[26,3716,3717,3718],{},"GET ",[979,3719,3720],{"href":3720,"rel":3721},"https:\u002F\u002Fregistry.npmjs.org\u002F@dragee-io\u002Fddd-asserter\u002Flatest",[983],[26,3723,3717,3724],{},[979,3725,3726],{"href":3726,"rel":3727},"https:\u002F\u002Fregistry.npmjs.org\u002F@dragee-io\u002Fddd-asserter\u002F-\u002Fddd-asserter-0.0.2-latest.tgz",[983],[479,3729,3730],{},[19,3731,3732,3733],{},"📄 Pour plus d’informations sur l’API registry de npm : ",[979,3734,3737],{"href":3735,"rel":3736},"https:\u002F\u002Fgithub.com\u002Fnpm\u002Fregistry\u002Fblob\u002Fmain\u002Fdocs\u002FREGISTRY-API.md",[983],"docs\u002FREGISTRY-API.md",[39,3739,3741],{"id":3740},"vérification-de-lintégrité-du-package","Vérification de l’intégrité du package",[19,3743,3744,3745,3747],{},"Nous avons réussi à télécharger nos ",[1613,3746,2993],{}," distants. Mais rien ne nous dit qu’ils n’aient pas été altérés ou corrompus pendant le process. Il est donc temps d’ajouter une étape de sécurité, et de vérifier l’intégrité de nos packages.",[19,3749,3750],{},"Comme nous l’avons vu plus haut, le téléchargement auprès de npm se fait en deux appels : les métadonnées puis le package. La réponse au premier appel contient les informations nécessaires pour effectuer la vérification.",[19,3752,3753,3754,3759,3760,3763,3764,3769],{},"Plus en détail, ",[979,3755,3758],{"href":3756,"rel":3757},"https:\u002F\u002Fblog.npmjs.org\u002Fpost\u002F172999548390\u002Fnew-pgp-machinery",[983],"npm nous conseille"," d’utiliser un champ nommé ",[1613,3761,3762],{},"integrity",", basée sur la ",[979,3765,3768],{"href":3766,"rel":3767},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FSecurity\u002FSubresource_Integrity",[983],"spécification SRI",". Nous allons récupérer cette valeur, une chaîne de caractère encodé en base 64 avec un cryptage SHA512. Exemple avec un de nos asserters :",[58,3771,3773],{"className":1667,"code":3772,"language":1669,"meta":63,"style":63},"sha512-xao4irn1WY822p6XcOEUkWfK0U\u002FukIvvn\u002F3lBLJ\u002FA6+d9RpwemKwuxTz5e6QZ8eOvJm17lul08O4v0DY\u002Fmh+rw==\n",[65,3774,3775],{"__ignoreMap":63},[68,3776,3777,3780],{"class":70,"line":71},[68,3778,3779],{"class":92},"sha512-xao4irn1WY822p6XcOEUkWfK0U\u002FukIvvn\u002F3lBLJ\u002FA6+d9RpwemKwuxTz5e6QZ8eOvJm17lul08O4v0DY\u002Fmh+rw",[68,3781,3782],{"class":1053},"==\n",[19,3784,3785],{},"Recalculons ce hash. Nous pourrions par exemple utiliser cette commande :",[58,3787,3789],{"className":1667,"code":3788,"language":1669,"meta":63,"style":63},"cat [PACKAGE] | openssl dgst -sha512 -binary | openssl base64 -A \n",[65,3790,3791],{"__ignoreMap":63},[68,3792,3793,3796,3799,3801,3804,3807,3810,3813,3816,3818,3821],{"class":70,"line":71},[68,3794,3795],{"class":92},"cat",[68,3797,3798],{"class":74}," [PACKAGE] ",[68,3800,2164],{"class":78},[68,3802,3803],{"class":92}," openssl",[68,3805,3806],{"class":1053}," dgst",[68,3808,3809],{"class":260}," -sha512",[68,3811,3812],{"class":260}," -binary",[68,3814,3815],{"class":78}," |",[68,3817,3803],{"class":92},[68,3819,3820],{"class":1053}," base64",[68,3822,3823],{"class":260}," -A\n",[19,3825,3826,3827,2095],{},"Ou alors, avec l’outil ",[979,3828,3831],{"href":3829,"rel":3830},"https:\u002F\u002Ffr.linux-console.net\u002F?p=15125",[983],"shasum",[58,3833,3835],{"className":1667,"code":3834,"language":1669,"meta":63,"style":63},"shasum -b -a 512 [PACKAGE] | awk '{ print $1 }' | xxd -r -p | base64\n",[65,3836,3837],{"__ignoreMap":63},[68,3838,3839,3841,3844,3847,3850,3852,3854,3857,3860,3862,3865,3868,3871,3873],{"class":70,"line":71},[68,3840,3831],{"class":92},[68,3842,3843],{"class":260}," -b",[68,3845,3846],{"class":260}," -a",[68,3848,3849],{"class":260}," 512",[68,3851,3798],{"class":74},[68,3853,2164],{"class":78},[68,3855,3856],{"class":92}," awk",[68,3858,3859],{"class":1053}," '{ print $1 }'",[68,3861,3815],{"class":78},[68,3863,3864],{"class":92}," xxd",[68,3866,3867],{"class":260}," -r",[68,3869,3870],{"class":260}," -p",[68,3872,3815],{"class":78},[68,3874,3875],{"class":92}," base64\n",[19,3877,3878,3879,3884,3885,2095],{},"Et nous obtenons le même résultat, sous réserve que le package soit valide. Maintenant, en TypeScript dans notre projet ",[979,3880,3883],{"href":3881,"rel":3882},"https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fdragee-package-installer\u002Fblob\u002Fmain\u002Fsrc\u002Fservices\u002Fproject.service.ts",[983],"dragee-package-installer",", et grâce au module ",[979,3886,3889],{"href":3887,"rel":3888},"https:\u002F\u002Fnodejs.org\u002Fapi\u002Fcrypto.html",[983],"crypto de Node",[58,3891,3893],{"className":1722,"code":3892,"language":1724,"meta":63,"style":63},"import { hash } from 'node:crypto';\n\nconst generateChecksumFile = (fileName: string, algorithm: string): string => {\n    const fileData = readFileSync(fileName);\n    return hash(algorithm, fileData, 'base64');\n};\n\nexport const controlPackageIntegrity = (\n    downloadDataIntegrity: string, filePath: string, projectName: string\n) => {\n    const [algorithm, integrity] = downloadDataIntegrity.split('-');\n    const generatedChecksum = generateChecksumFile(filePath, algorithm);\n    if (generatedChecksum !== integrity)\n        throw Error(`Could not verify ${projectName} package integrity`);\n};\n",[65,3894,3895,3909,3913,3954,3970,3986,3991,3995,4009,4037,4047,4078,4092,4106,4126],{"__ignoreMap":63},[68,3896,3897,3899,3902,3904,3907],{"class":70,"line":71},[68,3898,1731],{"class":78},[68,3900,3901],{"class":74}," { hash } ",[68,3903,765],{"class":78},[68,3905,3906],{"class":1053}," 'node:crypto'",[68,3908,1742],{"class":74},[68,3910,3911],{"class":70,"line":86},[68,3912,103],{"emptyLinePlaceholder":102},[68,3914,3915,3917,3920,3922,3925,3928,3931,3934,3936,3939,3941,3943,3945,3947,3949,3952],{"class":70,"line":99},[68,3916,1765],{"class":78},[68,3918,3919],{"class":196}," generateChecksumFile",[68,3921,139],{"class":78},[68,3923,3924],{"class":74}," (",[68,3926,3927],{"class":92},"fileName",[68,3929,3930],{"class":78},":",[68,3932,3933],{"class":260}," string",[68,3935,1023],{"class":74},[68,3937,3938],{"class":92},"algorithm",[68,3940,3930],{"class":78},[68,3942,3933],{"class":260},[68,3944,1395],{"class":74},[68,3946,3930],{"class":78},[68,3948,3933],{"class":260},[68,3950,3951],{"class":78}," =>",[68,3953,96],{"class":74},[68,3955,3956,3959,3962,3964,3967],{"class":70,"line":106},[68,3957,3958],{"class":78},"    const",[68,3960,3961],{"class":260}," fileData",[68,3963,139],{"class":78},[68,3965,3966],{"class":196}," readFileSync",[68,3968,3969],{"class":74},"(fileName);\n",[68,3971,3972,3975,3978,3981,3984],{"class":70,"line":112},[68,3973,3974],{"class":78},"    return",[68,3976,3977],{"class":196}," hash",[68,3979,3980],{"class":74},"(algorithm, fileData, ",[68,3982,3983],{"class":1053},"'base64'",[68,3985,552],{"class":74},[68,3987,3988],{"class":70,"line":148},[68,3989,3990],{"class":74},"};\n",[68,3992,3993],{"class":70,"line":153},[68,3994,103],{"emptyLinePlaceholder":102},[68,3996,3997,3999,4001,4004,4006],{"class":70,"line":166},[68,3998,2532],{"class":78},[68,4000,2535],{"class":78},[68,4002,4003],{"class":196}," controlPackageIntegrity",[68,4005,139],{"class":78},[68,4007,4008],{"class":74}," (\n",[68,4010,4011,4014,4016,4018,4020,4023,4025,4027,4029,4032,4034],{"class":70,"line":177},[68,4012,4013],{"class":92},"    downloadDataIntegrity",[68,4015,3930],{"class":78},[68,4017,3933],{"class":260},[68,4019,1023],{"class":74},[68,4021,4022],{"class":92},"filePath",[68,4024,3930],{"class":78},[68,4026,3933],{"class":260},[68,4028,1023],{"class":74},[68,4030,4031],{"class":92},"projectName",[68,4033,3930],{"class":78},[68,4035,4036],{"class":260}," string\n",[68,4038,4039,4042,4045],{"class":70,"line":182},[68,4040,4041],{"class":74},") ",[68,4043,4044],{"class":78},"=>",[68,4046,96],{"class":74},[68,4048,4049,4051,4054,4056,4058,4060,4063,4065,4068,4071,4073,4076],{"class":70,"line":202},[68,4050,3958],{"class":78},[68,4052,4053],{"class":74}," [",[68,4055,3938],{"class":260},[68,4057,1023],{"class":74},[68,4059,3762],{"class":260},[68,4061,4062],{"class":74},"] ",[68,4064,437],{"class":78},[68,4066,4067],{"class":74}," downloadDataIntegrity.",[68,4069,4070],{"class":196},"split",[68,4072,1050],{"class":74},[68,4074,4075],{"class":1053},"'-'",[68,4077,552],{"class":74},[68,4079,4080,4082,4085,4087,4089],{"class":70,"line":217},[68,4081,3958],{"class":78},[68,4083,4084],{"class":260}," generatedChecksum",[68,4086,139],{"class":78},[68,4088,3919],{"class":196},[68,4090,4091],{"class":74},"(filePath, algorithm);\n",[68,4093,4094,4097,4100,4103],{"class":70,"line":223},[68,4095,4096],{"class":78},"    if",[68,4098,4099],{"class":74}," (generatedChecksum ",[68,4101,4102],{"class":78},"!==",[68,4104,4105],{"class":74}," integrity)\n",[68,4107,4108,4111,4114,4116,4119,4121,4124],{"class":70,"line":228},[68,4109,4110],{"class":78},"        throw",[68,4112,4113],{"class":196}," Error",[68,4115,1050],{"class":74},[68,4117,4118],{"class":1053},"`Could not verify ${",[68,4120,4031],{"class":74},[68,4122,4123],{"class":1053},"} package integrity`",[68,4125,552],{"class":74},[68,4127,4128],{"class":70,"line":248},[68,4129,3990],{"class":74},[19,4131,4132],{},"Nous pouvons ainsi vérifier le checksum et l’intégrité du package téléchargé, avant de l’extraire et de l’installer.",[479,4134,4135],{},[19,4136,4137,4138,4140,4141,4143],{},"📌 Bien entendu, si l’",[1613,4139,2489],{}," est déjà téléchargé et installé, la CLI le détecte et saute toutes ces étapes. Nous parlerons de la mise à jour des ",[1613,4142,2993],{}," installés dans un autre article à paraître.",[39,4145,4147],{"id":4146},"extraction-et-installation","Extraction et installation",[19,4149,4150],{},"Notre package étant maintenant téléchargé et sûr, nous allons pouvoir en extraire l’asserter et l’installer. Pour ce faire, nous allons passer par plusieurs étapes :",[23,4152,4153,4161,4169,4182,4190],{},[26,4154,4155,4156],{},"Lecture du tarball en Buffer grâce à ",[979,4157,4160],{"href":4158,"rel":4159},"https:\u002F\u002Fnodejs.org\u002Fapi\u002Ffs.html#fsreadfilesyncpath-options",[983],"Node:fs",[26,4162,4163,4164,393],{},"Extraction du package avec  ",[979,4165,4168],{"href":4166,"rel":4167},"https:\u002F\u002Fgithub.com\u002Fisaacs\u002Fnode-tar",[983],"node-tar",[26,4170,4171,4172,4177,4178,4181],{},"Écriture des fichiers grâce à l’",[979,4173,4176],{"href":4174,"rel":4175},"https:\u002F\u002Fbun.sh\u002Fguides\u002Fwrite-file\u002Fbasic",[983],"API Bun"," dans un dossier ",[1613,4179,4180],{},"registry"," en local",[26,4183,4184,4185],{},"Installation des dépendances de l’asserter avec ",[979,4186,4189],{"href":4187,"rel":4188},"https:\u002F\u002Fbun.sh\u002Fdocs\u002Fcli\u002Finstall",[983],"Bun install",[26,4191,4192,4193],{},"Suppression du tarball via ",[979,4194,4160],{"href":4195,"rel":4196},"https:\u002F\u002Fnodejs.org\u002Fapi\u002Ffs.html#fsunlinkpath-callback",[983],[19,4198,4199,4200,4204],{},"Il n’y a maintenant plus qu’à importer l’asserter installé. C’est ici que le travail de notre projet ",[979,4201,3883],{"href":4202,"rel":4203},"https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fdragee-package-installer",[983]," se termine, en renvoyant l’asserter importé à la CLI.",[19,4206,4207],{},[1374,4208],{"alt":4209,"src":4210},"Process des asserters dans Dragee.io, composé des différentes étapes : détection namespace, téléchargement, validation de l’intégrité, extraction, installation, import et utilisation","\u002Fcontent-assets\u002F2025-01-07-tlchargement-et-gestion-de-packages-npm-avec-bun-et-axios\u002Fassets\u002Fimg1.webp",[39,4212,4214],{"id":4213},"utilisation-des-assertersgraphers","Utilisation des asserters\u002Fgraphers",[19,4216,3188,4217,4219,4220,4225,4226,4228],{},[1613,4218,2993],{}," téléchargés et installés, ils sont donc importés dynamiquement dans notre CLI. Ces modules reposent tous sur la même structure, grâce au package ",[979,4221,4224],{"href":4222,"rel":4223},"https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fdragee-model",[983],"dragee-model",". Celui-ci contient toutes les définitions de type des ",[1613,4227,2993],{},", règles, dragées, etc.",[19,4230,4231,4232,393],{},"Une fonction, elle aussi importée de ce package, va nous permettre de traiter les asserters de manière générique : la bien nommée ",[979,4233,4236],{"href":4234,"rel":4235},"https:\u002F\u002Fgithub.com\u002Fdragee-io\u002Fdragee-model\u002Fblob\u002Fmain\u002Fasserter\u002Findex.ts#L117",[983],"asserterHandler",[19,4238,4239,4240,4243,4244,393],{},"C’est l’avantage de ce fonctionnement se basant sur des types et fonctions mises en commun par ",[979,4241,4224],{"href":4222,"rel":4242},[983],". La CLI peut ainsi utiliser n’importe quel asserter validé, demandé par les dragées et répondant aux pré-requis du type ",[1613,4245,4246],{},"Asserter",[39,4248,1161],{"id":1160},[19,4250,4251,4252,4254,4255,393],{},"Notre projet ",[1311,4253,1595],{}," continue de grandir. Correctement documenté et construit, il est à présent parfaitement capable, grâce à sa CLI et son package idoine, de télécharger, installer et utiliser nos ",[1613,4256,2993],{},[19,4258,4259,4260,4262],{},"Le process mis en place nous permet d’effectuer toutes ces actions, de manière souple et sécurisée. Et nul besoin de préciser quel ",[1613,4261,2489],{}," utiliser, la CLI se charge de le détecter. Toutes ces actions sont effectuées de manière invisible pour les utilisateurs. Ainsi, cette analyse est extrêmement simple à mettre en place pour les dragées issues d’un projet.",[19,4264,2917,4265,2923],{},[979,4266,2922],{"href":2920,"rel":4267},[983],[1166,4269,4270],{},"html pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}html pre.shiki code .sH3jZ, html code.shiki .sH3jZ{--shiki-default:#8B949E}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}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 pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}",{"title":63,"searchDepth":86,"depth":86,"links":4272},[4273,4274,4275,4276,4277,4278],{"id":3476,"depth":86,"text":3477},{"id":3538,"depth":86,"text":3539},{"id":3740,"depth":86,"text":3741},{"id":4146,"depth":86,"text":4147},{"id":4213,"depth":86,"text":4214},{"id":1160,"depth":86,"text":1161},"2025-01-07T08:45:19.283Z","Nouvelle étape pour notre projet [Dragee.io](https:\u002F\u002Fgithub.com\u002Fdragee-io). Nous avons déjà vu [comment créer ](https:\u002F\u002Fblog.hoppr.tech\u002Fblogs\u002F2024-11-14-crer-une-cli-pour-un-projet-modulaire-avec-comm",{},"\u002Fblogs\u002F2025-01-07-tlchargement-et-gestion-de-packages-npm-avec-bun-et-axios",[4284,4286],{"id":3392,"name":3393,"image":4285,"linkedin":3395,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F53946b9e-3bb9-45bd-a8b4-429c51156179\u002FT04PC176TGB-U05EW3YF61Z-5e129f612df3-512.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45FSPPWI6X%2F20250107%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250107T084519Z&X-Amz-Expires=3600&X-Amz-Signature=4b8e375ee06970c61e404297e625978693bb7ea11b1b0eda1ef1896feea745d0&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":4287,"name":4288,"image":1188,"linkedin":4289,"x":1188},"174f4462-cd38-8061-bc88-f29602fcef5d","Guillaume Ferlin","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fguillaume-ferlin-262681272\u002F",{"title":3405,"description":4280},"blogs\u002F2025-01-07-tlchargement-et-gestion-de-packages-npm-avec-bun-et-axios\u002Findex",[1724,1208,2950,2951],"8epbcjTyiwgVmkdOrTiArtR_Gy4GXaZaQQrfFVUHRgo",{"id":4295,"title":4296,"alt":4297,"authors":4298,"body":4307,"date":4497,"description":4498,"extension":1178,"image":1179,"meta":4499,"navigation":102,"ogImage":1179,"path":4500,"published":102,"reviewers":4501,"seo":4511,"stem":4512,"tags":4513,"__hash__":4515},"blogs\u002Fblogs\u002F2025-01-28-interview-chti-tremplin\u002Findex.md","Interview Ch’ti Tremplin","Elisa et Edouard, les 2 interviewés pour le tremplin 2024",[4299,4301,4302],{"id":1542,"name":1543,"image":4300,"linkedin":1545,"x":1188},".\u002Fassets\u002Fauthor-nicolas-zago.webp",{"id":10,"name":11,"image":12,"linkedin":13,"x":14},{"id":4303,"name":4304,"image":4305,"linkedin":4306,"x":1188},"f09c2e62-135b-40c0-a141-b239e8e1e761","Elisa Degobert",".\u002Fassets\u002Fauthor-elisa-degobert.webp","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fdegobert\u002F",{"type":16,"value":4308,"toc":4482},[4309,4312,4321,4327,4331,4337,4340,4346,4349,4352,4358,4361,4364,4367,4373,4376,4379,4382,4385,4391,4394,4397,4403,4407,4413,4416,4422,4429,4432,4438,4441,4447,4450,4453,4459,4462,4473],[19,4310,4311],{},"Participer à une conférence en tant que speaker peut sembler intimidant, mais c’est aussi une expérience incroyablement enrichissante. C’est précisément pour aider celles et ceux qui souhaitent franchir ce cap et se lancer que le Ch’ti Tremplin a vu le jour : un programme qui accompagne six participants pendant plusieurs semaines pour les aider à affiner leur sujet, structurer leur talk, créer un support percutant et se préparer à parler devant un public.",[19,4313,4314,4315,4320],{},"A la précédente édition, 2 HoppRs ont participé au tremplin :  Elisa en tant que coachée pour son premier sujet \"",[979,4316,4319],{"href":4317,"rel":4318},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=8EN_hqU5zPU",[983],"À la découverte de Dependency Track","\" et Edouard en tant que coach.\nPetite interview pour vous partager leur expériences, les coulisses de cette expérience, les défis qu’ils ont relevés et les enseignements qu’ils en ont tirés 😄",[19,4322,4323],{},[1374,4324],{"alt":4325,"src":4326},"Présentation des interviewés Elisa Degobert, Software Crafteuse  chez HoppR et Edouard Cattez Head of Craft chez HoppR","\u002Fcontent-assets\u002F2025-01-28-interview-chti-tremplin\u002Fassets\u002Fimg1.webp",[39,4328,4330],{"id":4329},"retours-de-elisa-sur-sa-participation-au-tremplin","Retours de Elisa sur sa participation au Tremplin",[582,4332,4334],{"id":4333},"elisa-quest-ce-qui-ta-donné-envie-de-participer-au-chti-tremplin-pourquoi-as-tu-choisi-de-parler-de-dependency-track",[1311,4335,4336],{},"Elisa : Qu'est-ce qui t’a donné envie de participer au Ch’ti Tremplin ? Pourquoi as-tu choisi de parler de \"Dependency Track\" ?",[19,4338,4339],{},"Depuis que je participe à des conférences tech comme Cloud Nord ou le Devfest, j’ai toujours été admirative des speakers. Leur capacité à captiver une salle, à transmettre leur passion et à rendre des sujets complexes accessibles me fascinait. Mais honnêtement, je me disais que je ne serais jamais capable de faire la même chose. Monter sur scène et parler devant une audience me semblait hors de portée, presque inatteignable.",[19,4341,4342,4343],{},"Quand on m’a présenté le Ch’ti Tremplin et qu’on m’a encouragée à tenter l’aventure, j’ai d’abord hésité. J’avais peur de ne pas être à la hauteur. Mais l’idée que ce concours offrait un cadre bienveillant et un accompagnement m’a rassurée. C’était une chance unique de sortir de ma zone de confort, tout en me sentant soutenue. Alors, je me suis dit : ",[1613,4344,4345],{},"\"Pourquoi pas moi ? Si je ne tente pas maintenant, je ne le ferai jamais.\"",[19,4347,4348],{},"Le choix de parler de Dependency-Track n’a pas été un hasard. J’ai eu la chance de travailler sur la mise en place de cet outil au sein de la forge de développement de Worldline, où il a révolutionné notre manière de gérer la sécurité. Grâce à Dependency-Track, nous avons pu analyser plus de 3 500 applications, détecter des vulnérabilités et prioriser efficacement leur résolution. Ce sujet me tenait particulièrement à cœur, car je sais à quel point la sécurité peut être perçue comme un casse-tête pour les développeurs. Avec cet outil, j’ai vu qu’il était possible de transformer une contrainte en une opportunité d’amélioration, sans friction excessive.",[19,4350,4351],{},"J’ai aussi choisi Dependency-Track pour une raison très pratique : je connaissais bien le sujet ! C’était un projet auquel j’avais directement contribué, ce qui me permettait d’en parler avec naturel, sans avoir à réciter un texte appris par cœur. Je savais que si j’avais le trac sur scène, je pourrais improviser plus facilement, en me basant sur mes propres expériences.",[582,4353,4355],{"id":4354},"elisa-comment-as-tu-vécu-les-semaines-de-coaching-quest-ce-qui-a-été-le-plus-marquant-ou-le-plus-challengeant-pour-toi-dans-cette-préparation",[1311,4356,4357],{},"Elisa : Comment as-tu vécu les semaines de coaching ? Qu’est-ce qui a été le plus marquant ou le plus challengeant pour toi dans cette préparation ?",[19,4359,4360],{},"J’ai découvert un monde que je ne connaissais pas : celui de la préparation d’un talk. On ne se rend pas compte du travail qu’il faut pour structurer un message, capter l’attention d’un public et transmettre des idées de manière claire et impactante.\nLes semaines de coaching ont été une expérience à la fois enrichissante et exigeante, elles ont été rythmées par les répétitions en groupes restreints pour avoir des retours constructifs et enrichir ma présentation.",[19,4362,4363],{},"En parallèle, mon coach m’a guidé avec des conseils précieux et des anecdotes issues de son expérience en tant que speaker. Toute cette organisation m’a beaucoup aidé à garder le cap et à prendre confiance en moi pour le jour J.",[19,4365,4366],{},"Le plus challengeant pour moi a été d’adopter un ton plus narratif dans ma présentation. La dernière chose que je souhaitais c’était de présenter mon sujet de manière trop scolaire et ennuyeuse. Parler en public, ce n’est pas juste transmettre des informations, c’est aussi raconter une histoire et donner des idées de réflexion pour que l’audience réagisse et en tire quelque chose. Mon coach m’a aidé à trouver un fil rouge auquel je pourrais aussi me rattacher pendant ma présentation.",[582,4368,4370],{"id":4369},"elisa-quest-ce-que-cette-expérience-ta-apporté-que-ce-soit-sur-le-plan-personnel-ou-professionnel-y-a-t-il-quelque-chose-qui-ta-surprise-dans-ton-propre-parcours",[1311,4371,4372],{},"Elisa : Qu’est-ce que cette expérience t’a apporté, que ce soit sur le plan personnel ou professionnel ? Y a-t-il quelque chose qui t’a surprise dans ton propre parcours ?",[19,4374,4375],{},"Participer au Ch’ti Tremplin m’a fait grandir, tant sur le plan personnel que professionnel.",[19,4377,4378],{},"Sur le plan personnel, j’ai gagné en confiance en moi. Je me suis prouvée que je pouvais non seulement partager mes idées devant un public, mais aussi captiver et intéresser des gens avec un sujet qui me tient à cœur.",[19,4380,4381],{},"Sur le plan professionnel, cette expérience a renforcé ma capacité à vulgariser des sujets techniques. Cela m’a aussi ouvert de nouvelles perspectives : aujourd’hui, je me sens plus à l’aise pour participer à d’autres conférences.",[19,4383,4384],{},"Enfin, cette aventure m’a permis de rencontrer des personnes incroyables, toutes animées par l’envie de partager leurs connaissances.",[582,4386,4388],{"id":4387},"elisa-si-tu-devais-donner-un-conseil-à-quelquun-qui-hésite-à-se-lancer-dans-une-aventure-comme-le-chti-tremplin-que-lui-dirais-tu",[1311,4389,4390],{},"Elisa : Si tu devais donner un conseil à quelqu’un qui hésite à se lancer dans une aventure comme le Ch’ti Tremplin, que lui dirais-tu ?",[19,4392,4393],{},"Si vous hésitez à participer à une aventure comme le Ch’ti Tremplin, je n’ai qu’un conseil : foncez ! Oui, c’est un défi. Oui, cela demande du travail. Mais les bénéfices que vous en tirerez en valent largement la peine.",[19,4395,4396],{},"Vous n’avez pas besoin d’être parfait⋅e ou \"expert⋅e\" pour vous lancer. Ce qui compte, c’est d’avoir envie de partager quelque chose qui vous tient à cœur. Et surtout, vous ne serez pas seul⋅e. Le cadre bienveillant et l’accompagnement proposé vous aideront à progresser à chaque étape. Le plus dur, c’est de faire le premier pas et d’oser.",[19,4398,4399],{},[1374,4400],{"alt":4401,"src":4402},"Elisa, le jour J sur scène à la conférence Cloud Nord","\u002Fcontent-assets\u002F2025-01-28-interview-chti-tremplin\u002Fassets\u002Fimg2.webp",[39,4404,4406],{"id":4405},"retour-dedouard-sur-son-rôle-de-coach","Retour d’Edouard sur son rôle de coach",[582,4408,4410],{"id":4409},"edouard-quest-ce-qui-ta-motivé-à-devenir-coach-pour-le-chti-tremplin-et-quas-tu-pensé-du-sujet-delisa-dependency-track",[1311,4411,4412],{},"Edouard : Qu’est-ce qui t’a motivé à devenir coach pour le Ch’ti Tremplin ? Et qu’as-tu pensé du sujet d’Elisa, \"Dependency Track\" ?",[19,4414,4415],{},"J’aime beaucoup accompagner les gens dans la prise de parole car c’est un domaine que j’affectionne particulièrement. Le sujet d’Elisa était l’occasion de remettre en perspective le travail du développeur vis à vis de son outillage. C’est une passerelle parfaite entre le monde du développement et le monde du cloud & devops.",[582,4417,4419],{"id":4418},"edouard-comment-as-tu-accompagné-elisa-dans-la-structuration-de-son-talk-quels-ont-été-selon-toi-les-plus-grands-progrès-quelle-a-réalisés-pendant-ces-semaines",[1311,4420,4421],{},"Edouard : Comment as-tu accompagné Elisa dans la structuration de son talk ? Quels ont été, selon toi, les plus grands progrès qu’elle a réalisés pendant ces semaines ?",[19,4423,4424,4425,4428],{},"En réalité, Elisa n’a eu aucune difficulté à définir un plan. Ce qui lui manquait surtout, c’était l’",[1613,4426,4427],{},"histoire",", ce fil conducteur qui permet à l’audience de comprendre le déroulé sans effort et de s’ancrer dans une situation qui peut lui arriver.",[19,4430,4431],{},"L’effort était à mettre sur sa démo live, qui d’après moi était plus importante que ses slides.",[582,4433,4435],{"id":4434},"edouard-quest-ce-que-cette-expérience-ta-apporté-en-tant-que-coach-as-tu-découvert-ou-renforcé-certaines-compétences",[1311,4436,4437],{},"Edouard : Qu’est-ce que cette expérience t’a apporté en tant que coach ? As-tu découvert ou renforcé certaines compétences ?",[19,4439,4440],{},"Je dirais que cette expérience va dans la continuité de mon rôle de Head of Craft chez HoppR. J’ai pu transmettre mes connaissances et mes expériences en toute bienveillance et sans chercher à être le coach “héros” que l’on peut voir dans certains films.",[582,4442,4444],{"id":4443},"edouard-quel-conseil-donnerais-tu-à-quelquun-qui-souhaiterait-devenir-coach-pour-une-aventure-comme-le-chti-tremplin",[1311,4445,4446],{},"Edouard : Quel conseil donnerais-tu à quelqu’un qui souhaiterait devenir coach pour une aventure comme le Ch’ti Tremplin ?",[19,4448,4449],{},"Pourquoi la personne souhaite elle devenir speaker ? Qu’est-ce qui la satisferait dans l’idée de monter sur scène et de présenter son sujet ?",[19,4451,4452],{},"C’est pour moi des questions fondamentales que doit se poser un speaker, et donc son coach. Grâce à ça, le coach peut accompagner le coaché dans le format de la présentation ainsi que son contenu.",[39,4454,4456],{"id":4455},"alors-quattends-tu-pour-te-lancer",[1311,4457,4458],{},"Alors, qu’attends-tu pour te lancer ?",[19,4460,4461],{},"Le Ch’ti Tremplin est une opportunité unique de te préparer à devenir speaker, dans un cadre bienveillant et avec l’accompagnement personnalisé d’un coach dédié. Que tu sois passionné⋅e par le développement logiciel, le DevOps ou le Cloud, ou que tu souhaites simplement partager une idée qui te tient à cœur, ce programme est fait pour toi.",[19,4463,4464,4465,4468,4469,4472],{},"En plus, lors de la soirée finale, le public choisira deux talks qui seront sélectionnés pour le programme de ",[1311,4466,4467],{},"DevLille"," les 12 et 13 juin, et un talk qui rejoindra celui de ",[1311,4470,4471],{},"Cloud Nord"," en octobre à Euratech. Une belle manière de débuter sur scène devant une audience tech !",[19,4474,4475,4476,4481],{},"Ne réfléchis pas trop longtemps : ",[979,4477,4480],{"href":4478,"rel":4479},"https:\u002F\u002Fforms.gle\u002Fpvgu9tSFGSkumY3B7",[983],"Les candidatures sont ouvertes jusqu’au 15 février",", alors fonce ! 😉",{"title":63,"searchDepth":86,"depth":86,"links":4483},[4484,4490,4496],{"id":4329,"depth":86,"text":4330,"children":4485},[4486,4487,4488,4489],{"id":4333,"depth":99,"text":4336},{"id":4354,"depth":99,"text":4357},{"id":4369,"depth":99,"text":4372},{"id":4387,"depth":99,"text":4390},{"id":4405,"depth":86,"text":4406,"children":4491},[4492,4493,4494,4495],{"id":4409,"depth":99,"text":4412},{"id":4418,"depth":99,"text":4421},{"id":4434,"depth":99,"text":4437},{"id":4443,"depth":99,"text":4446},{"id":4455,"depth":86,"text":4458},"2025-01-28T07:40:21.432Z","Participer à une conférence en tant que speaker peut sembler intimidant, mais c’est aussi une expérience incroyablement enrichissante. C’est précisément pour aider celles et ceux qui souhaitent franch",{},"\u002Fblogs\u002F2025-01-28-interview-chti-tremplin",[4502,4504,4506,4508],{"id":1195,"name":1196,"image":4503,"linkedin":1198,"x":1199},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc69d0b59-558d-4e48-879f-bea3fec1fdef\u002FLinkedin_Profile.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45FSPPWI6X%2F20250128%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250128T074021Z&X-Amz-Expires=3600&X-Amz-Signature=c132405c84ba034764ac18052c695cb2b5c280474efac32900cd06e40264f342&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":4287,"name":4288,"image":4505,"linkedin":4289,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F27c18bae-6c33-403c-b7fd-7d46ce96c376\u002FGuillaume_Ferlin_Image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45FSPPWI6X%2F20250128%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250128T074021Z&X-Amz-Expires=3600&X-Amz-Signature=6e0534932218a0bda9f5bdb521380dd6f3927faacdb5c7f73423dc1ec546c3e3&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":3392,"name":3393,"image":4507,"linkedin":3395,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F53946b9e-3bb9-45bd-a8b4-429c51156179\u002FT04PC176TGB-U05EW3YF61Z-5e129f612df3-512.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45FSPPWI6X%2F20250128%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250128T074021Z&X-Amz-Expires=3600&X-Amz-Signature=1e352029ec982bf3ae50ae0825535c2478ee301db1bb24cb8aefbda7eddcc66c&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":4509,"name":4510,"image":1188,"linkedin":1188,"x":1188},"188f4462-cd38-80d5-b9e6-ec28a94d11e5","Bastien Dufour",{"title":4296,"description":4498},"blogs\u002F2025-01-28-interview-chti-tremplin\u002Findex",[4514,1208],"événement","6ngSjFnQS_iV5MB8euHxF97OP8U3EoUDXAx3SWTWF5s",{"id":4517,"title":4518,"alt":4519,"authors":4520,"body":4522,"date":4922,"description":4923,"extension":1178,"image":1179,"meta":4924,"navigation":102,"ogImage":1179,"path":4925,"published":102,"reviewers":4926,"seo":4933,"stem":4934,"tags":4935,"__hash__":4937},"blogs\u002Fblogs\u002F2025-04-24-lyon-craft-2025-12\u002Findex.md","Lyon Craft 2025 1\u002F2","Photo de l’écran d’accueil au Lyon Craft 2025, présentant la liste des sessions prévues ainsi que le plan des lieux.",[4521],{"id":1190,"name":1191,"image":1560,"linkedin":1193,"x":1188},{"type":16,"value":4523,"toc":4915},[4524,4530,4543,4546,4550,4555,4562,4565,4568,4571,4597,4600,4617,4628,4635,4641,4644,4647,4658,4664,4668,4673,4679,4682,4685,4693,4701,4711,4717,4725,4728,4737,4740,4773,4776,4787,4790,4803,4809,4816,4821,4827,4840,4843,4851,4854,4857,4896,4899,4905,4909,4912],[19,4525,4526,4527],{},"Retour sur la conférence Lyon Craft 2025 qui s’est déroulée le lundi 14 Avril 2025. Au programme, comme l’indique la conférence elle-même : “",[1311,4528,4529],{},"Des passionné·e·s, des ateliers, des présentations et… c’est déjà pas mal !”",[19,4531,4532,4533,4538,4539,4542],{},"Une journée sur le thème du ",[979,4534,4537],{"href":4535,"rel":4536},"https:\u002F\u002Fmanifesto.softwarecraftsmanship.org\u002F",[983],"Software Craftsmanship"," donc, dans l’optique d’apprendre ou de se perfectionner dans ces thématiques avec des experts locaux. Après une séquence d’introduction et de remerciement des sponsors (dont ",[979,4540,1607],{"href":1605,"rel":4541},[983]," qui était VIP), une vingtaine de sessions, couvrant talks et workshops (et même une table ronde) étaient proposées.",[19,4544,4545],{},"Je vous propose de revenir avec moi sur les sessions auxquelles j’ai assisté, en commençant dans cet article par celles du matin.",[39,4547,4549],{"id":4548},"ia-générative-tdd-et-architecture-hexagonale-une-synergie-révolutionnaire","IA Générative, TDD et Architecture Hexagonale : Une Synergie Révolutionnaire ?",[19,4551,4552],{},[1613,4553,4554],{},"Par Florine CHEVRIER et Clément VIRIEUX",[19,4556,4557],{},[979,4558,4561],{"href":4559,"rel":4560},"https:\u002F\u002Flyon-craft.fr\u002Fsessions\u002Fia-generative-et-architecture-hexagonale-une-synergie-revolutionnaire.html",[983],"Lien vers la conférence",[19,4563,4564],{},"Pour bien démarrer la journée, Florine et Clément nous proposent de réaliser un kata avec l’IA. Nous sommes donc directement plongés dans les sujets en vogue du moment.",[19,4566,4567],{},"L’idée ici : faire du TDD avec une IA en lui fournissant les éléments nécessaires pour développer une nouvelle fonctionnalité dans un projet front. Le contexte : nous avons une application bancaire très simple, et nous souhaitons mettre en place une fonctionnalité “nouveau virement” avec un bouton dédié.",[19,4569,4570],{},"Pour ce faire, trois outils seront utilisés ici :",[23,4572,4573,4581,4589],{},[26,4574,4575,4580],{},[979,4576,4579],{"href":4577,"rel":4578},"https:\u002F\u002Fgithub.com\u002Fcline\u002Fcline",[983],"Cline",", un agent IA qui vient s’intégrer à Visual Studio Code",[26,4582,4583,4588],{},[979,4584,4587],{"href":4585,"rel":4586},"https:\u002F\u002Fwww.anthropic.com\u002Fclaude\u002Fsonnet",[983],"Claude 3.7 Sonnet ",", l’IA spécialisée dans le codage derrière Cline",[26,4590,4591,4596],{},[979,4592,4595],{"href":4593,"rel":4594},"https:\u002F\u002Fopenrouter.ai\u002F",[983],"Open Router",", pour pouvoir switcher d’IA et être à l’état de l’art ou contrôler les coûts",[19,4598,4599],{},"À présent, comment communiquer avec cette IA ? Pour développer, elle va se baser sur un prompt indiquant :",[23,4601,4602,4611,4614],{},[26,4603,4604,4605,4610],{},"Des contraintes d’architecture, sous forme de tests. Nous souhaitons ici obliger l’IA à suivre une ",[979,4606,4609],{"href":4607,"rel":4608},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FArchitecture_hexagonale",[983],"architecture hexagonale"," déjà en place",[26,4612,4613],{},"Une capture d’écran d’une maquette Figma faite par un UX\u002FUI",[26,4615,4616],{},"Des tests Cypress, pour définir les règles de gestion fonctionnelles, et qui seront à la base de notre TDD. À noter que ces tests auraient été rédigés également pour un développement par un humain",[19,4618,4619,4620,4623,4624,4627],{},"Cline va ainsi d’abord passer une première phase ",[1311,4621,4622],{},"PLAN",", pour indiquer ce qu’elle souhaite mettre en place, ainsi qu’une estimation des coûts de l’API. C’est une sorte de dry run si l’on veut. Ensuite, il sera temps de passer à la seconde phase ",[1311,4625,4626],{},"ACT",", où l’IA va réellement coder pour nous.",[19,4629,4630,4631,4634],{},"À noter que Cline peut apprendre de ses erreurs. De nouvelles règles sont ajoutées au fur et à mesure dans un fichier ",[1613,4632,4633],{},".clinerules",", un fichier texte lisible également par l’humain. Ici, ce sont les développeurs qui demandent à Cline de les ajouter quand ils sont confrontés à une erreur de l’IA.",[19,4636,4637],{},[1374,4638],{"alt":4639,"src":4640},"Cline sur le Marketplace Visual Studio Code","\u002Fcontent-assets\u002F2025-04-24-lyon-craft-2025-12\u002Fassets\u002Fimg1.webp",[19,4642,4643],{},"Revenons à notre démonstration. Nous sommes ici plus sûr du test-first que sur du TDD en réalité, mais l’IA traite le prompt. Surprise (effet démo), Cline oublie lors de la présentation d’ajouter le bouton alors qu’il a développé la fonctionnalité. Les tests Cypress ne passent pas. Au deuxième essai, plus de problème, l’IA se base sur ce qu’elle peut lire dans le terminal, et est satisfaite de son travail : les tests Cypress passent. Effectivement, le bouton et la fonctionnalité sont présents et correspondent à l’attendu.(",[19,4645,4646],{},"Maintenant, Florine et Clément demandent à l’IA de développer la suite de la fonctionnalité : le formulaire du virement. Même principe : tests Cypress correspondants + capture d’écran en maquette. Les fonctionnalités sont présentes et tout fonctionne, le respect de la maquette est par contre très approximative, une problématique a priori très présente avec ce genre d’outils.",[19,4648,4649,4650,4653,4654,4657],{},"C’est d’ailleurs un bon point de ce talk, nos deux speakers restent critiques par rapport aux outils d’IA qu’ils utilisent. La démonstration est bluffante de rapidité, l’API de l’IA n’a coûté que 1,5$ pour cette démo. Mais le code, bien que répondant aux critères évoqués, n’est pas à la qualité d’un bon développeur humain. D’ailleurs, au quotidien, l’équipe laissent à Cline les droits de lecture, mais l’écriture passe par une validation manuelle. Et il y a d’autres subtilités à connaître qui viennent avec l’expérience de l’outil (",[1613,4651,4652],{},"clinerules",", mauvaise gestion des erreurs, erreurs de compréhension de la maquette, gestion de la ",[1613,4655,4656],{},"context window",", etc.).",[19,4659,4660],{},[1374,4661],{"alt":4662,"src":4663},"Florine explique les informations que Clément est en train de donner à l’IA","\u002Fcontent-assets\u002F2025-04-24-lyon-craft-2025-12\u002Fassets\u002Fimg2.webp",[39,4665,4667],{"id":4666},"the-choice-must-go-on-mais-le-bon-de-préférence","The choice must go on, mais le bon de préférence",[19,4669,4670],{},[1613,4671,4672],{},"Par Céline LOUVET",[19,4674,4675],{},[979,4676,4561],{"href":4677,"rel":4678},"https:\u002F\u002Flyon-craft.fr\u002Fsessions\u002Fthe-choice-must-go-on.html",[983],[19,4680,4681],{},"Un sujet qui a été, est et sera toujours d’une importance capitale pour un projet, quelque soit le domaine : comment faire le bon (ou le moins pire) choix pour un projet ?",[19,4683,4684],{},"Tout d’abord, Céline nous confie la définition du projet réussi. Pour les décideurs, le respect du budget, du délai et la satisfaction utilisateur sont les principales métriques permettant de jauger la réussite du projet. À cela viennent s’ajouter, concernant les développeurs, leur bien-être et la maintenabilité du projet.",[19,4686,4687,4688,2095],{},"Viennent ensuite quelques chiffres issus du ",[979,4689,4692],{"href":4690,"rel":4691},"https:\u002F\u002Ffile.notion.so\u002Ff\u002Ff\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fce54b1c6-d205-4f58-b57c-a0c9c377767e\u002FCHAOSReport2015_rev.pdf?table=block&id=1dcf4462-cd38-80cf-9017-e873e03fa39b&spaceId=5863e833-64f2-4f13-9f7a-2c92c72b5bbf&expirationTimestamp=1745280000000&signature=PgRSPuAWReZL_sg2opmSPe1NqAHZsjtjcJLHQEb-fs4&downloadName=CHAOSReport2015_rev.pdf",[983],"CHAOS report",[23,4694,4695,4698],{},[26,4696,4697],{},"Le budget est respecté à 44%, le délai à 40%, le périmètre à 56%",[26,4699,4700],{},"Les projets réussissent à 36%, sont en difficulté à 45% et échouent à 19%",[19,4702,4703,4704,4707,4708,393],{},"10 facteurs de mise en difficulté sont ainsi identifiés, dont on ressortira par exemple les ",[1311,4705,4706],{},"besoins flous",", et le ",[1311,4709,4710],{},"choix de technologies non maîtrisées",[19,4712,4713],{},[1374,4714],{"alt":4715,"src":4716},"Les 10 facteurs de mise en difficulté identifiés pour un projet","\u002Fcontent-assets\u002F2025-04-24-lyon-craft-2025-12\u002Fassets\u002Fimg3.webp",[19,4718,4719,4720,393],{},"Première piste pour expliquer ces problématiques, les besoins flous. Se basant sur une expérience personnelle, Céline nous évoque un cas courant : l’expression d'une solution à la place du besoin. Comment s’en sortir dans ce cas ? En utilisant par exemple la fameuse technique des ",[979,4721,4724],{"href":4722,"rel":4723},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FCinq_pourquoi",[983],"cinq pourquoi",[19,4726,4727],{},"Ainsi, la demande “j’ai besoin d’un bouton pour télécharger un CSV” est devenue une page web dynamique avec des données financières et un envoi de mail avec cette même page en lien.",[19,4729,4730,4731,4736],{},"Également, attention à ne pas créer de faux besoins (",[979,4732,4735],{"href":4733,"rel":4734},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FProbl%C3%A8me_XY",[983],"Problème XY","), à identifier l’inconnu (ce qui manque de clarté) et à se concentrer sur le futur proche (plus on anticipe, plus c’est flou).",[19,4738,4739],{},"On se concentre ainsi sur la valeur à apporter, avec des critères d’acceptation par exemple, et on identifie ainsi les objectifs à atteindre tout en prenant compte des contraintes impossibles à ignorer (budget, temps, réglementation, etc.).",[19,4741,4742,4743,4746,4747,4752,4753,4758,4759,4762,4769,4772],{},"Il faut connaître l’existant (technologies, équipe, méthodes, dettes, etc.), et mettre par exemple en place un ",[1613,4744,4745],{},"tech radar",", comme celui de ",[979,4748,4751],{"href":4749,"rel":4750},"https:\u002F\u002Fopensource.zalando.com\u002Ftech-radar\u002F",[983],"Zalando par exemple",". De même, on pourra mettre en place des ",[979,4754,4757],{"href":4755,"rel":4756},"https:\u002F\u002Fwww.redhat.com\u002Ffr\u002Ftopics\u002Fdevops\u002Fgolden-paths",[983],"golden paths"," pour répondre aux besoins les plus communs. Gardons en tête, comme Céline nous le répétera tout au long de son talk, qu’il n’y a pas de ",[1613,4760,4761],{},"silver bullet (”",[979,4763,4766],{"href":4764,"rel":4765},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FPas_de_balle_en_argent",[983],[1613,4767,4768],{},"No Silver Bullet",[1613,4770,4771],{},"”)",", c’est-à-dire pas de solution miracle.",[19,4774,4775],{},"Trois complexités sont ici représentées :",[23,4777,4778,4781,4784],{},[26,4779,4780],{},"Essentielle, ce qui a trait au problème à résoudre",[26,4782,4783],{},"Obligatoire, liée à la technologie (et donc à un choix)",[26,4785,4786],{},"Accidentelle, liée à de mauvaises décisions (et donc à un choix)",[19,4788,4789],{},"Nous avons la main sur les deux dernières, qu’il nous faudra viser à réduire.",[19,4791,4792,4793,4796,4797,4802],{},"Un conseil : tous les choix devraient être tracés dans des ",[1613,4794,4795],{},"decisions records"," (comme les ",[979,4798,4801],{"href":4799,"rel":4800},"https:\u002F\u002Fadr.github.io\u002F",[983],"ADR par exemple",") pour y expliquer les raisons du choix, et capitaliser sur ces décisions.",[19,4804,4805],{},[1374,4806],{"alt":4807,"src":4808},"La slide la plus utilisée lors de cette conférence : pas de “silver bullet”, c’est-à-dire pas de solution miracle","\u002Fcontent-assets\u002F2025-04-24-lyon-craft-2025-12\u002Fassets\u002Fimg4.webp",[39,4810,4812,4815],{"id":4811},"rex-le-craft-en-startup",[68,4813,4814],{},"REX"," Le craft en startup",[19,4817,4818],{},[1613,4819,4820],{},"Par Anne JACQUET et Philippe LEBOC",[19,4822,4823],{},[979,4824,4561],{"href":4825,"rel":4826},"https:\u002F\u002Flyon-craft.fr\u002Fsessions\u002Frex-le-craft-en-startup.html",[983],[19,4828,4829,4830,4835,4836,4839],{},"Anne et Philippe travaillent actuellement dans la start-up ",[979,4831,4834],{"href":4832,"rel":4833},"https:\u002F\u002Fwww.auxodynamics.com\u002F",[983],"Auxo",", fondée il y a quasiment deux ans (à peine moins âgée que ",[979,4837,1607],{"href":1605,"rel":4838},[983],"). Le produit proposé accompagne la transformation durable des entreprises, à partir de leurs données financières et opérationnelles. L’équipe technique se compose d’une dizaine de personnes développant le produit. Les technologies employées : Java \u002F SpringBoot, Vue.js et AWS.",[19,4841,4842],{},"Nos deux speakers sont les deux développeurs backend de l’équipe. Ils commencent par nous faire un rappel de certains concepts :",[23,4844,4845,4848],{},[26,4846,4847],{},"Monolithe \u002F Monolithe distribué \u002F Microservices \u002F Nanoservices",[26,4849,4850],{},"Synchrone \u002F Asynchrone",[19,4852,4853],{},"La solution technique est ici partie sur des microservices asynchrones, qui échangent des messages via Kafka. Mais un des microservices a ensuite nécessité des appels synchrones, nous sommes donc ici sur un mix des deux. Les microservices partagent tous une “shared library” commune, contenant de la configuration, des briques techniques, des concepts métier partagés, etc.",[19,4855,4856],{},"Nous passons ensuite en revue de nombreux concepts mis en place sur leur produit, dont certains biens connus des crafteurs :",[23,4858,4859,4862,4871,4874,4882,4885,4888],{},[26,4860,4861],{},"Architecture hexagonale",[26,4863,4864,4865,4870],{},"Pyramide de tests : Unitaire > Intégration > Comportement (",[979,4866,4869],{"href":4867,"rel":4868},"https:\u002F\u002Fcucumber.io\u002F",[983],"Cucumber",") > End-to-end",[26,4872,4873],{},"Respect des principes REST (PUT vs PATCH par ex.) et comment cela est mis en place (idempotence, pagination, etc.)",[26,4875,4876,4881],{},[979,4877,4880],{"href":4878,"rel":4879},"https:\u002F\u002Fwww.openapis.org\u002F",[983],"OpenAPI",", contrats entre front et back",[26,4883,4884],{},"Assertions dans le domaine, exceptions dans le domaine traduites ensuite et renvoyant un statut HTTP adéquat",[26,4886,4887],{},"Annotations Java pour valider request body, query parameters et path parameters",[26,4889,4890,4895],{},[979,4891,4894],{"href":4892,"rel":4893},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FOptimistic_concurrency_control",[983],"Optimistic lock"," pour des questions de performance",[19,4897,4898],{},"Au final, Anne et Philippe font ici un tour complet de tout ce qui a été mis en place pour assurer la qualité du produit et de son implémentation technique. On voit ici que tout ce qui a été mis en place l’a été pour une bonne raison. Ces conseils ne sont d’ailleurs pas spécifiques à un contexte de startup.",[19,4900,4901],{},[1374,4902],{"alt":4903,"src":4904},"Fin de conférence pour Anne et Philippe","\u002Fcontent-assets\u002F2025-04-24-lyon-craft-2025-12\u002Fassets\u002Fimg5.webp",[39,4906,4908],{"id":4907},"fin-de-cette-matinée","Fin de cette matinée",[19,4910,4911],{},"Après cette belle matinée et tous ces concepts craft abordés, la faim se fait sentir, et il est temps pour tout le monde de profiter d’un déjeuner bien mérité.",[19,4913,4914],{},"Je vous propose de continuer ce retour de conférence dans un second article à paraître, où nous parlerons front, regard scientifique et… Smalltalk !",{"title":63,"searchDepth":86,"depth":86,"links":4916},[4917,4918,4919,4921],{"id":4548,"depth":86,"text":4549},{"id":4666,"depth":86,"text":4667},{"id":4811,"depth":86,"text":4920},"REX Le craft en startup",{"id":4907,"depth":86,"text":4908},"2025-04-24T14:50:16.697Z","Retour sur la conférence Lyon Craft 2025 qui s’est déroulée le lundi 14 Avril 2025. Au programme, comme l’indique la conférence elle-même : “**Des passionné·e·s, des ateliers, des présentations et… c’",{},"\u002Fblogs\u002F2025-04-24-lyon-craft-2025-12",[4927,4929,4931],{"id":1184,"name":1185,"image":4928,"linkedin":1187,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc88f5dfa-16db-4e6f-acf1-34dd80ee8766\u002Femma_hoppr.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466ZZP3LNIZ%2F20250424%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250424T145016Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEH8aCXVzLXdlc3QtMiJGMEQCIAD4hutXa0ml3Y8EZ316barAph9dGk3IRKbBJ6H2NPkkAiAdQG9T0owlAUFInwuzoMAQM01j1NtLiiZG2jW%2FT%2FjX9Sr%2FAwgYEAAaDDYzNzQyMzE4MzgwNSIMTN%2FWccyZZyqeFjXDKtwD7likSB1JMnGkfv56Ja%2BDQbgTvr%2FuJpffy5MHg%2BJJ%2BVxIrE8T7Zw7%2BvvkaExh1fWWPq67xJJ0DJDM%2Ft9Www2JFLzAGwhWQRoTTDxSWuHpSpf6kwgapJaMIAA%2FclFRJnE%2FHc3xXXWWELIAgjzltcVwh5cbNj8JI5Qp3gDQztRRhTnx7%2B0demwgkRXJMsTvGl37RLwMb29yHrc3yR6JnZOKM9T25ZXu95SRD9xAmJkCp209j5R04n0Yjy0QJ7Sni32FBwcRJhh6Wg1e2Kyk80QnjAQ7lNCH6EoRDSV8Fe%2FH3ZKYqFdDNa5lEUwIOt7SnGG%2FGtQu2kIlEb%2FS0SgpQBKpGtuDPe7G%2F3sQ2pI8NLWlUqEfikpjbDnR3OINc7Gz3lxesdG9U6YBSK%2F25acDDqAgrv7KtTidxsXrLao8hWQ38TEpwQQSicWbPT1yM3bIZbnPNOy9Bt2qCfA0q60kftPkXY2g5Lo6HrMP1zWlYV2FRxivvTHB5q%2FYBPLz0%2FqsN3b5gGo8kcQUso9UP4toIrOUjXDXUX28ntdwfdHzDltQAmuPUmPNiVjPcCBRItdwRVXUdtEBIalb3JDRTk1YDxz7PIOCQsLGq2IvdfqryYvGXGfIe5R%2FD0wSqVzRjLowpJ6pwAY6pgEgu8gqJrHbvb0l%2B2yitK%2FU094ki4AwNAQuta9J%2BzV%2BzNuwqbK%2F44yu4xRTm5GmLX20gbrkJdK1hmfEQ6Yvq0gfsLRcah%2F5nVaybYz4ggsj4RjepHqij2rdOpB%2B9mDEVs2hzbADQQSC%2B2Xa8Ial%2BLh0RvQv%2FnGulFlegFMIg%2BewQwGSHiAjqGXjZFix8O5Kep53wEA8nmg8sXPTkR6XCqygwwUJAItM&X-Amz-Signature=ea9ad56797425f78e31cddc4a233f3fb6f6a3d762a4de2d79c59c807a7fd2c48&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":3392,"name":3393,"image":4930,"linkedin":3395,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F53946b9e-3bb9-45bd-a8b4-429c51156179\u002FT04PC176TGB-U05EW3YF61Z-5e129f612df3-512.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466ZQTQSD5B%2F20250424%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250424T145016Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEH4aCXVzLXdlc3QtMiJIMEYCIQDbg0WLCaqDYBaT5EAm90fC9VoyYCY744P%2B1c3G4qB%2ByQIhAM36Uk9ob7vyZOspNUFT728A6IE7FMyTluR9N6yZIWZNKv8DCBcQABoMNjM3NDIzMTgzODA1IgxwBrjSMTqOr1iBNTkq3AOkQ79%2BucvRaAFugoBvlSJSfacPAIgOWapcuAgBDgIZ8t6ucyaNiHnoN8RJQf%2FsUFXbmNZOi7QsD%2F8kn5fpfedw%2BDrtUHhOgSREmYFSeYUX4j0gKDARi4R7gJVa0atIUnSEiqXcpxljIGEaJlhVpuZu3cI6TdRRCvBO%2FkHFnh%2FMsWBA23IGHMJ1wzVZP23rwOcU%2BPb6Fee4AN6y%2BmxJuv6KGk6745C65PQvJrhBonJ1xHYywuFqr2GgYXuUPB7csZ%2FzzYo%2BA0xYXIhkf5qETQqYXwbGBDxJDT5xYyZiqiGr7xhtPRrKb%2F0eJfdUoBXTNr3alIIexaZ2j%2FUk9WREQBtyRuvZxJfIBz053pG2%2FzII6mFzE7T5PeuhfeYG6bSYiVOV3RYhueioXzukCtuBDzv0U9nT5i98QQZd2HX7rGlFW8gU3BiEwCYLP0A4yVuckpgFKy5bzU3ri9BrDBxBUhZtDdl%2FQAvibikqUv5xqK9DbYuE7UzzF7x1B%2FCjIGdRT648ftoW22ktXjYNMygX5URd8CHEJFTgCpXUqGF3MVgCRZ%2F%2FeWK6dV9h7ZoeXiGpPnHsZf8ikcN2JR5PQsF3roTMauTBpbHak62F14FkazBMpu0IPYpdUacDvOCZ1jDGgqnABjqkAZeHxXrKXw%2FyxBY0qW0PJk%2Fqwy%2FzyEtCN3aab7YFWGbU1XZ7wZg1wMEmfandoQ%2FF9QDrKfu6vDxaWidbSI%2Bqhc9%2BxMtpFIp8mwOXqFBUJyQedEzox0S20vcgrYK7yS%2BNppUdMCz1QWCmd8RAB3PZHWVDw6FxvJE5ObD14am0QJf3JYZDzVfvLjKSL8dbXCpHRZ8Lh7bP3AHjubFmBqIKqx9hjJJs&X-Amz-Signature=4b999cb952cb26fd3de1e914824e91a2eb46e1bf095d9dc88f2a653b3c2f1104&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":1218,"name":1219,"image":4932,"linkedin":1221,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fb8425b05-a0a4-4560-964d-259e8a84c063\u002Fsamuel.jpeg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4662CQLWNJA%2F20250424%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250424T145016Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEH4aCXVzLXdlc3QtMiJIMEYCIQCmhL9dbISruox6fMvLj6hlUJwDL2RNROCNk%2BRnyXewpAIhAPIZL8fOAQYfTbrQhTi1roY4yx9Y4wlxLIexFvMQ8SxfKv8DCBcQABoMNjM3NDIzMTgzODA1IgxKEHa2%2BtpiCyu7zb4q3ANQvi91jPvQtMl6sDwBYHgyOYunstmWOnOtycBpk8d2NIALAsY8hqSc5vdD2oApdfs%2BUQIHYZQ1qEm2OyVx7UYR6O4ftWad0slO4MKujNa3%2BQzNSa24AWMSWQEnvvtwFi8Rd0I9Q8dD8xmROSgGgJbKYxkU96LJg25t4uKHYUyTlZ1F6jkIV9PtKWMLJ0XM7fVrDrHObwgND4g3ngRTArfVxNR3LaIvoNHvHuOkduBpf%2F0uzIE6glxdSb0wzTW74DZUTv%2F68rI%2Fnw6ePZRTJTYmFk0fxy%2BifaPRO7%2B9CjT2eA4xak9ibyRakRnoxJI%2Fkwe6FctuAYBzsttofBD8eQREOxcp%2BqesuA4KkHUIe9oR4%2FOV8GD9vBX3xtpZcodp97CvsECaxbBmVRVhBLna4I7MZcc7ISurFOEBh3JM1%2FHdVvcXrkWMZXcsYnvMtonQRbXx0jp4gy3J0NigBQDYvUQZV6%2F7TTqpIhCig%2Fzc0UCn3kQp8jTn%2FJQFnZ0KkTDPIiEyKDixz42R%2B5uTU2bWP8CmnQST2BIl2yoxVeUMZJkyaG7Q6pA%2Bvm3gFjJubT11CeEwZckBuf%2FP1HAPkl6iKEDik4szHdjg4vI2sRvP0s8V%2BBVinUfBLcRIWCi65jDWg6nABjqkAbyEEgUPtQBKSOClV6oaBk3mWzg%2BSZcaOqklt6UcxlmfihqrfgPXbDJl0JXZkWJOPhbq8gg4sffVR9w1Sun0twXdMHAP0Ek2NyRy0PxQVgS0k1q%2FHCaUH0kPyma8c3pj96t6ckwXeXsBY%2FBYOV7Jk3FZJ6%2BrfQUitMPu2EzyziL8JenDdqjasIspgXU8X8qwMX3ZxV4TItv9BQsVYjc6j2C25O2q&X-Amz-Signature=1dab69f2b448359e58c91caae1e683bb810dc844845994020bbdd895da8d33b5&X-Amz-SignedHeaders=host&x-id=GetObject",{"title":4518,"description":4923},"blogs\u002F2025-04-24-lyon-craft-2025-12\u002Findex",[4514,4936,1208],"2025","K6Kk6szdZICyofOvs45o1g8MLue3DQMf-WQkz_bx5xM",{"id":4939,"title":4940,"alt":4941,"authors":4942,"body":4944,"date":5511,"description":5512,"extension":1178,"image":1179,"meta":5513,"navigation":102,"ogImage":1179,"path":5514,"published":102,"reviewers":5515,"seo":5522,"stem":5523,"tags":5524,"__hash__":5525},"blogs\u002Fblogs\u002F2025-05-07-lyon-craft-2025-22\u002Findex.md","Lyon Craft 2025 2\u002F2","Photo de la scène principale du Lyon Craft 2025, avec le logo de la conférence, avant le talk de conclusion de la journée.",[4943],{"id":1190,"name":1191,"image":1560,"linkedin":1193,"x":1188},{"type":16,"value":4945,"toc":5504},[4946,4955,4959,4964,4970,4976,4989,5007,5016,5027,5034,5040,5043,5046,5061,5067,5094,5109,5124,5141,5144,5155,5169,5173,5178,5184,5187,5196,5203,5224,5233,5239,5250,5257,5271,5286,5292,5301,5307,5310,5327,5336,5355,5362,5376,5382,5388,5391,5397,5404,5409,5412,5423,5429,5438,5441,5444,5455,5466,5469,5472,5474,5489,5492,5501],[19,4947,4948,4949,4954],{},"Cet article est la suite de ",[979,4950,4953],{"href":4951,"rel":4952},"https:\u002F\u002Fblog.hoppr.tech\u002Fblogs\u002F2025-04-24-lyon-craft-2025-12",[983],"mon retour sur Lyon Craft",". Dans cette première partie, nous avions couvert les talks auxquels j’ai eu la chance d’assister dans la matinée. Place maintenant à un après-midi de Craft !",[39,4956,4958],{"id":4957},"smalltalk-voyage-futuriste-dans-le-passé","Smalltalk, voyage futuriste dans le passé !",[19,4960,4961],{},[1613,4962,4963],{},"Par Lionel ARMANET",[19,4965,4966],{},[979,4967,4561],{"href":4968,"rel":4969},"https:\u002F\u002Flyon-craft.fr\u002Fsessions\u002Fsmalltalk-voyage-futuriste-dans-le-passe.html",[983],[19,4971,4972],{},[1374,4973],{"alt":4974,"src":4975},"Début de talk pour Lionel sur Smalltalk","\u002Fcontent-assets\u002F2025-05-07-lyon-craft-2025-22\u002Fassets\u002Fimg1.webp",[19,4977,4978,4979,4984,4985,4988],{},"Tout commence par une discussion entre Lionel et un de ses collègues. Celui-ci vient pour son premier jour avec un livre, ",[979,4980,4983],{"href":4981,"rel":4982},"https:\u002F\u002Fwww.babelio.com\u002Flivres\u002FMartin-Coder-proprement\u002F125763",[983],"Coder Proprement"," de Robert C. Martin. Lionel parcourt ce livre, un classique du Craft, revient vers son collègue et lui dit \"",[1613,4986,4987],{},"ton bouquin, c'est mon cours de Smalltalk de l'école","\".",[19,4990,4991,4992,4995,4996,5001,5002,393],{},"S’en suit une plongée dans les années 70, au Xerox PARC ",[1613,4993,4994],{},"(Palo Alto Research Center)",". Les créateurs de l’ordinateur ",[979,4997,5000],{"href":4998,"rel":4999},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FXerox_Alto",[983],"Xerox Alto",", un des premiers ordinateurs présentant une interface graphique, inventent le langage ",[979,5003,5006],{"href":5004,"rel":5005},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FSmalltalk",[983],"Smalltalk",[19,5008,5009,5010,5015],{},"Ce langage est le premier à utiliser les concepts clés de la ",[979,5011,5014],{"href":5012,"rel":5013},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FProgrammation_orient%C3%A9e_objet",[983],"POO",". L’idée sous-jacente est une métaphore du monde qui nous entoure, des objets et de leurs interactions. Les concepts objet ici :",[23,5017,5018,5021,5024],{},[26,5019,5020],{},"Encapsulation",[26,5022,5023],{},"Envoyer un message",[26,5025,5026],{},"Recevoir un message",[19,5028,5029,5030,5033],{},"Le Smalltalk est compilé en ",[1613,5031,5032],{},"bytecode"," et interprété par une machine virtuelle, pour plus de portabilité. Jusque là, les amateurs de Java ne sont pas surpris. Et pour cause, Java s’inspire de Smalltalk !",[19,5035,5036],{},[1374,5037],{"alt":5038,"src":5039},"Hello World en Smalltalk","\u002Fcontent-assets\u002F2025-05-07-lyon-craft-2025-22\u002Fassets\u002Fimg2.webp",[19,5041,5042],{},"Il est à noter que Smalltalk avait pour but de pouvoir être simple à apprendre (par des enfants par exemple), et à se rapprocher au plus du langage anglais naturel. En Smalltalk, on écrit plus qu’on ne code, et on finit nos instructions par un point.",[19,5044,5045],{},"Pour finir l’histoire, la Xerox Alto est un échec commercial. Steve Jobs ayant visité le Xerox PARC, reprendra l’interface graphique pour créer l’Apple Lisa puis les Macintosh, et l’Objective-C comme successeur au Smalltalk.",[19,5047,5048,5049,5054,5055,5060],{},"Place maintenant à une démonstration de Smalltalk en live avec Lionel. Il utilise ici ",[979,5050,5053],{"href":5051,"rel":5052},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FCuis_Smalltalk",[983],"CUIS",", et nous parle également de ",[979,5056,5059],{"href":5057,"rel":5058},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FPharo",[983],"Pharo",". En effet, une communauté Smalltalk existe toujours pour garder le langage vivant.",[19,5062,5063],{},[1374,5064],{"alt":5065,"src":5066},"Démonstration de Smalltalk par Lionel","\u002Fcontent-assets\u002F2025-05-07-lyon-craft-2025-22\u002Fassets\u002Fimg3.webp",[19,5068,5069,5070,5075,5076,5081,5082,5087,5088,5093],{},"En Smalltalk, tout est objet, et tout peut se réécrire. La ",[979,5071,5074],{"href":5072,"rel":5073},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FM%C3%A9taprogrammation",[983],"métaprogrammation"," y est monnaie courante, et l’IDE lui-même (écrit en Smalltalk également) peut-être modifié. Ici, pas de ",[979,5077,5080],{"href":5078,"rel":5079},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FSucre_syntaxique",[983],"sucre syntaxique",", pas d’",[979,5083,5086],{"href":5084,"rel":5085},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FInstruction_conditionnelle_(programmation)",[983],"instruction conditionnelle",", mais du typage dynamique, des ",[979,5089,5092],{"href":5090,"rel":5091},"https:\u002F\u002Fwww.gnu.org\u002Fsoftware\u002Fsmalltalk\u002Fmanual-base\u002Fhtml_node\u002FBlockClosure.html",[983],"BlockClosures"," (équivalent de lambdas en Java par exemple) qui sont eux-mêmes des objets manipulables.",[19,5095,5096,5097,5102,5103,5108],{},"Lionel nous fait naviguer sur des concepts simples, comme les objets ",[979,5098,5101],{"href":5099,"rel":5100},"https:\u002F\u002Fwww.gnu.org\u002Fsoftware\u002Fsmalltalk\u002Fmanual-base\u002Fhtml_node\u002FBoolean.html",[983],"Boolean"," et ",[979,5104,5107],{"href":5105,"rel":5106},"https:\u002F\u002Fwww.gnu.org\u002Fsoftware\u002Fsmalltalk\u002Fmanual-base\u002Fhtml_node\u002FMessageNotUnderstood.html",[983],"MessageNotUnderstood"," (erreur de fonction introuvable). Etant des objets comme les autres, tous deux pouvant être également réécrits  !",[19,5110,5111,5112,5117,5118,5102,5121,393],{},"La notion de test n’est pas présente à cette époque, mais il est quand même possible d’y faire une sorte de TDD. Smalltalk s’écrit en ",[979,5113,5116],{"href":5114,"rel":5115},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FApproches_ascendante_et_descendante",[983],"top-down"," et avec une approche très itérative en ",[1613,5119,5120],{},"baby steps",[1613,5122,5123],{},"red\u002Fgreen\u002Frefactor",[19,5125,5126,5127,5130,5131,5134,5135,5140],{},"Il nous est montré ici à quel point Smalltalk se veut ",[1613,5128,5129],{},"simple & fluent",", se rapprochant vraiment de notions telles que le ",[1613,5132,5133],{},"clean code",". D’ailleurs, le livre ",[979,5136,5139],{"href":5137,"rel":5138},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FDesign_Patterns",[983],"Design Patterns"," de 1994 donne des exemples en Smalltalk.",[19,5142,5143],{},"En conclusion, nous pouvons effectivement faire un parallèle entre les années 70 et maintenant. Beaucoup de concepts qui nous semblent novateurs de nos jours sont en fait tirés ou inspirés d’une époque très lointaine de la programmation. Lionel nous indique trois points qui en découlent :",[23,5145,5146,5149,5152],{},[26,5147,5148],{},"Changer l’anormal (remettre en question les choix, les paradigmes et langages utilisés)",[26,5150,5151],{},"Apprendre de tous (attention à une potentiel hiérarchie entre les développeurs)",[26,5153,5154],{},"Apprendre du passé (se baser sur toutes les connaissances du passé)",[19,5156,5157,3570,5160,5167],{},[1613,5158,5159],{},"Pour plus d’informations, voici un podcast où",[979,5161,5164],{"href":5162,"rel":5163},"https:\u002F\u002Fpodcast.ausha.co\u002Fpunkindev\u002Fs05e10-aux-origines-du-craft-et-de-tdd-smalltalk-avec-lionel-armanet",[983],[1613,5165,5166],{},"Lionel parle de Craft et de Smalltalk avec Sylvain\u002FPunkinDev",[1613,5168,393],{},[39,5170,5172],{"id":5171},"du-style-au-sein-du-craft","Du Style au sein du Craft",[19,5174,5175],{},[1613,5176,5177],{},"Par Aurélie BRACCO et Arnaud FREISMUTH",[19,5179,5180],{},[979,5181,4561],{"href":5182,"rel":5183},"https:\u002F\u002Flyon-craft.fr\u002Fsessions\u002Fdu-style-au-sein-du-craft.html",[983],[19,5185,5186],{},"Aurélie et Arnaud, tous deux développeurs front, sont venus nous parler de concepts craft… graphiques !",[19,5188,5189,5190,5195],{},"Tout d’abord, nous assistons à une présentation de plusieurs concepts bien connus des devs front, comme par exemple le ",[979,5191,5194],{"href":5192,"rel":5193},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FSyst%C3%A8me_de_design",[983],"Design System",", comprenant les blocs de construction et normes à adopter pour un projet \u002F une organisation. Les avantages : cohérence de produit et de marque, collaboration simplifiée, temps de conception diminué, et même une amélioration de l’accessibilité, dont on parlera juste après.",[19,5197,5198,5199,5202],{},"Ce Design System est composé d’un ",[1613,5200,5201],{},"styleguide",", un document qui définit des règles et bonnes pratiques. Il permet une communication visuelle cohérente à l’entreprise\u002Fau projet, et comprend également une charte éditoriale, permettant de savoir à quel public on s’adresse et comment.",[19,5204,5205,5206,5211,5212,5217,5218,5223],{},"Aurélie et Arnaud nous montrent un exemple sur ",[979,5207,5210],{"href":5208,"rel":5209},"https:\u002F\u002Fwww.figma.com\u002F",[983],"Figma",", où sont également définis les couleurs primaires\u002Fsecondaires et leurs teintes, la typographie, les ",[979,5213,5216],{"href":5214,"rel":5215},"https:\u002F\u002Fwww.w3schools.com\u002Fcss\u002Fcss3_borders.asp",[983],"radius",", les ",[979,5219,5222],{"href":5220,"rel":5221},"https:\u002F\u002Fwww.w3schools.com\u002Fcss\u002Fcss3_shadows.asp",[983],"shadows",", etc. qui seront utilisés sur le projet.",[19,5225,5226,5227,5232],{},"Parlons maintenant ",[979,5228,5231],{"href":5229,"rel":5230},"https:\u002F\u002Fbradfrost.com\u002Fblog\u002Fpost\u002Fatomic-web-design\u002F",[983],"Atomic Design",", ce concept structure les interfaces en composants réutilisables, des plus simples (atomes) aux plus complexes (pages), pour une conception modulaire et cohérente.",[19,5234,5235],{},[1374,5236],{"alt":5237,"src":5238},"Les composants de l’Atomic Design, dans l’ordre : Atomes, Molécules, Organismes, Templates et enfin Pages","\u002Fcontent-assets\u002F2025-05-07-lyon-craft-2025-22\u002Fassets\u002Fimg4.webp",[19,5240,5241,5242,5245,5246,5249],{},"Dans cette optique, nous pouvons également ajouter la notion de ",[1613,5243,5244],{},"quarks",", encore plus petits que l’atome et inutilisables seuls (par exemple une couleur). L’ensemble de ces éléments est trouvable sur la ",[1613,5247,5248],{},"pattern library"," qu’Aurélie et Arnaud nous présentent alors.",[19,5251,5252,5253,5256],{},"Pour la suite de la démonstration, les deux développeurs nous embarquent sur la création d’un composant accordéon, que nous allons ajouter à leur ",[1613,5254,5255],{},"component library"," (regroupant et standardisant les différents composants graphiques utilisables sur le projet).",[479,5258,5259],{},[19,5260,5261,5262,5265,5266,393],{},"💡 Si vous ne savez pas ce qu’est un accordéon, ou que vous voulez jeter un œil au ",[1613,5263,5264],{},"Système de Design"," de l’Etat, vous pouvez aller voir ",[979,5267,5270],{"href":5268,"rel":5269},"https:\u002F\u002Fwww.systeme-de-design.gouv.fr\u002Fcomposants-et-modeles\u002Fcomposants\u002Faccordeon\u002F",[983],"l’accordéon des sites gouvernementaux ici",[19,5272,5273,5274,5279,5280,5285],{},"Nous assistons donc à une démonstration sur ",[979,5275,5278],{"href":5276,"rel":5277},"https:\u002F\u002Ftikui.org\u002F",[983],"Tikui"," (projet Open Source). Cette solution s’appuie sur ",[979,5281,5284],{"href":5282,"rel":5283},"https:\u002F\u002Fpugjs.org\u002Fapi\u002Fgetting-started.html",[983],"Pug",", qui permet du templating minimaliste traduisible en HTML. L’accordéon est créé, puis ensuite un groupe d’accordéons.",[19,5287,5288,5289,5291],{},"Dans une appli Vue, on récupère ensuite tout simplement la ",[1613,5290,5248],{}," provenant de Tikui, et l’organisme “groupe d’accordéons” précédemment créé. Et nous voici avec notre composant injecté sur notre application.",[19,5293,5294,5295,5300],{},"S’en suit un focus sur l’",[979,5296,5299],{"href":5297,"rel":5298},"https:\u002F\u002Faccessibilite.numerique.gouv.fr\u002Fobligations\u002Fnotions-accessibilite-numerique\u002F",[983],"accessibilité numérique",", un élément ô combien important. Situation de handicap, connexion lente, matériel ancien ou défectueux, environnement défavorable, les problématiques potentielles d’utilisation d’un produit numérique ne manquent pas. C’est au développeur front qu’incombe la responsabilité de ne pas créer l’inaccessibilité du web.",[19,5302,5303],{},[1374,5304],{"alt":5305,"src":5306},"Pourquoi l’accessibilité c’est important ? Aurélie et Arnaud nous informent sur le sujet","\u002Fcontent-assets\u002F2025-05-07-lyon-craft-2025-22\u002Fassets\u002Fimg5.webp",[19,5308,5309],{},"Quelques pistes pour améliorer l’accessibilité numérique de votre produit :",[23,5311,5312,5315,5318,5321,5324],{},[26,5313,5314],{},"conformité aux normes HTML\u002FW3C,  utilisation correcte des balises sémantiques et des attributs aria",[26,5316,5317],{},"tester la navigation clavier",[26,5319,5320],{},"tests avec lecteur d'écran",[26,5322,5323],{},"responsivité et zoom du texte jusque 200%",[26,5325,5326],{},"tests sur différents navigateurs",[19,5328,5329,5330,5335],{},"Aurélie et Arnaud font ici un test avec un lecteur d’écran. Celui-ci démontre que notre accordéon n’est pas encore accessible : le lecteur d’écran ne sait pas si celui-ci est ouvert ou fermé, ce qui n’est pas pratique. Les deux développeurs ajoutent donc l’attribut ",[979,5331,5334],{"href":5332,"rel":5333},"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAccessibility\u002FARIA\u002FReference\u002FAttributes\u002Faria-expanded",[983],"aria-expanded"," pour déterminer le statut de l’accordéon, et le tour est joué !",[19,5337,5338,5339,5344,5345,3570,5348,3570,5351,5354],{},"La conclusion donnée ici est un lien avec le Craft, et notamment avec le ",[979,5340,5343],{"href":5341,"rel":5342},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FTest_driven_development",[983],"TDD"," : le cycle ",[1613,5346,5347],{},"red >",[1613,5349,5350],{},"green >",[1613,5352,5353],{},"refactor"," est ici remplacé par un cycle structure (HTML) > style (CSS) > refacto (qualité, accessibilité).",[39,5356,5358,5361],{"id":5357},"conférence-dessinée-regard-scientifique-sur-lartisanat-logiciel",[68,5359,5360],{},"Conférence dessinée"," Regard scientifique sur l'artisanat logiciel",[19,5363,5364,5367,5374],{},[1613,5365,5366],{},"Par Victor LAMBRET (",[979,5368,5371],{"href":5369,"rel":5370},"https:\u002F\u002Fvlambret.github.io\u002F",[983],[1613,5372,5373],{},"boringdev.eu",[1613,5375,1395],{},[19,5377,5378],{},[979,5379,4561],{"href":5380,"rel":5381},"https:\u002F\u002Flyon-craft.fr\u002Fsessions\u002Fregard-scientifique-sur-l-artisanat-logiciel.html",[983],[19,5383,5384,5385,393],{},"En avant pour une conférence dessinée, Victor nous illustrant ses propos avec des dessins faits maison. Nous allons ici bien sûr parler de Craftsmanship, ou en français d’",[1613,5386,5387],{},"artisanat logiciel",[19,5389,5390],{},"Cet artisanat logiciel, ces concepts, méthodes et outils pour mieux faire, est-ce que cela compte réellement ? Est-ce simplement une accumulation d’effets de mode ? Ne pas faire de Craft conduit-il forcément à l’échec, est-ce si manichéen ? Pour s’en assurer, rien de mieux que de se référer aux études scientifiques.",[19,5392,5393],{},[1374,5394],{"alt":5395,"src":5396},"C’est parti pour un regard scientifique sur l'artisanat logiciel, et sur les dessins de Victor","\u002Fcontent-assets\u002F2025-05-07-lyon-craft-2025-22\u002Fassets\u002Fimg6.webp",[19,5398,5399,5400,5403],{},"Par exemple, Robert C. Martin, dans son livre ",[979,5401,4983],{"href":4981,"rel":5402},[983]," (déjà cité plus haut dans cet article, décidément) nous indique que la taille des fonctions est une donnée importante pour jauger la qualité d’un code. En effet, on peut trouver cette citation dans son ouvrage :",[479,5405,5406],{},[19,5407,5408],{},"The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that.",[19,5410,5411],{},"Écrire de plus petites fonctions permettrait de simplifier la programmation. Mais est-ce aussi évident ? Dans un exemple sur du Fortran datant de 83, plus le module était grand, plus le nombre de bugs par ligne était petit, rentrant en contradiction avec Robert C. Martin.",[19,5413,5414,5415,5418,5419,5422],{},"Pour du plus récent et complet, une étude de 2016 compare deux équipes (que je nommerai ici A et B pour que ce soit plus simple). L’équipe A part avec une base de code, une équipe B avec une version “améliorée” respectant le ",[1613,5416,5417],{},"Clean Code."," Le résultat ? En moyenne, A arrive plus facilement à ajouter une fonctionnalité sur son code, B à refactorer, et des bugs furent trouvés beaucoup plus rapidement par l’équipe A. Conclusion : a priori, pas de bénéfice de ",[1613,5420,5421],{},"Clean Code"," sur la compréhension d’un code par des développeurs.",[19,5424,5425],{},[1374,5426],{"alt":5427,"src":5428},"Un exemple de slide présenté par Lionel, une illustration parlante de sa conférence","\u002Fcontent-assets\u002F2025-05-07-lyon-craft-2025-22\u002Fassets\u002Fimg7.webp",[19,5430,5431,5432,5437],{},"C’est au tour du TDD de passer à la loupe de Lionel. Une première ",[979,5433,5436],{"href":5434,"rel":5435},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FM%C3%A9ta-analyse",[983],"méta-analyse"," nous indique une tendance : le TDD serait positif pour éviter\u002Fcorriger les bugs, mais en contre-partie la productivité serait impactée négativement. Mais il est compliqué de statuer de manière très claire avec les études industrielles.",[19,5439,5440],{},"IBM et Microsoft ont mené des études comparatives, sur des projets en parallèle. Une équipe utilisait le TDD, systématiquement, l’autre était un groupe contrôle. Là aussi, le temps passé était plus important du côté de l’équipe en TDD (+20% en moyenne), mais le nombre de bugs bien inférieur (de -40% à -90%).",[19,5442,5443],{},"Mais ces études ne portent pas de conclusion clair sur l’efficacité de la pratique du TDD. En effet, ces bugs auraient pu être trouvés par l’équipe de QA, et la notion de TDD dépend parfois d’une équipe, d’un projet, d’une étude à l’autre. De plus, l’échantillon est trop faible : par exemple seuls 0,8% des repos Java hébergés sur GitHub feraient du TDD.",[19,5445,5446,5447,5450,5451,5454],{},"Enfin, Lionel nous évoque une comparaison réalisée entre TDD et ITL (",[1613,5448,5449],{},"Iterative Test Last",", que l’on pourrait également qualifier de ",[1613,5452,5453],{},"reverse TDD","), dans différents contextes, qu’ils soient universitaires ou professionnels, sur des katas bien connus. Les conclusions :",[23,5456,5457,5460,5463],{},[26,5458,5459],{},"pas de grandes différences sur étudiants vs professionnels",[26,5461,5462],{},"pas de grandes différences entre les différents langages de projets",[26,5464,5465],{},"la qualité est légèrement meilleure pour l’ITL, mais pas de manière significative.",[19,5467,5468],{},"On peut en conclure que les deux sont globalement équivalents, et que la qualité vient surtout des concepts de feedback régulier, de refactoring continu, et bien sûr des tests à l’origine de ces deux méthodologies.",[19,5470,5471],{},"En conclusion du talk, Lionel nous conseille de ne pas hésiter à attaquer nos croyances. Tout dépend du contexte et des conditions des projets sur lesquels nous intervenons.",[39,5473,1161],{"id":1160},[19,5475,5476,5477,5482,5483,5488],{},"Après toutes ces conférences, j’ai eu la chance d’assister à une très intéressante ",[979,5478,5481],{"href":5479,"rel":5480},"https:\u002F\u002Flyon-craft.fr\u002Fsessions\u002Fpodcast.html",[983],"table ronde sur le sujet de l’image du Craft"," (à retrouver également en ",[979,5484,5487],{"href":5485,"rel":5486},"https:\u002F\u002Fpodcast.ausha.co\u002Fpunkindev\u002Fs05e14-liveatlyoncraft-table-ronde-sur-l-image-du-craft",[983],"podcast chez PunkinDev","), un sujet dont nous pourrons discuter à l’avenir dans un autre article.",[19,5490,5491],{},"En somme, le bilan de la journée est très positif, et j’en tire beaucoup d’informations et de réflexions sur le sujet du Software Craftsmanship. Je participerai avec plaisir à l’éventuelle prochaine édition.",[19,5493,5494,5495,5500],{},"Les conférences ont été enregistrées et sont retrouvables sur le ",[979,5496,5499],{"href":5497,"rel":5498},"https:\u002F\u002Fwww.youtube.com\u002F@softwarecrafterslyon9383",[983],"YouTube de Software Crafters Lyon",", où vous pourrez également visionner tous les talks dont je n’ai pas pu parler sur ce blog.",[19,5502,5503],{},"Merci de votre lecture et à très bientôt sur ce blog pour reparler de Craft et de plein d’autres belles choses !",{"title":63,"searchDepth":86,"depth":86,"links":5505},[5506,5507,5508,5510],{"id":4957,"depth":86,"text":4958},{"id":5171,"depth":86,"text":5172},{"id":5357,"depth":86,"text":5509},"Conférence dessinée Regard scientifique sur l'artisanat logiciel",{"id":1160,"depth":86,"text":1161},"2025-05-07T14:36:44.718Z","Cet article est la suite de [mon retour sur Lyon Craft](https:\u002F\u002Fblog.hoppr.tech\u002Fblogs\u002F2025-04-24-lyon-craft-2025-12). Dans cette première partie, nous avions couvert les talks auxquels j’ai eu la chan",{},"\u002Fblogs\u002F2025-05-07-lyon-craft-2025-22",[5516,5518,5520],{"id":1184,"name":1185,"image":5517,"linkedin":1187,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc88f5dfa-16db-4e6f-acf1-34dd80ee8766\u002Femma_hoppr.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466SQLTIAF2%2F20250507%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250507T143641Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjELb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLXdlc3QtMiJHMEUCIGaaze3jgEdj29BimvIE1eGEt4qcalirK6%2FWgg9g4VXdAiEA2s2DRTx%2FquxcccKRcDDhOWLiQThMIdIwyUz9GvfAyYIq%2FwMIXxAAGgw2Mzc0MjMxODM4MDUiDMRJa%2BxwLownPoG3IyrcA5%2Bszya42miksRJHP6y%2FlvJP9Rckvb5%2BAI1jicKYgmFdOENO%2FfizcRhBrvXjTkyrFKvWZBnN21gnYEGSq5jNjn34c2tdyizCbgS2o4Iu5WcPjpIJL55qIf6%2F2gULe1cRqIOuJ48rDbjX6TePNNqL52G1RRw3Ay3HhnBPfzxTQRnywmDA12ehDGZiszlILVE%2FyYSOV%2FYpd2029bYw9J2bBwctBVGUnSSQ22ofy3DedPAvR7p%2BkMaU09Z5tXNMDsNubkoqZoIc7xFgPy99eOwKHTOVnct0Yf5wPjZM4X%2FcJNk8dw1yh%2FBhkemS2jIEghRtNYYdZqFivTr6TUG12UqCaOIQRGj8GQvnZh3auvGy3OcRU%2F5m1CF9vpAVsW04H%2BwNJMZ6YOmLdwZDYaA7lS4EBrxcb2RCqqRhlgrzEJGR9F0Y0cfGYisH7X4gc7VScJfL82YLsWpZb6UfTMbasF2M6YN4xvJDk%2FDg%2FqsVrPwijuPQ%2FoQJueDdvcN9cxtB2P14rg%2B62hE9ATlJgHe23%2F5rFMUJEDnUPdW3N%2FSjMA01k0MNzm7GugUpCAM0LsEDQkThSwrtHiKKHYWR%2Fh86O7GOdVSHEPgkU29d7%2BtWhX0XWcCK651XvFE9%2FTCg%2B8xSMP%2FN7cAGOqUB4jy0APhdW%2BErX1PpMP0PXNeaUNivHk%2Bm2MRgonNUbdLTSBS3nTkOYx6k6KwlMCi0WF6pf7Lvtmd%2FPlxjoeMEjA5FCC8ssMZFNGE6RrzrrUkUUzi96xWge5H8ZGgwlLwecYDKzalplbbZY0VifoILWWwGrsM%2BvOjv5Q8Pvom5AE85Umx%2BSyZZHDwpM5jMghiKarsWEUX32ZWcY0Wb8dCK8EXN5PGW&X-Amz-Signature=f8d741c680fcbaa2d614ae05ccd4476c1fe5ee5d263e2526fe8c7d933f245601&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":3392,"name":3393,"image":5519,"linkedin":3395,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F53946b9e-3bb9-45bd-a8b4-429c51156179\u002FT04PC176TGB-U05EW3YF61Z-5e129f612df3-512.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466USHIWH35%2F20250507%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250507T143644Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjELb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLXdlc3QtMiJHMEUCIQDI6VFScPT%2BI3I6BD536jpkQPrkeA%2BbPftds4nXdYDgjgIgPnZIPY%2FRgFHtx1tcrXyCWcijN%2FFaO7jtsforzr15Tq4q%2FwMIXxAAGgw2Mzc0MjMxODM4MDUiDCZO8N8P2NAfXTTDOSrcA%2FgkSBnN9EE62zrEPz657yafi0ZOaY5w7xj9f8rGGjDsXVnhiXFe0mwQ9cwejPEnf7A2uTaxdpAnYF%2FXX6C7KgJWAGrtUdb8e6nBQ8w06SJZE1m%2FYzGoeJ4AlWoyZdCK214swZe2daBed6TndMX%2B9zEeoc2nhcIVejUe2ZkLgM8ueR6lVdQtSPthOqecKCOokepMclEaeWdimnZm60VNvco5NT%2BTKUPnxLZjnDpEw6tuTpiLmIICsn%2BnCAfmFU8qXIywCkiyeTHv68Q4lNSqnOruxJnHlaKITMNrePffyviFjY4vMjNazpeFsHbErK%2FilGDyFc7VPH6OyW6IXDJO4M%2BkcBQFhJOy31HlT5Ror3DVIQhtTgCn4vZJKPFRG4mba%2FfizDKlQhAuapwpyPn45L8ju8C4g3OWI%2FCFI5woHaThZJjI%2B7uaD%2F7GzR9%2F66RiErJkGLwbWUY9SIPZFSSA04Et%2BJhX6XGvsjQZ5G5sbR32NL22w4kHGlSwC3cfD8m93L%2FP0uWQyybqRMdaAJqWoMfWzpfnemeQT%2Fd1BTNhVNUyYQreKsKJ99WeK0lDLIMydzExOrMiM%2BfedIYH2kmaVUIMjrW4jf5%2BOrFnWS1XIIZzD6BlR4R5hYwvpBskMI3O7cAGOqUBifel974t2leDwzdqu4SDnKsVyHpK9w7r4RvRWXIbc67J2doJg4W%2FcIEpa1yC%2BO7kw07Ee7rVvRWZQeNliCmms4vMYWDaxLkAjAr4WGRnDN7KRdGakXMlbDBwnO34q6vBy5JJf3b1cGjN1xNgjF1B58IkhScl5hVaI3jDRKFIsUh5vs8DZI%2Fa6XO9rO5MhddzyPKFrnt1efUcDC%2FKaA1mSrxDGbAy&X-Amz-Signature=6a4d4c88d15a7eb01702572dbf16211e542898d3fb8f8cdddf194730c044939d&X-Amz-SignedHeaders=host&x-id=GetObject",{"id":1195,"name":1196,"image":5521,"linkedin":1198,"x":1199},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc69d0b59-558d-4e48-879f-bea3fec1fdef\u002FLinkedin_Profile.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4665EYDPHT7%2F20250507%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250507T143641Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjELb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLXdlc3QtMiJGMEQCIG0EXEyE1pXT6gi9pnqyvkaP8ZxWWRsKmmOieqQiOHpXAiBKg9dgfHm%2BShieFhmyoH%2F2CAADimVSzT3SNmH1o6t0nCr%2FAwhfEAAaDDYzNzQyMzE4MzgwNSIMzTls379yYnws2GePKtwDQ1bS97UeqDzfoF7q%2F7fjhOaJXQ2NqAEHJioVxoHnyN%2F%2BnS85XI0dj1tu0kwUJBDi%2FoVmzKyEhPx0gmwXsGikV7IwoH1vhtbqoyMdZwT7RI2bRS1g8KwZfBx%2FqHpP4z%2Fav1cr9td4ygCOfTcFygVGglIc%2FHoJ9CYzgHyM084pGxGCE1fXi7sXvEhdYSbtoqt1UdcD4C%2FyHIa6V1Xhqy0Uets3uzVTRmBCngj0JCtCsLFPY97Xyo%2BuOzzdlvKSun5VSusI%2BbB%2BoG8X%2F3wB0z58dzt%2F5qvSP21DgQFvWTGMe9qY6oCEOX7G8njBPb7JXZfKvdU91RLKV31auGh4WjvgZ620afFU43C4cxE6PEBOrMfs9Yt8d%2FRNu6%2FeI%2Beh9NwuDJUA1WXyvQvpKfhy0FtcZVqAu%2Fo6Hp%2FD50CDjrJivs8vDzlbWIwlf5IIWdbiKWKfNENreGxD4mo%2Fgn6VogkX6UdszuP4XiI%2FixQ4TZbUOfyZeS6OuvvOXUjiC384%2BCa3Bw3ZIpBxzy3B8cnNG7Jkzq77IjoyRJbc1jhjNaTXVRnnXH3Hznqc9FKQmNLhbg7hEbkIo6GReGjIfC9AZIr7Z3%2BPXk15vUbHjQQbICCrufGfBjmn54%2BG3OrlnCswh87twAY6pgHqm6aayYs46ytFdEbIoqMt%2Buk5pDL85bLMuJuk%2FQnuhd96b95P1xAv4Pu3vtI1osrfD115iKvRbomJyU1Gl3S7SwfFe0mv1c%2FoyEgZiIpCVvI8WzhO5Z9hxUPe8FQALuJu12GBhoQdagyFQvf9Ht1lbftfj00ZJv7ynG4I%2FaDZSd38eBp1M0ygI8wwDDyYlJ8zLYf0DHq4XHAiPSSPLYsEVjKOKVRE&X-Amz-Signature=b8109bc670122dbf25b92481a5b8841c12310f13099ec289ec98af779f14726b&X-Amz-SignedHeaders=host&x-id=GetObject",{"title":4940,"description":5512},"blogs\u002F2025-05-07-lyon-craft-2025-22\u002Findex",[4514,4936,1208],"JGNL2uMKCOn8AXAh_6F39Jmwbc8A14_BylccDlX37AY",{"id":5527,"title":5528,"alt":5529,"authors":5530,"body":5532,"date":5659,"description":5660,"extension":1178,"image":1179,"meta":5661,"navigation":102,"ogImage":1179,"path":5662,"published":102,"reviewers":5663,"seo":5668,"stem":5669,"tags":5670,"__hash__":5672},"blogs\u002Fblogs\u002F2025-07-16-from-scratch-to-craft-lhistoire-dun-programme-de-formation-pas-comme-les-autres\u002Findex.md","From Scratch to Craft : l’histoire d’un programme de formation pas comme les autres","Photo de Edouard pour illustrer le parcours de formation mis en place",[5531],{"id":1542,"name":1543,"image":4300,"linkedin":1545,"x":1188},{"type":16,"value":5533,"toc":5652},[5534,5542,5546,5563,5566,5570,5573,5587,5590,5594,5604,5607,5618,5622,5625,5636,5640,5643],[19,5535,5536,5537,5541],{},"Chez HoppR, la montée en compétences ne se décrète pas, elle se cultive. Et qui\nmieux qu'",[979,5538,5540],{"href":13,"rel":5539},[983],"Édouard Cattez, Head of Craft",", pour incarner cette exigence de qualité et\ncette envie de transmettre ? On est allé lui poser quelques questions sur From\nScratch to Craft, le programme de formation qu’il a conçu et porté tout au long de\nl’année. Un parcours ambitieux, intensément pratique, profondément ancré dans la\nréalité terrain des missions chez nos clients.",[39,5543,5545],{"id":5544},"doù-est-venue-lidée-de-ce-programme","D’où est venue l’idée de ce programme ?",[479,5547,5548],{},[19,5549,5550,5551,5553,5554,5556,5557,5559,5560,5562],{},"J’ai eu l’occasion de participer à de nombreuses formations dans mes expériences",[1581,5552],{},"\nprécédentes. Mais, souvent, les sujets étaient abordés trop en surface. Pas assez",[1581,5555],{},"\nconcret, pas assez challengeant. Alors quand j’ai eu l’opportunité de créer un cycle",[1581,5558],{},"\nchez HoppR, je suis reparti de zéro avec une idée en tête : transmettre ce que",[1581,5561],{},"\nj’aurais aimé recevoir.",[19,5564,5565],{},"From Scratch to Craft est né d’un double besoin : structurer la formation continue\nchez HoppR, et incarner notre vision exigeante du Craft, à la croisée des\ncompétences techniques et du savoir-être. Le programme aborde bien sûr des sujets\npointus (TDD, DDD, architecture...), mais surtout, il forme à ce qui fait vraiment la\ndifférence sur le terrain : la posture, la communication, la conduite du changement.\nParce que le développement ne se résume pas seulement au code, mais à notre capacité à\nfaire équipe, à convaincre, à embarquer.",[39,5567,5569],{"id":5568},"une-boîte-à-outils-technico-humaine","Une boîte à outils technico-humaine",[19,5571,5572],{},"Le programme vise principalement les consultants tech (développeurs, testeurs,\narchitectes), mais touche aussi les Product Owners. Il propose un enchaînement\nlogique de modules, avec une vraie progression sur l’année.",[479,5574,5575],{},[19,5576,5577,5578,5580,5581,5583,5584,5586],{},"Chaque thème est abordé de manière hybride : 1 à 2 jours de formation (dont 70%",[1581,5579],{},"\nde pratique), puis on revient dessus un mois plus tard avec un cas client réel en",[1581,5582],{},"\nintroduisant les katas, des exercices répétés pour gagner en fluidité, en réflexe.",[1581,5585],{},"\nCar c’est la pratique qui fait l’expert !",[19,5588,5589],{},"Et bientôt, les katas soft skills feront leur apparition: vulgarisation, diplomatie, réaction sous pression, … autant de sujets qui serviront à muscler les compétences transverses.",[39,5591,5593],{"id":5592},"formation-et-esprit-déquipe","Formation, et esprit d’équipe",[19,5595,5596,5597,5600,5601],{},"Comme partout, la formation a beaucoup de vertus. Mais chez HoppR, c’est aussi\nun temps fort pour les équipes. Faire collectif, quand on est chacun impliqués dans\ndes missions différentes sur le terrain, ça n’est pas toujours facile. C’est rare de tous\nse retrouver. Ce programme de formation nous permet ces temps de partage et aussi\nde convivialité. Cela nourrit le sentiment d’appartenance et ça consolide notre collectif,\n",[1311,5598,5599],{},"fait rare à souligner, dans le monde des ESN"," ",[1311,5602,5603],{},"!",[19,5605,5606],{},"Le format hybride de From Scratch to Craft, pensé pour durer, intègre également une\ndimension collaborative forte : les consultants sont invités à co-construire les\nmodules.",[479,5608,5609],{},[19,5610,5611,5612,5614,5615,5617],{},"Quand Bastien m’a dit qu’il voulait enrichir la partie front-end, je lui ai répondu :",[1581,5613],{},"\n”Super, tu vas la faire !”. Il est devenu le premier à rejoindre l’équipe pédagogique. Et",[1581,5616],{},"\nil ne sera pas le dernier.",[39,5619,5621],{"id":5620},"un-programme-vivant-au-service-du-terrain","Un programme vivant, au service du terrain",[19,5623,5624],{},"La vocation de From Scratch to Craft ne s’arrête pas aux frontières de HoppR. Le programme s’ouvre aussi à l’extérieur : des sessions sur-mesure sont proposées chez les clients, et 1 à 2 personnes extérieures peuvent rejoindre certaines de nos sessions. Une façon de faire rayonner la vision Craft HoppR au-delà de nos murs.",[479,5626,5627],{},[19,5628,5629,5630,5632,5633,5635],{},"Ce que j’aime, c’est que ce programme est en constante évolution. Il vit, il",[1581,5631],{},"\ns’améliore, il m’oblige à me remettre en question. Les participants me challengent, et",[1581,5634],{},"\nc’est tant mieux.",[39,5637,5639],{"id":5638},"en-conclusion","En conclusion ?",[19,5641,5642],{},"From Scratch to Craft, c’est plus qu’un programme de formation : c’est un levier de\ntransformation. Une réponse concrète à notre volonté de cultiver l’excellence, de\njouer collectif, et de faire grandir chacun au service des projets de demain.",[19,5644,5645,5646,5651],{},"Tu souhaites nous rejoindre ou en savoir plus sur le programme de formation, ",[979,5647,5650],{"href":5648,"rel":5649},"https:\u002F\u002Fwww.hoppr.tech\u002Fcontact",[983],"contactes-nous"," pour qu’on en discute !",{"title":63,"searchDepth":86,"depth":86,"links":5653},[5654,5655,5656,5657,5658],{"id":5544,"depth":86,"text":5545},{"id":5568,"depth":86,"text":5569},{"id":5592,"depth":86,"text":5593},{"id":5620,"depth":86,"text":5621},{"id":5638,"depth":86,"text":5639},"2025-07-16T20:45:04.885Z","Chez HoppR, la montée en compétences ne se décrète pas, elle se cultive. Et qui mieux qu'[Édouard Cattez, Head of Craft](https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fedouard-cattez-865794133\u002F), pour incarner cette exi",{},"\u002Fblogs\u002F2025-07-16-from-scratch-to-craft-lhistoire-dun-programme-de-formation-pas-comme-les-autres",[5664,5666],{"id":1190,"name":1191,"image":5665,"linkedin":1193,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F82ebd0fe-de28-43f3-ab7b-0431af41baad\u002FPhoto_HoppR.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466U7TKQO3I%2F20250716%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250716T204504Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEsaCXVzLXdlc3QtMiJHMEUCICDbo4xi5KAjQwIagyPdYh99gyhgxwCFXDh%2FtlctDquUAiEAxDVnKiTszM9vbXOYsJ9pp8IGwqaV1Lw05S6jMAh1gfAq%2FwMIZBAAGgw2Mzc0MjMxODM4MDUiDDyJGyVa%2Bq8DAPx4EyrcA1s28cZsrdqO6XV%2FHwdE4DKwmlnCmZ33wNtXYoKt7tyBTdZBYKAKa0EUpSRaVcfMt6BPuF93MzaITpZdT5kDy89U74TkFIu8n66ORUDa5ncWNd%2BRO5sqM3ro27RJzw3B9rxmKccbYaATUEc6j2RTRdqPGfB86nSMIvtdgBBLmG4INhEEVsyBGvu7pK5%2FuQNom0Z96yCwc%2BI2H2KvfIKYHNE%2Fi5aAJ8Ue8qinrx7whK%2FhMuC%2F04aeD1jtUmuMoBrx%2B4ey8ykZysOA%2F%2FF7Y3%2F5%2BKmxZ5aHgX7ujJnjSpf5XDavGwziiSyiYbeP0iv7UpEy1Z6qiw1PcVR6yonJelCGcmqyJrpzJdnTBQmD7eiKQjKEO6U7WYVdhtrwotj%2BLo%2Fi4fBZ4Cl6NqeZ5PLtz4eh1Rg3Ij3mSXEmsvQsiSGhHCE6C12BY%2Fes448t%2FpzoYKM6ydv8EAnXrhSwHFU2Ul54jF%2FA6VvultCk%2BmHelpa9camWTVKvQqluR6yPYQFmVIUuROMFbR4mFN3ofYM2qg1V4AdJQC4vK%2FTT82KqxaWc%2BsW5YvXKRFYjYrQg9wtCe6VQAP9wPqUtROyfmP3DnmD8%2FMBsxW0g0JCPo8qpZleQXCayQB8r7ub%2F%2BlNGQ1fZMJnx38MGOqUBkptv2EzREwYg4l4XXTLxOmeSXv9U4OQXpNpXJ4B8odw1UykQ5U4RnOZWb4RvvdzgXRTt%2BqJ73bWwk6Hg4K%2FC300uUfylOk2rYU%2FU0Qu2e%2FDwGNBab%2FtpKayFVsv0euYJC8v5Fcn7466msz1ldykBni9%2FaruPw1f7WrCwWIkIsZervdb%2BR61TnhYEgn%2B3thOj7Nhbif%2BjJWMQ6bz7CfhO200F8h6b&X-Amz-Signature=540d13d7cc41550adf5daa489136f608ef50963b854db5adf064af742449f811&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"id":3392,"name":3393,"image":5667,"linkedin":3395,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F53946b9e-3bb9-45bd-a8b4-429c51156179\u002FT04PC176TGB-U05EW3YF61Z-5e129f612df3-512.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466722KP3K4%2F20250716%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250716T204504Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEsaCXVzLXdlc3QtMiJHMEUCIB5GvSuuDzEYfv4i2hCyYt%2BYu9OJo7gYBfTc2QldnDYgAiEAzjQLeem2Wv2i2NuM2gknT3WMTqU%2BPx%2FhKdmBGzirQWIq%2FwMIZBAAGgw2Mzc0MjMxODM4MDUiDDNKg4BprosexZXFoyrcAwoy426ercVBglCE4GA23T0k2Ler9M56hfivSgUctexU43GQu1VPISO40kjkNddR1zLLcQ1xJDsYYeWyZ1nKwNx4ubdIsXAu20KIMpsi4NZCljjXn4PO1zoeZuurVY17hOJ3EMkiV%2Bf4o39RyGLdHLBs%2BIQXTVivwuoTnzfrulfCCbaScUq20Hf8UrHSzLqtaCWCn07IjQViNT9znpvFd8Q%2BioGfGN84Fm2zVZwl20Yx10mny6IsD4GUu%2FkIRM9UVrm4S%2BxNBFQmTxncWbSLSumJYdJAN%2Ffj8v6qgsPjXX1YCYBbglbnu%2BS1o4ppZBdYbPXKasyNNdKE7SfWpYhgrKXwGJROyvNC3oqFT%2BY5RnZKZicIItjju%2BYR7%2BxDnfqzgFJXBHn1s%2FXqbGSQ%2BeKd5JN%2Frg1tkdXfXv1uf1Hl%2BeWUJh%2B6hI4BD190y7ZoVvlv%2BvAwa9XXXiSV4nfj62pj9J83ZIdY7NOJCTzEohuqlF8CjQf7CZYXluJDaoSwwISUmfGQCO4hmCj6ga3wozNSZ2PaZw7m4FYeEKMK7fp2X554bMQtKlJX8ybTdivQJOSNOGRbvs%2BTG1Z95WrTstAwhqvJ4KfyD2uoIEbWuBr58BvqeJVOFrkR1y%2BAiPKAMIXx38MGOqUBaHdkT3H%2FYUC9up1N8bRUAnePswcVfVTi%2BLVhi8%2BE%2BYC%2BjUqCPtrB6p0Kffrlfqtz5Mabc8mKeg7na6SPChM6rZyPBjFoexdW06d3DONP8bHg%2FmNhs858Ut%2F%2B9sKukMdo%2F%2Bi6jvnMLj05IK4JLSR316cXkhwG06JopOdF3Zz0DqI%2FAQggtCSx5O6ngP78c%2BMljnP06J5cT1KE5pMQpgDnWAbuhTwQ&X-Amz-Signature=0c38dd95cbd6ed09ac9ca54775344260cdea0491685ca2bc849d653ae577b709&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"title":5528,"description":5660},"blogs\u002F2025-07-16-from-scratch-to-craft-lhistoire-dun-programme-de-formation-pas-comme-les-autres\u002Findex",[1208,5671],"formation","Q7gBesYsq195cVV6WdoQPxFK6hsJAHwrE522TSvhFog",{"id":5674,"title":5675,"alt":5676,"authors":5677,"body":5685,"date":5879,"description":5880,"extension":1178,"image":1179,"meta":5881,"navigation":102,"ogImage":1179,"path":5882,"published":102,"reviewers":5883,"seo":5890,"stem":5891,"tags":5892,"__hash__":5896},"blogs\u002Fblogs\u002F2025-08-01-refactorer-sans-casser-le-golden-master-appliqu-un-outil-finops\u002Findex.md","Refactorer sans casser : le Golden Master appliqué à un outil FinOPS","Illustration d’un Golden Master sous forme de disquette",[5678,5683],{"id":5679,"name":5680,"image":5681,"linkedin":5682,"x":1188},"23ff4462-cd38-8078-b550-c960c1e6ebdb","Sandrine Miras",".\u002Fassets\u002Fauthor-sandrine-miras.webp","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fsandrine-miras\u002F",{"id":3392,"name":3393,"image":5684,"linkedin":3395,"x":1188},".\u002Fassets\u002Fauthor-tho-lanord.webp",{"type":16,"value":5686,"toc":5871},[5687,5692,5695,5698,5701,5705,5716,5725,5737,5742,5746,5753,5758,5762,5767,5784,5791,5797,5801,5806,5814,5819,5839,5843,5854,5858,5865,5868],[5688,5689,5691],"h1",{"id":5690},"refactorer-sans-casser-le-golden-master","Refactorer sans casser : le Golden Master",[19,5693,5694],{},"appliqué à un outil FinOPS",[19,5696,5697],{},"Refondre un outil critique sans casser l’existant ? Pas simple.\nLa mission était claire : continuer à faire évoluer la solution FinOPS de notre client tout en garantissant que chaque chiffre affiché reste juste. Parce qu’ici, une erreur, ça ne se compte pas en lignes de code… mais en euros.",[19,5699,5700],{},"Pour sécuriser le projet, Théo Lanord, consultant et développeur Full Stack chez HoppR, a choisi une arme simple et efficace : le Golden Master. Une pratique Craft qui n’est pas systématiquement utilisée, mais qui peut sauver un projet. On lui a demandé : pourquoi cette méthode, et qu’est-ce que ça change concrètement ?",[39,5702,5704],{"id":5703},"théo-cétait-quoi-le-contexte-quand-tu-es-arrivé-sur-le-projet","Théo, c’était quoi le contexte quand tu es arrivé sur le projet ?",[19,5706,5707,5710,3570,5713],{},[1311,5708,5709],{},"Théo :",[1613,5711,5712],{},"« J’ai rejoint l’équipe tech d’un acteur majeur de la distribution présent dans plusieurs pays, qui développe et maintient des outils internes pour les entreprises du groupe.",[1613,5714,5715],{},"Leur mission est de fournir des services fiables et industrialisés à toutes les équipes de développement du groupe, pour qu’elles puissent livrer rapidement et en toute sécurité.",[19,5717,5718,5721,5722],{},[1613,5719,5720],{},"Pour ça, on s’appuie sur un écosystème"," allant de ",[1613,5723,5724],{},"GitHub pour le versioning, GitHub Actions pour automatiser les déploiements, SonarQube pour analyser la qualité du code, Ghas pour la sécurité, JFrog pour gérer les images, Jira pour le suivi et enfin Confluence pour la doc. Bref, un environnement complet mais complexe, où tout doit s’enchaîner sans faille.",[19,5726,5727,3570,5730,5733,5734],{},[1613,5728,5729],{},"Dans ce contexte, l’outil FinOPS est un produit clé. Il analyse la consommation des ressources Cloud et la traduit en euros permettant aux différentes entités du groupe de savoir combien elles dépensent et sur quoi.",[1613,5731,5732],{},"L’enjeu est fort"," puisque chaque entité ",[1613,5735,5736],{},"a ses propres projets, ses propres besoins, et doit pouvoir maîtriser ses coûts.",[19,5738,5739],{},[1613,5740,5741],{},"Avant, il y avait un budget global pour tout le monde. Aujourd’hui, on est dans une logique de refacturation interne par Business Unit. Ça change tout : chaque équipe doit rendre des comptes et optimiser sa consommation. Et cet outil est le cœur de ce pilotage. »",[39,5743,5745],{"id":5744},"où-se-situait-le-challenge-principal","Où se situait le challenge principal ?",[19,5747,5748,5750],{},[1311,5749,5709],{},[1613,5751,5752],{},"« Le problème, c’est que l’entreprise utilisait déjà un outil FinOPS au quotidien. Mais on devait le faire évoluer sans compromettre son fonctionnement actuel. Or, il y avait peu de tests automatisés pour garantir que les comportements en place resteraient corrects.",[19,5754,5755],{},[1613,5756,5757],{},"Quand tu touches à un code hérité sans filet de sécurité, chaque modification est un pari risqué. Tu peux introduire des régressions invisibles… qui auront un impact direct sur la facturation et les arbitrages budgétaires. Et là, l’erreur ne se compte pas en millisecondes de temps de calcul mais en milliers d’euros. »",[39,5759,5761],{"id":5760},"pourquoi-avoir-choisi-le-golden-master","Pourquoi avoir choisi le Golden Master ?",[19,5763,5764],{},[1613,5765,5766],{},"« Parce qu’on avait besoin de figer le comportement actuel avant d’aller plus loin. Le Golden Master, c’est une pratique simple mais redoutablement efficace :",[23,5768,5769,5774,5779],{},[26,5770,5771],{},[1613,5772,5773],{},"On capture l’existant en enregistrant les entrées et sorties du système.",[26,5775,5776],{},[1613,5777,5778],{},"On crée une “photo” de référence qui servira de point de comparaison.",[26,5780,5781],{},[1613,5782,5783],{},"À chaque évolution, on compare les résultats à cette référence pour détecter toute régression.\nEn clair, c’est comme mettre un miroir en face du logiciel : tant que l’image reste identique, tu sais que tu n’as rien cassé. Ça nous a permis de refactorer, modulariser et optimiser le code en toute sérénité, sans réécrire tout le produit. »",[19,5785,5786,5787,5790],{},"👉 ",[1311,5788,5789],{},"Pour résumer",", on peut définir le Golden Master comme une méthode de validation utilisée principalement dans les tests de non-régression. Elle consiste à conserver une version de référence du comportement attendu d’un système – souvent sous forme de données d’entrée et de sortie – afin de pouvoir comparer automatiquement les résultats des versions futures et garantir qu'aucune modification n’altère le fonctionnement prévu.",[19,5792,5793],{},[1374,5794],{"alt":5795,"src":5796},"Mise en avant de l’importance de la comparaison des output dans le Golden Master","\u002Fcontent-assets\u002F2025-08-01-refactorer-sans-casser-le-golden-master-appliqu-un-outil-finops\u002Fassets\u002Fimg1.webp",[39,5798,5800],{"id":5799},"la-méthode-du-golden-master-est-parfois-critiquée-était-elle-la-meilleure-solution-dans-ce-contexte","La méthode du Golden Master est parfois critiquée. Était-elle la meilleure solution dans ce contexte ?",[19,5802,5803],{},[1613,5804,5805],{},"« Si tu pars from scratch, tu peux faire autrement. Mais dans une situation comme celle-ci, un outil critique, déjà en production, avec des impacts financiers, c’est une assurance vie. Le Golden Master, c’est comme mettre un filet sous un funambule : tu avances serein, tu sais que même si tu tombes, tu ne te blesseras pas.",[19,5807,5808,3570,5811],{},[1613,5809,5810],{},"A noter que",[1613,5812,5813],{},"ça ne remplace pas les tests unitaires ni le TDD. Ça vient en complément, comme un filet de sécurité quand tu n’as pas d’historique fiable. Et ça s’inscrit parfaitement dans une logique Craft : on ne fait pas de la qualité “pour faire joli”, on le fait pour apporter de la confiance et réduire le risque métier. »",[19,5815,5816],{},[1311,5817,5818],{},"👉 Le Golden Master en 3 points clés :",[23,5820,5821,5827,5833],{},[26,5822,5823,5826],{},[1311,5824,5825],{},"Objectif :"," prévenir les régressions dans un code hérité.",[26,5828,5829,5832],{},[1311,5830,5831],{},"Quand l’utiliser :"," reprise de legacy sans confiance dans les tests actuels.",[26,5834,5835,5838],{},[1311,5836,5837],{},"Limite :"," il ne valide pas la justesse métier, mais garantit la stabilité des comportements existants.",[39,5840,5842],{"id":5841},"et-concrètement-quel-a-été-limpact-pour-le-client","Et concrètement, quel a été l’impact pour le client ?",[19,5844,5845,3570,5848,3570,5851],{},[1613,5846,5847],{},"« On a pu faire évoluer le produit plus efficacement, avec moins de stress, parce qu’on savait qu’on ne casserait rien d’essentiel. L’équipe a gagné en sérénité, le client en fiabilité. Et au-delà de la technique, ça a changé notre",[1613,5849,5850],{},"posture",[1613,5852,5853],{},"en tant que consultant et celle de notre client en tant qu’utilisateur: on peut refactorer en toute confiance, innover sans risquer de mettre en péril la facturation. »",[39,5855,5857],{"id":5856},"si-tu-devais-résumer-pourquoi-le-craft-est-indispensable-dans-ce-genre-de-projet","Si tu devais résumer, pourquoi le Craft est indispensable dans ce genre de projet ?",[19,5859,5860,5862],{},[1311,5861,5709],{},[1613,5863,5864],{},"« Parce que ça crée de la confiance. Pour les devs, qui savent qu’ils peuvent intervenir sans crainte. Pour le client, qui sait que son outil critique ne tombera pas à cause d’une évolution mal contrôlée. Et cette confiance, ça se traduit par plus de qualité, plus de sérénité et plus de vitesse. »",[19,5866,5867],{},"Chez HoppR, on croit que la qualité n’est jamais un luxe, mais une assurance. Le Golden Master est une des pratiques que nous utilisons, mais pas la seule : TDD, pair programming, revue de code… autant de leviers pour livrer des solutions robustes.",[19,5869,5870],{},"Merci de votre lecture !",{"title":63,"searchDepth":86,"depth":86,"links":5872},[5873,5874,5875,5876,5877,5878],{"id":5703,"depth":86,"text":5704},{"id":5744,"depth":86,"text":5745},{"id":5760,"depth":86,"text":5761},{"id":5799,"depth":86,"text":5800},{"id":5841,"depth":86,"text":5842},{"id":5856,"depth":86,"text":5857},"2025-08-01T14:37:21.374Z","appliqué à un outil FinOPS  Refondre un outil critique sans casser l’existant ? Pas simple. La mission était claire : continuer à faire évoluer la solution FinOPS de notre client tout en garantissant ",{},"\u002Fblogs\u002F2025-08-01-refactorer-sans-casser-le-golden-master-appliqu-un-outil-finops",[5884,5887,5889],{"id":5885,"name":5886,"image":1188,"linkedin":1188,"x":1188},"197f4462-cd38-801b-859a-c33742e0ed0d","Pierre-Emmanuel Denys",{"id":4287,"name":4288,"image":5888,"linkedin":4289,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F27c18bae-6c33-403c-b7fd-7d46ce96c376\u002FGuillaume_Ferlin_Image.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466ULFPFZUH%2F20250801%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20250801T143721Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEMb%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLXdlc3QtMiJGMEQCIG1mqZcKiAMfFxyUrFUGHfaT4CcUgj%2BLoBszrMnBcKdXAiAzDBfUqsdSt4Q4ipOxsQZieesJXoa7qrGJlD350URqQiqIBAjv%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F8BEAAaDDYzNzQyMzE4MzgwNSIMdcI0v36Q64VtIlbtKtwDL%2BNrlFhdx1Ke4l94CtVPwXsIzuwNu4ZcUAnnbi29HkBC9nOvGrDrEzbdydY0p6ZBhhd3md7q1HhIgSeKa7dyJN84TeFlkYMgZCqmV2unHi2B%2FOvARfm7%2FhpF7VKbXYFsssXA7%2Bojou0hb%2BeRXUjjqCHJ%2B4l%2B7wkgzufa9aS4OaGfoJj%2Bld06GnqJMjNrzMpCcjxyzOo5BUDGxv8H8Q%2FEZCQKeeG%2B%2BlffOAHpbcaa4YGG6ebhh4PwCZNiJtrMsLlTT076jFJEYyG%2FIUmyXY4pwPYuhX3kceWV2%2FFdZ7xLvvLK3STrjdcHfinSzf2bvjKmSInnRVf27B7QpS0SlvzQBtEX2g4EI7bUDbw2z5x4pgPfhceoX6R2yZFzaC48izTkcsmmwOQLDY6Rl8iAShSURx%2FM1JLq0ThwFdiAosmqhKfgxrbfOMr9KcPyfPdXPWmstqwaMS%2BJdk19a21v0ttyf84J3D6hCTKffSJFn7jsqJcyq78gEqO%2BTUkrs%2BAgLlQSz7kXSXYTkSmI8xTtl8FYscnz61uH2KOCBJlHLjZMCm4ElhjNho1hCnj9FJOQQfXHxJzBtxnkb%2Bs%2B9BAcvtzVNIdr0zxoPc5qttMsp9p88RALtgdpzRGjaWXRUtYwkZazxAY6pgFtPfWHqkUKY%2B2ZtC9UZtQu8d9o%2BD1i%2Bqs2B8iimvVqpJnlCAkGL3KSV%2FlDPd0PrFfrsiTf1gF4CPLtVb1N68fdMMUv8TCpVC0sFfHn0jeqyzT3FBPezG14nCfqkWX8j9%2BjSxTk7XNBg1y5YP0c0GoLFvWODluLCeivL8O%2Fq%2BwV8BmSxNj8HIaH5NBPkmvFN13vcIrHIoEQ41kIRqSdpc5l8AttcKt3&X-Amz-Signature=b37ba0b7d8ebcfef63a5b3d2f7b75ff8c4c2539d2f6e8b41d1a5d76e62704901&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"id":4509,"name":4510,"image":1188,"linkedin":1188,"x":1188},{"title":5675,"description":5880},"blogs\u002F2025-08-01-refactorer-sans-casser-le-golden-master-appliqu-un-outil-finops\u002Findex",[1208,5893,5894,5895],"veille tech","test","testing","ZT0RlBGevXEZHXtRzxGch5IAXtpbuMXUx09jvD3lJyI",{"id":5898,"title":5899,"alt":5900,"authors":5901,"body":5903,"date":6344,"description":6345,"extension":1178,"image":1179,"meta":6346,"navigation":102,"ogImage":1179,"path":6347,"published":102,"reviewers":6348,"seo":6357,"stem":6358,"tags":6359,"__hash__":6361},"blogs\u002Fblogs\u002F2025-12-02-alors-ctait-comment-ce-premier-devfest-lyon\u002Findex.md","Alors ? C’était comment ce premier DevFest Lyon ?","L’équipe HoppR sur la scène du DevFest Lyon",[5902],{"id":1190,"name":1191,"image":1560,"linkedin":1193,"x":1188},{"type":16,"value":5904,"toc":6337},[5905,5913,5924,5927,5933,5936,5939,5942,5946,5951,5954,5969,5978,5984,5987,6009,6015,6018,6021,6025,6030,6039,6064,6072,6075,6081,6084,6087,6102,6105,6132,6135,6149,6152,6156,6161,6170,6173,6176,6182,6185,6196,6199,6207,6211,6216,6225,6233,6236,6242,6245,6260,6273,6276,6301,6304,6307,6311,6314,6317,6328,6331,6334],[19,5906,5907,5908,393],{},"Une question que l’on me pose beaucoup en ce début de semaine. En effet, vendredi 28 novembre 2025, nous avons eu la chance, avec plusieurs collègues de HoppR, de faire partie des 250 participants de la première édition du ",[979,5909,5912],{"href":5910,"rel":5911},"https:\u002F\u002Fdevfest.gdglyon.com\u002F",[983],"DevFest Lyon",[19,5914,5915,5916,5102,5920,2914],{},"En effet, notre participation s’inscrit dans le cadre de la veille tech proposée, organisée et prise en charge par HoppR. C’est d’ailleurs comme cela que j’avais pu participer au Lyon Craft plus tôt cette année (et écrire deux articles sur ce blog, à retrouver ",[979,5917,5919],{"href":4951,"rel":5918},[983],"ici",[979,5921,5919],{"href":5922,"rel":5923},"https:\u002F\u002Fblog.hoppr.tech\u002Fblogs\u002F2025-05-07-lyon-craft-2025-22",[983],[19,5925,5926],{},"Avant de rentrer dans le vif du sujet, nous sommes tous unanimes chez HoppR pour saluer l’organisation au poil de l’évènement. Nous avons toutes et tous été très bien accueillis, et nous avons passé un moment de qualité avec nombre d’acteurs importants de la scène tech lyonnaise.",[19,5928,5929],{},[1374,5930],{"alt":5931,"src":5932},"Photo du programme de la journée, directement dans le badge nominatif délivré à l’accueil de l’évènement","\u002Fcontent-assets\u002F2025-12-02-alors-ctait-comment-ce-premier-devfest-lyon\u002Fassets\u002Fimg1.webp",[19,5934,5935],{},"Nous voici donc arrivés à cette journée de conférences, réparties sur deux tracks, et donc des choix difficiles à faire tant les talks étaient prometteurs.",[19,5937,5938],{},"Beaucoup de thématiques étaient proposées : Data & IA, Frontend, Backend, Software Architecture, Cloud, Infra et DevOps, il y en avait pour tous les goûts. On notera cependant une forte présence de la première thématique : la datascience et les LLM ont la côte !",[19,5940,5941],{},"Je vous propose ici de vous (re)plonger dans celles auxquelles j’ai pu assister en cette froide matinée lyonnaise.",[39,5943,5945],{"id":5944},"keynote-datascience-for-performance","Keynote : Datascience for performance",[19,5947,5948],{},[1613,5949,5950],{},"Par Joseph MESTRALLET - Data & AI - 30 minutes",[19,5952,5953],{},"Après la traditionnelle ouverture par le staff devant une salle pleine, avec remerciement des sponsors et rappels de l’organisation, Joseph prend la parole pour une keynote surprise.",[19,5955,5956,5957,5962,5963,5968],{},"Son CV est impressionnant : il accompagne certains des plus grands champions, comme ",[979,5958,5961],{"href":5959,"rel":5960},"https:\u002F\u002Fwww.equipedefrance.com\u002Fathlete\u002Fquentin-fillon-maillet",[983],"Quentin Fillon Maillet"," (5 médailles de biathlon aux JO de Pékin 2022, dont 2 en or) ou ",[979,5964,5967],{"href":5965,"rel":5966},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FRuth_Croft",[983],"Ruth Croft"," (vainqueure de l’Ultra-Trail du Mont-Blanc 2025). Et comment fait-il pour aider ces grands champions ? Par la data, par la science, par la datascience !",[19,5970,5971,5972,5977],{},"Joseph nous fait un petit historique des liens entre sport de haut niveau et science, de l’amélioration des photos finish à l’exploit du ",[979,5973,5976],{"href":5974,"rel":5975},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FIneos_1:59_Challenge",[983],"marathon en moins de 2h de Kipchoge",". La densification des performances au plus haut niveau a nécessité une nouvelle approche basée sur la science et la data, pour gagner encore les quelques secondes, les quelques centimètres qui font la différence entre un champion olympique et les finalistes qui resteront inconnus du grand public.",[19,5979,5980],{},[1374,5981],{"alt":5982,"src":5983},"Ruth Croft à l’arrivée de l’UTMB en 2025","\u002Fcontent-assets\u002F2025-12-02-alors-ctait-comment-ce-premier-devfest-lyon\u002Fassets\u002Fimg2.webp",[19,5985,5986],{},"Mais revenons au travail de Joseph, qui nous parle de quatre niveaux de datas, toujours plus précis:",[23,5988,5989,5992,5995,6003],{},[26,5990,5991],{},"Les données issues de la littérature",[26,5993,5994],{},"Les données personnalisées, via des applications de suivi comme Strava par exemple",[26,5996,5997,5998],{},"Le ",[979,5999,6002],{"href":6000,"rel":6001},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FR%C3%A9glage_fin",[983],"fine tuning",[26,6004,6005,6006],{},"Le compute, pour trouver les meilleures conditions pour une course via un ",[1613,6007,6008],{},"digital twin",[19,6010,6011,6012,6014],{},"Cette notion de ",[1613,6013,6008],{}," revient souvent : il s’agit d’un avatar numérique de l’athlète, que l’on fait participer virtuellement des centaines de fois à la prochaine course, avec des paramètres différents, pour trouver la meilleure stratégie à adopter.",[19,6016,6017],{},"Joseph nous parle également de son quotidien aux côtés des athlètes. Ceux-ci doivent être très impliqués au quotidien pour obtenir de la data de la meilleure qualité possible. A l’inverse, une grande confiance avec lui doit être construite, il s’est donc lui aussi mis à la course à pied de manière intensive pour gagner de la connaissance du terrain et de la légitimité.",[19,6019,6020],{},"Le speaker, visiblement passionné, égraine les anecdotes de ses championnes et champions, et montre à quel point ce travail de performance demande une communication et une proximité forte entre le scientifique et les sportifs.",[39,6022,6024],{"id":6023},"démêler-vrais-produits-et-hallucinations-rex-dun-agent-téléphonique-chercheur-de-chaussettes","Démêler vrais produits et hallucinations, REX d’un agent téléphonique chercheur de chaussettes",[19,6026,6027],{},[1613,6028,6029],{},"Par Marie TERRIER - Data & AI - 50 minutes",[19,6031,6032,6033,6038],{},"Marie est CTO d’une ",[979,6034,6037],{"href":6035,"rel":6036},"https:\u002F\u002Fwww.yelda.ai\u002F",[983],"start-up"," proposant un SaaS d’agent vocaux. Cette société a proposé des solutions avant l’avènement des LLMs, et a suivi celui-ci pour présenter des solutions plus performantes. Ainsi, nous faisons un tour de l’historique :",[23,6040,6041,6058,6061],{},[26,6042,6043,6044,6051,6055,6057],{},"2017 : Le Machine Learning existe déjà depuis longtemps, mais les LLMs n’en sont qu’à leurs balbutiements. L’agent vocal est alors basé sur du ",[979,6045,6048],{"href":6046,"rel":6047},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FTraitement_automatique_des_langues",[983],[1613,6049,6050],{},"natural language processing",[979,6052,6054],{"href":6046,"rel":6053},[983]," (NLP)",[1613,6056,393],{}," On essaie de déterminer l’intention du client au bout du fil, et de proposer une réponse pré-généré en fonction. Et si on y parvient pas, alors on demande à l’utilisateur de répéter. Un bon début, mais ce n’est pas très pratique.",[26,6059,6060],{},"2022 : Les LLMs sont utilisés pour ces cas de questions inattendues, et permettent donc de combler le besoin. La fin de l’histoire ?",[26,6062,6063],{},"Fin 2023 : Une nouvelle commande arrive, avec pour problématique la suivante : l’IA peut elle aider les pharmaciens à commander des produits par téléphone ?",[19,6065,6066,6067,2672],{},"A priori, rien de bien sorcier maintenant que nous avons des outils adaptés et efficaces. Mais finalement, c’est la douche froide. Une liste de 40 produits seront proposés, et ceux-ci posent de nombreux problèmes : nous avons ici affaire avec du franglais, sans aucun standard, avec des notions complexes et difficilement transcriptibles comme le ",[979,6068,6071],{"href":6069,"rel":6070},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FMillim%C3%A8tre_de_mercure",[983],"mmHg",[19,6073,6074],{},"Difficile ainsi de trouver la bonne paire de chaussettes ou de bas de contention à commander…",[19,6076,6077],{},[1374,6078],{"alt":6079,"src":6080},"Marie nous présente un échantillon de la liste des 40 produits qui seront disponibles à l’achat. On comprend tout de suite la problématique.","\u002Fcontent-assets\u002F2025-12-02-alors-ctait-comment-ce-premier-devfest-lyon\u002Fassets\u002Fimg3.webp",[19,6082,6083],{},"De plus, les pharmaciens ne connaissent pas le nom complet de chaque produit, il est donc nécessaire de poser des questions pour être sûrs de comprendre, tout en étant rapide étant donné que nous sommes au téléphone. Le service existant alors est inadapté, tout est trop long, et le LLM pose trop de questions pour arriver au produit désiré.",[19,6085,6086],{},"La solution passe par des sous-agents, qui permettent d’avoir plus de pertinence dans les questions posées tout en étant plus rapide. C’est donc parfait ! Enfin jusqu’à ce que le catalogue passe de 40 à 20000 références dont certaines en allemand…",[19,6088,6089,6090,6095,6096,6101],{},"Nous sommes en 2023, et les LLMs disposent encore de limites, comme de petites ",[979,6091,6094],{"href":6092,"rel":6093},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FFen%C3%AAtre_de_contexte",[983],"fenêtres de contexte"," (8000 tokens puis le modèle “oublie”), une latence trop importante (difficile d’attendre 30s au téléphone) et des ",[979,6097,6100],{"href":6098,"rel":6099},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FHallucination_(intelligence_artificielle)",[983],"hallucinations"," fréquentes.",[19,6103,6104],{},"Plusieurs essais sont donc effectués :",[23,6106,6107,6115,6124],{},[26,6108,6109,6114],{},[979,6110,6113],{"href":6111,"rel":6112},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FG%C3%A9n%C3%A9ration_%C3%A0_enrichissement_contextuel",[983],"RAG"," et prompt dynamique, un bon début mais toujours trop lent et ne gère que 80 produits max lors des tests",[26,6116,6117,6118,6123],{},"Une ",[979,6119,6122],{"href":6120,"rel":6121},"https:\u002F\u002Ffr.wikipedia.org\u002Fwiki\u002FBase_de_donn%C3%A9es_vectorielle",[983],"base de données vectorielle",", mais beaucoup de produits ont des noms trop proches sémantiquement, et chaque requête donne trop de retours",[26,6125,6126,6131],{},[979,6127,6130],{"href":6128,"rel":6129},"https:\u002F\u002Fwww.algolia.com\u002Ffr",[983],"Algolia",", un moteur de recherche boosté à l’IA, qui donne de bons résultats",[19,6133,6134],{},"La solution se construit alors avec plusieurs briques comme ceci :",[23,6136,6137,6140,6143,6146],{},[26,6138,6139],{},"LLM extracteur, qui fournit un nom de produit partiel",[26,6141,6142],{},"Algolia, permettant d’effectuer la recherche",[26,6144,6145],{},"LLM conversationnel",[26,6147,6148],{},"LLM évaluateur",[19,6150,6151],{},"Ainsi, Marie et son équipe ont pu proposer à leur client un outil répondant pleinement au besoin : celui de permettre à des pharmaciens de commander des produits… dont les fameuses chaussettes du titre !",[39,6153,6155],{"id":6154},"lets-play-factorio","Let’s play Factorio",[19,6157,6158],{},[1613,6159,6160],{},"Par Julien WITTOUCK - Software Architecture - 50 minutes",[19,6162,6163,6164,6169],{},"Julien est un grand fan du jeu ",[979,6165,6168],{"href":6166,"rel":6167},"https:\u002F\u002Ffactorio.com\u002F",[983],"Factorio",". Ce jeu bac-à-sable consiste à construire et gérer une usine permettant d’exploiter des ressources afin de s’échapper de la planète. Tout le plaisir du jeu et d’agrandir et d’optimiser chaque extraction et production.",[19,6171,6172],{},"Le jeu permet, à l’instar de Minecraft, une grande liberté dans sa construction, y compris en utilisant de la logique permettant même de coder dans Factorio.",[19,6174,6175],{},"Julien se propose ici de nous illustrer de nombreux concepts que l’on peut trouver dans nos projets professionnels.",[19,6177,6178],{},[1374,6179],{"alt":6180,"src":6181},"Le logo du jeu Factorio","\u002Fcontent-assets\u002F2025-12-02-alors-ctait-comment-ce-premier-devfest-lyon\u002Fassets\u002Fimg4.webp",[19,6183,6184],{},"Voici les concepts abordés :",[23,6186,6187,6190,6193],{},[26,6188,6189],{},"Développement (Plat de spaghettis, Architecture en couches, Micro-services..)",[26,6191,6192],{},"Urbanisation (ESB, Scaling Vertical\u002FHorizontal)",[26,6194,6195],{},"Sécurité (Métriques & monitoring, DDoS, Firewalls…)",[19,6197,6198],{},"L’exécution est impressionnante, et tous les concepts sont expliqués de manière visuelle et claire. Parfait pour présenter notre travail de tous les jours à des personnes non-tech ! Et il aura fallu plus de 100h pour préparer ce talk original.",[19,6200,6201,6202,393],{},"Si vous souhaitez vous aussi voir ce qu’a construit Julien, un replay de son ",[979,6203,6206],{"href":6204,"rel":6205},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=I07DxOLw10E",[983],"talk est disponible sur YouTube",[39,6208,6210],{"id":6209},"cétait-dans-quel-épisode-déjà-bref-jai-indexé-une-série-culte","C'était dans quel épisode déjà ? Bref, j'ai indexé une série culte",[19,6212,6213],{},[1613,6214,6215],{},"Par Tim Carry - Data & AI - 20 minutes",[19,6217,6218,6219,6224],{},"Tim travaille chez Algolia (oui encore eux). Tim est développeur. Mais surtout, Tim est un très grand fan de la série ",[979,6220,6223],{"href":6221,"rel":6222},"https:\u002F\u002Fwww.imdb.com\u002Ffr\u002Ftitle\u002Ftt2044128\u002F",[983],"Bref"," (note personnelle : il a bien raison !).",[19,6226,6227,6228,393],{},"Il s’est lancé sur un projet personnel qui lui tenait à cœur : un site qui permettrait de trouver l’épisode et l’extrait d’une réplique culte que l’on recherche. Ce sera la genèse de ",[979,6229,6232],{"href":6230,"rel":6231},"https:\u002F\u002Fwww.brefsearch.com\u002F",[983],"brefsearch.com",[19,6234,6235],{},"Les fonctionnalités du site : recherche par ligne de dialogue \u002F pensée, gère les fautes de frappe, aperçu animé de l’extrait au survol, recherche rapide et qui mène directement au moment trouvé.",[19,6237,6238],{},[1374,6239],{"alt":6240,"src":6241},"Un exemple : la recherche du terme “Internet” sur brefsearch.com. Le résultat est très rapide et pertinent, et les extraits se jouent au survol.","\u002Fcontent-assets\u002F2025-12-02-alors-ctait-comment-ce-premier-devfest-lyon\u002Fassets\u002Fimg5.webp",[19,6243,6244],{},"Mais comment cela marche-t-il ?",[19,6246,6247,6248,6253,6254,6259],{},"Tout d’abord, il faut une extraction via ",[979,6249,6252],{"href":6250,"rel":6251},"https:\u002F\u002Fgithub.com\u002Fyt-dlp\u002Fyt-dlp",[983],"Yt-dlp",", un outil en lignes de commande qui permet à Tim de récupérer toutes les vidéos de la ",[979,6255,6258],{"href":6256,"rel":6257},"https:\u002F\u002Fwww.youtube.com\u002Fplaylist?list=PLlFikkv2B2ffwYiFQJmcao3RKtw1DFMz5",[983],"playlist Bref sur YouTube",". Il obtient également beaucoup de metadatas très utiles, et également les sous-titres automatiques.",[19,6261,6262,6263,6268,6269,6272],{},"Un problème se pose cependant, dans Bref, ça parle vite. Les sous-titres automatiques sont de qualité médiocre. Pour récupérer du texte exploitable, une extraction audio est alors faite par Yt-dlp puis passés dans ",[979,6264,6267],{"href":6265,"rel":6266},"https:\u002F\u002Fwww.happyscribe.com\u002Ffr",[983],"HappyScribe",", un outil de ",[1613,6270,6271],{},"speech-to-text"," IA. C’est bien mieux, et la retouche des dialogues est facilement réalisable (et cela donne une raison de plus de revoir la série !)",[19,6274,6275],{},"Et pour la suite :",[23,6277,6278,6286,6289],{},[26,6279,6280,6285],{},[979,6281,6284],{"href":6282,"rel":6283},"https:\u002F\u002Fwww.ffmpeg.org\u002F",[983],"ffmpeg"," permet de construire les frames des lignes de texte trouvées et les aperçus animés",[26,6287,6288],{},"Algolia permet la recherche",[26,6290,6291,6292,6297,6298,2914],{},"Le frontend est hébergé sur Netlify en utilisant le CDN ",[979,6293,6296],{"href":6294,"rel":6295},"https:\u002F\u002Fcloudinary.com\u002F",[983],"Cloudinary",", avec quelques astuces données par Tim (notamment l’utilisation de LQIP (",[1613,6299,6300],{},"Low Quality Image Placeholder",[19,6302,6303],{},"Le résultat ?",[19,6305,6306],{},"Un site efficace, qui fait très bien ce qu’il a à faire, et qui impressionne les créateurs de la série eux-mêmes. Je ne sais pas vous, mais moi cela m’a donné envie de revoir Bref… J’en ai mangé tout le week-end suivant !",[39,6308,6310],{"id":6309},"speechless-fin-de-la-matinée","Speechless & fin de la matinée",[19,6312,6313],{},"Après ces présentations toutes plus intéressantes les unes que les autres, il est maintenant temps de récupérer des forces et de débriefer autour d’un grand buffet. Mais l’heure n’est pas qu’à la pause : un speechless live est organisé.",[19,6315,6316],{},"Le principe ? Plusieurs speakeuses et speakers passent devant le public dans un exercice d’improvisation :",[23,6318,6319,6322,6325],{},[26,6320,6321],{},"Un thème tiré au sort",[26,6323,6324],{},"Un sujet choisi par le public",[26,6326,6327],{},"Des slides imposées, qui n’ont aucun sens",[19,6329,6330],{},"C’était en tout cas un plaisir de voir Carmen Piciorus passer un entretien d’embauche pour devenir gardienne de nains de jardin, et Tim Carry nous pitcher son nouveau film “La revanche des cacahuètes”. Un moment amusant qui clôture une matinée aux petits oignons au DevFest Lyon !",[19,6332,6333],{},"Il est temps ensuite d’attaquer un après-midi qui s’avèrera tout aussi riche en enseignements et en échanges de qualité. Vous voulez en savoir plus ?",[19,6335,6336],{},"Guettez ce blog, j’ai entendu dire qu’un second article était en préparation…",{"title":63,"searchDepth":86,"depth":86,"links":6338},[6339,6340,6341,6342,6343],{"id":5944,"depth":86,"text":5945},{"id":6023,"depth":86,"text":6024},{"id":6154,"depth":86,"text":6155},{"id":6209,"depth":86,"text":6210},{"id":6309,"depth":86,"text":6310},"2025-12-02T10:02:07.045Z","Une question que l’on me pose beaucoup en ce début de semaine. En effet, vendredi 28 novembre 2025, nous avons eu la chance, avec plusieurs collègues de HoppR, de faire partie des 250 participants de ",{},"\u002Fblogs\u002F2025-12-02-alors-ctait-comment-ce-premier-devfest-lyon",[6349,6351,6353,6355],{"id":3392,"name":3393,"image":6350,"linkedin":3395,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F53946b9e-3bb9-45bd-a8b4-429c51156179\u002FT04PC176TGB-U05EW3YF61Z-5e129f612df3-512.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466Y7KSVBHC%2F20251202%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20251202T100206Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEoaCXVzLXdlc3QtMiJHMEUCIQDlPhoO6t9yJQoUoZw%2FLTwJI5MC%2Foym8X1E%2Bg6KJ%2BiFgwIgalDq7jA%2BP85pFMTva72lsMaql%2B9LjtYqLDQH3dgn1M0q%2FwMIExAAGgw2Mzc0MjMxODM4MDUiDNOvwSwCkwWPXxfr2CrcA8769%2FoIXsCvhgFS4Dv2as%2FMadMS7wa5TlBrAZ7cnJv7%2BruPZECofduiEAoBV9ATh622gA0pQ231dPxprnYq6SLobx%2FEXgHUroXG3Hqn%2ByV4BZcmFzpgks2HcaAcuaHoAxQhO%2FJOFl%2FDUsk7u2C%2F03ZHWHalr4QFq8HF2kBEzUf5y9Z7lCS23wHUe9yrlp6dnDaMZv7ctg37nRsZ5cPFFyP%2Bi6DvsRbv4jk7ZR4d1uGo%2FyGh6TxAtZ6bjsPYjOKjZ8cDFQubPif7rBAfK8Gu2lMBuI8Cpd1qw32YzUj4dprm8t1R1SUxs4t9d3SmZ415ZoYHufTwtLVK2sMPIP1y2pVCL5TQxoCQIeZknhH6tf7lN%2BJs7CiIk9lolBqdAUPcwZ3F%2F5Fp4IC63yn6syo0RgnVB8MqfB%2BSgFz%2BdmhDCDcQ%2B54ndcErvY2ae44aPLzBYI6haQ8DpNzmYS6KvY3lcnCqKPTRx2M6%2BTlHpOEeq7CbpF%2FYsMtskDgQyVVqQRGZJJ98f4naA%2F8A41AIqXTDNAK8ifTyOLNOa8pF%2BGwiNsbrbjT%2F4qhB43PK%2FjzXCKtb8czB7vQydYQ0mjlSkxtXFecZ5qOzQIYc%2BJI%2BNhPsJoCLRKw3sr9EwLqY5d6NMJrvuskGOqUBkmJA1855ur4UyoLjGj1WPfKYtvmOStTXYnAk6EEA5p4DMsWp%2FtC79u340iUu7rojNw3oS3XeDN7pdCo%2FZMsMGkduaNvN3RB3crutakC38cpYKKo8%2Fg1VYP9a5GzXDfesiZ4RzRQIMUhkFjYbvPnQiOxmXU%2FxTyvR57RMWG7ApufvcgZqmTQgxzACj5uuwFn8iXfklA7WVG0WqI3CseqOO%2F7ssBqz&X-Amz-Signature=0f9dedf66c69728d181b86c075540248b1da9d52cb45a0d9b21654912eac01eb&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"id":1195,"name":1196,"image":6352,"linkedin":1198,"x":1199},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc69d0b59-558d-4e48-879f-bea3fec1fdef\u002FLinkedin_Profile.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466QRTGLWHL%2F20251202%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20251202T100206Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEkaCXVzLXdlc3QtMiJHMEUCIQDm4%2F0Wbjn1yhS08yXFNJ3YxNfXhqedQ1Ibi1ixjAZwQAIgfOCHgnZEctCvQodF01gw3q43a2oTC9uvnFD5rnVsxQkq%2FwMIEhAAGgw2Mzc0MjMxODM4MDUiDEGDfT3DvHNF4e0%2F1ircA%2BMNtVAYwDGaDc4cTMBzN4fvTjoy2vcW6lqQwt0fckDmqFnu4KACIzQS0IBnqRuEKVrwHDf462itGMt45GGMFgrJ%2FwZjcEXN7mqtsDnO6Okzrr8o2cez6enA7ugl2qQa1K9n00YRnX6S9YbDBhvJk4%2Fob8H1RM5f3g%2FWZ0DE%2Bw4ZQtTkqyzk9cmEFnZj9PgP3P5JJ6%2B1XC6y4hKPodsLFwdB%2FiWv93l%2BIQJvtA7Pmzf7MEsf%2Fjfo9lBKh96v0muhHM%2BOW3HziW%2B2%2F43PzDRq0fLk5PEHcyDk0ir6a0d1SE54JtxwNCQ2r4c9kr2U4QPmDgplyC0rvhlk1MkY8KQW%2BbKHW%2B7bDhIIUZKx6blcBvvnsHF8SxMYxMaKSuOigd8%2FfKtXdYhUIv5kl4a4pjX8%2Btgg4E1FEgXY6OOSuvWlAlKEOS1TLV5Mm9BxvLdM6lCaoPeD%2BwjBRBe3QsEal0tM8%2FoPZzY6qjoajqvXvfL6LVwme461vj52ccT9xnaR2bRChgs%2BU8ucdg320zC3PiGEn55r19XsS2syF4KKPQ2ZqotsOp8ddRvwjdUs2yeIjMi8MfSSklsKTRnqzGITcKfg9UPQ%2FkmP4ZIfFJ9BAEug3TroU6elUhH%2FX99VHkbdMJnQuskGOqUBDw6lrXPYCboisCRNK3rSof7RUE8dcfUUhSnHXeQhFy%2Bxtswny8NKyhzu2f3axx%2B8BAWeqnJLi9juYMoEcyH57Z1OlduE9j4c8XNeb5S5QGuPgL2eFFm2Q6A2%2B1%2Bx%2B%2FWc9u8KRHsPWxmnH%2FDvGCo%2B5NAwT2Z59tSaAy4WLaU69x7pOXJw2GLcmvqj7GY8t1wWC%2BgZmvjgT5pr7JoAqMyCTlqWW0p8&X-Amz-Signature=c4e986c0dead60c4b87052795a3446cf2dc94d9ffdea6f9da0065c64e12d46b3&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"id":1542,"name":1543,"image":6354,"linkedin":1545,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Ff8f82a79-9d41-4302-b1a5-37882985167f\u002Fnicoz_hoppr.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4664RUX22IJ%2F20251202%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20251202T100206Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEoaCXVzLXdlc3QtMiJIMEYCIQC2nkVt1TGIZcU4J2cW%2BwnOm%2FJYMKPRvr9QoJI0ln9rtAIhAMbp5av2Mva0lC3zvk9RTnOZcfJVPe3y49DPXDto73qKKv8DCBMQABoMNjM3NDIzMTgzODA1IgxWonWoO8Q%2F6Tb5h5wq3APC9x01ntajt7LgXx2%2F6oWSdtYAHetuavmLKHuZBE4qZkUxVZlNf8yev7okfjbHkNTEZGT9ewi71Ep6%2Fj7OgQALj8WCrQ0NStHtOAGlQxjSBldfbVhSqpD%2FcKXOxmZe78PH9Bah3KKZgd%2Fzq1QZxCFIUp9YdjF%2FbL9yDF32Qffz6s4hE2xPX%2BIWXiLgy%2FoAHqyuuF9yLOJGFULNae1K0SpX%2BUPc9bGDrF5eqmdZEagZw3NdTYVzpYNJeJjDdTA2wSH9V3zloZQUllFmA2FapNJ7m43SbD5crno2TvsppP%2FIZwtcKkbyYQ1dIu9yUkQcP0YtKL5HESyEHe2wZ%2Fh8ySECV5JLqJ612Tq%2FjQ4WG5IRA3LnmsLqVwlkmclF9qqhCLkr1raV9rxA49bzA9HTJlJbbx9W2IJ%2B1NF9k%2Ba4%2FgPg%2BBKeMMHbVVxnzJVULzJY3wX5CMRMW%2Bsb5j4idcJB9stl3D4taE42iOp%2FRdjAS0hnlyNzgN9wrV0exYYd5KhJjI4Q%2FULsOOo7qA%2B4HsqdYL36zkfJN6T%2BvjrzVG%2FjxzTNwUrTVKwzOHom2lkGd%2BX8KE1Cq3Xx%2FmRIx1Qdp3ua8xccBoGHYsEItydyKDT%2FvmhS8VqeCS3Mf719fVMjDTCT8LrJBjqkAfQTGT%2Fs9MjD6z%2BwJvaGpYM%2F%2Fj7dc8MdLQBOe1TXVGUugJIFO3k9RJBOnpGAcJ8iORYMTxNiQmlpchi%2FeJ%2FloxMKo9KLYsP51i8IawGdvOfcd7gPws3WPQglaWdiRbMl%2BsIKoY3ltOxuyzwHnFe3odYH9Jk7H15EvVy4X7KZns8dNnyZMF19aidySWh55J%2Byb%2BSI0fDuPaOd75%2BN97EwLFgvXSKB&X-Amz-Signature=10f2e76341a5b95b387a7537285d41aa5d73f789611b6e3f7291d5e48bf31b7f&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"id":1184,"name":1185,"image":6356,"linkedin":1187,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc88f5dfa-16db-4e6f-acf1-34dd80ee8766\u002Femma_hoppr.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466Y3X2BERO%2F20251202%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20251202T100206Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEEoaCXVzLXdlc3QtMiJHMEUCIHRw%2FuSZy28SiwpPOCs76n11i%2F1NJe6CZ%2BsRL3AWO%2B2fAiEA4vhBhGzEMfJI4snF1OloxuWCCMbRf1PDwUR5mDJ9XhMq%2FwMIExAAGgw2Mzc0MjMxODM4MDUiDFG70SUdJEiGKkM5bSrcAw9rPmsJGzwqShqiv0%2BeWtdSAO1vDBF77Pr3fo57wA6onGBTD13Mb6SFRhQ9%2Fra6mwI6ViUxzjAKm12NSy5iUxqlpE7OCeRIo9Anm1I3%2BeCrcf9sgDbmjO9%2Fq0CVRI%2FU5IZdPCJ33TU8DMeMHEqWABPbQu2JgRA08oGE7VhNIyxEibEDVBt2efeb9Lo5jxMEz%2FWlDtk3PoQhL%2FLS5Z%2F3VJ46ZG%2FjmqYw0w7eL3RTXvpO3F8ba8FwLP%2BPox3DLRsE%2Bb7RZip03wWeSyGpIyQrk%2Fk%2F5G3jj7VUUplHpHhT5D1Wqy5cXNWNlTczPw5oVN2IUc50L5iFv5LS4fAd8pFkfN62EjccKbWW0KnK539jxa9NktNFfLvcYD%2F6vLcDNwyfQgGP8yceEsSpjcPO0qHzchwhu3DNi7CIZe6US62dUslZUaUrSBMOsmUn%2B9ddPbtRSMqOY%2Bm%2FtLRJg%2FViOt1WXTF3IbqHZ4NJdCtZACK05FQhMzGqqH2dD2ItGI4Omx34fVPkSaT9o4dYKTix8VDrwD5EhMNmYrjOp7D%2BV8xhKoDoiW8JlW1VuSG6lVBEwLYkuwaHxUACflNG8bSl2YauKHvUTet%2FfaANzQSi%2F9UCf9y%2FPrn5IcGO2wdGGRxoMO%2FvuskGOqUBkceK1DQegSzZ4IUtEx%2B4PllbY2t6tdvBH15yyh4%2B%2FGCAcS9vea00xBAgUT4sC5YvyzMBR5INtwcv8At2vEauvXRTSiRU%2B5v13rmi1RpZpgRNRpspuzp4m9IRPeludo%2B%2FPgFsi%2FJ5dUfWIr1kzF%2F6ElvWLADEI9Chpz%2BvdUDbn%2F6ysbTgkF%2F8X4vIJraEEIdfrTio7kDfiOb%2BelfK7YochB5tFNJG&X-Amz-Signature=48a8f72788c038f6fc6755f0a2dd5a263a644eb8957eb8922c1068162f4056db&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"title":5899,"description":6345},"blogs\u002F2025-12-02-alors-ctait-comment-ce-premier-devfest-lyon\u002Findex",[1208,6360,4514,4936,5893],"ia","sCW66ulOtCP9_c_OAQWwJLKRWm825rxal5BWlrgds-A",{"id":6363,"title":6364,"alt":6365,"authors":6366,"body":6372,"date":6823,"description":6824,"extension":1178,"image":1179,"meta":6825,"navigation":102,"ogImage":1179,"path":6826,"published":102,"reviewers":6827,"seo":6830,"stem":6831,"tags":6832,"__hash__":6833},"blogs\u002Fblogs\u002F2026-01-14-devbox-ou-comment-enfin-onboarder-un-dev-en-5min-chrono-sur-son-projet\u002Findex.md","Devbox, ou comment (enfin) onboarder un dev en 5min chrono sur son projet","Caisse à outils futuriste \"DEVBOX\" d'où émanent des outils virtuels et des logos de langages de programmation, posée sur un établi technologique avec un cube Docker lumineux.",[6367,6369,6370],{"id":1201,"name":1202,"image":6368,"linkedin":1204,"x":1188},".\u002Fassets\u002Fauthor-paul-alexandre-chrtien.webp",{"id":3392,"name":3393,"image":5684,"linkedin":3395,"x":1188},{"id":1195,"name":1196,"image":6371,"linkedin":1198,"x":1199},".\u002Fassets\u002Fauthor-maxime-deroullers.webp",{"type":16,"value":6373,"toc":6812},[6374,6379,6382,6385,6391,6396,6413,6416,6419,6433,6437,6447,6450,6456,6466,6492,6495,6509,6513,6516,6519,6540,6543,6547,6550,6565,6569,6572,6592,6596,6599,6611,6614,6621,6624,6632,6636,6639,6642,6674,6680,6683,6686,6692,6735,6738,6802,6806,6809],[19,6375,6376],{},[1311,6377,6378],{},"Le scénario est un classique du film d'horreur en entreprise.",[19,6380,6381],{},"C'est le \"Day 1\" d'un nouveau développeur. Il est motivé, son café est chaud, sa machine est prête. On lui donne l'accès au repo. Il clone. Il lance le projet.",[19,6383,6384],{},"Et là, le drame :",[19,6386,6387,6388],{},"🔴 ",[1613,6389,6390],{},"Error: Node version 18.x required (found 14.x).",[19,6392,6387,6393],{},[1613,6394,6395],{},"Error: libssl.so.1.1: cannot open shared object file.",[19,6397,6398,6399,6402,6403,1023,6406,1023,6409,6412],{},"S'ensuivent deux jours de \"bricolage\". On modifie le ",[65,6400,6401],{},".zshrc",", on installe ",[65,6404,6405],{},"nvm",[65,6407,6408],{},"jenv",[65,6410,6411],{},"pyenv",", on casse la config d'un autre projet... Le fameux \"purgatoire de l'environnement local\".",[19,6414,6415],{},"L'onboarding ne devrait pas être une cérémonie mystique. En 2026 ça devrait être un simple script. Surtout si l’on rapporte cela à l’échelle du nombre de développeurs et consultants avec une moyenne (ambitieuse) de 2 ou 3 jours à chaque démarrage. Que de temps perdu !",[19,6417,6418],{},"Et si le fichier de configuration de votre projet suffisait à installer tous les outils, sans la lourdeur de Docker ?",[19,6420,6421,6422,3570,6429,6432],{},"C'est la promesse de ",[979,6423,6426],{"href":6424,"rel":6425},"https:\u002F\u002Fwww.jetify.com\u002Fdevbox",[983],[1311,6427,6428],{},"DevBox",[1311,6430,6431],{},": une boîte à outils en ligne de commande qui transforme un simple fichier de configuration en un environnement de développement complet, isolé et natif.","  Lancé en 2022 par l'entreprise Jetify, le projet open-source connaît une adoption rapide et cumule déjà plus de 11K étoiles sur GitHub.",[39,6434,6436],{"id":6435},"devbox-la-puissance-de-nix-sans-la-douleur","DevBox : La puissance de Nix, sans la douleur",[19,6438,6439,6440,393],{},"Pour comprendre l'intérêt de DevBox, il faut d'abord saluer le moteur qui tourne en dessous : ",[979,6441,6444],{"href":6442,"rel":6443},"https:\u002F\u002Fnixos.org\u002F",[983],[1311,6445,6446],{},"Nix",[19,6448,6449],{},"Nix est le Saint Graal de la gestion de paquets : il est purement fonctionnel, immuable et garantit qu'un paquet installé aujourd'hui sera identique au bit près dans 10 ans.",[19,6451,6452,6455],{},[1311,6453,6454],{},"Le problème ?"," Nix est notoirement difficile à apprendre. Il demande de maîtriser un langage de configuration complexe et verbeux. C'est souvent un frein rédhibitoire pour les équipes qui veulent juste coder.",[19,6457,6458,6461,6462,6465],{},[1311,6459,6460],{},"C'est là qu'intervient DevBox.","\nVoyez DevBox comme une \"télécommande simplifiée\" ou un ",[1613,6463,6464],{},"wrapper"," élégant.",[23,6467,6468,6478],{},[26,6469,6470,6473,6474,6477],{},[1311,6471,6472],{},"Sans DevBox :"," Vous devez écrire des scripts ",[65,6475,6476],{},".nix"," cryptiques pour configurer votre environnement.",[26,6479,6480,6483,6484,6487,6488,6491],{},[1311,6481,6482],{},"Avec DevBox :"," Vous remplissez un simple fichier ",[65,6485,6486],{},"devbox.json"," (aussi facile à lire qu'un ",[65,6489,6490],{},"package.json",") et l'outil se charge de \"parler\" à Nix pour vous.",[19,6493,6494],{},"Maintenant que la barrière technique est levée, quelle est la différence d'expérience par rapport à Docker ? Utilisons une analogie simple :",[23,6496,6497,6503],{},[26,6498,6499,6502],{},[1311,6500,6501],{},"Docker, c'est l'hôtel."," C'est standardisé, isolé, sécurisé. Mais vous n'êtes pas chez vous. Les fichiers sont montés via des volumes (lent), le réseau est une couche supplémentaire. C'est parfait pour la Production.",[26,6504,6505,6508],{},[1311,6506,6507],{},"DevBox, c'est la Réalité Augmentée."," Vous restez chez vous (votre terminal natif, votre OS). Mais quand vous enfilez les lunettes (quand vous lancez DevBox), les outils dont vous avez besoin apparaissent comme par magie. C'est natif, instantané, et ça disparaît quand vous les enlevez.",[39,6510,6512],{"id":6511},"la-preuve-par-lexemple-le-5-minutes-challenge","La Preuve par l'Exemple (Le \"5 Minutes Challenge\")",[19,6514,6515],{},"Assez de théorie. Prenons un cas concret qui parle à tout Tech.",[19,6517,6518],{},"Imaginez un projet Python un peu capricieux qui nécessite :",[23,6520,6521,6527,6533],{},[26,6522,6523,6526],{},[1311,6524,6525],{},"Python 3.10"," (alors que votre système est en 3.12).",[26,6528,6529,6532],{},[1311,6530,6531],{},"Poetry"," pour gérer les paquets.",[26,6534,6535,6536,6539],{},"Un client ",[1311,6537,6538],{},"PostgreSQL"," spécifique.",[19,6541,6542],{},"Avec DevBox, voici à quoi ressemble l'installation :",[582,6544,6546],{"id":6545},"étape-1-création-du-contrat","Étape 1 : Création du contrat",[19,6548,6549],{},"Dans votre dossier projet, initialisez l'environnement.",[58,6551,6555],{"className":6552,"code":6553,"language":6554,"meta":63,"style":63},"language-bash shiki shiki-themes github-dark-default","devbox init\n","bash",[65,6556,6557],{"__ignoreMap":63},[68,6558,6559,6562],{"class":70,"line":71},[68,6560,6561],{"class":92},"devbox",[68,6563,6564],{"class":1053}," init\n",[582,6566,6568],{"id":6567},"étape-2-linstallation-isolée","Étape 2 : L'installation isolée",[19,6570,6571],{},"Ajoutez vos outils. DevBox va chercher les binaires pré-compilés (pas de compilation interminable).",[58,6573,6575],{"className":6552,"code":6574,"language":6554,"meta":63,"style":63},"devbox add python@3.10 poetry postgresql\n",[65,6576,6577],{"__ignoreMap":63},[68,6578,6579,6581,6583,6586,6589],{"class":70,"line":71},[68,6580,6561],{"class":92},[68,6582,1679],{"class":1053},[68,6584,6585],{"class":1053}," python@3.10",[68,6587,6588],{"class":1053}," poetry",[68,6590,6591],{"class":1053}," postgresql\n",[582,6593,6595],{"id":6594},"étape-3-lactivation","Étape 3 : L'activation",[19,6597,6598],{},"Entrez dans la matrice.",[58,6600,6602],{"className":6552,"code":6601,"language":6554,"meta":63,"style":63},"devbox shell\n",[65,6603,6604],{"__ignoreMap":63},[68,6605,6606,6608],{"class":70,"line":71},[68,6607,6561],{"class":92},[68,6609,6610],{"class":1053}," shell\n",[19,6612,6613],{},"C'est fini.",[19,6615,6616,6617,6620],{},"Si vous tapez ",[65,6618,6619],{},"python --version",", le terminal vous répondra 3.10, même si votre système est pollué par d'autres versions.",[19,6622,6623],{},"Pour le nouveau développeur qui arrive, l'onboarding se résume désormais à deux commandes :",[19,6625,6626,5102,6629,393],{},[65,6627,6628],{},"git clone",[65,6630,6631],{},"devbox shell",[39,6633,6635],{"id":6634},"sous-le-capot-pourquoi-cest-fiable","Sous le capot : Pourquoi c'est fiable ?",[19,6637,6638],{},"Je sais ce que vous pensez : \"Encore un wrapper node qui va casser mon PATH ?\"",[19,6640,6641],{},"Non. DevBox est une interface accessible pour Nix, l'un des gestionnaire de paquets les plus robustes.",[23,6643,6644,6658,6668],{},[26,6645,6646,6649,6650,6653,6654,6657],{},[1311,6647,6648],{},"Le Store Immuable :"," DevBox n'installe rien dans ",[65,6651,6652],{},"\u002Fusr\u002Fbin",". Tout va dans ",[65,6655,6656],{},"\u002Fnix\u002Fstore",", en lecture seule. Vous pouvez avoir 10 versions de Node.js côte à côte, elles ne se connaissent pas.",[26,6659,6660,6663,6664,6667],{},[1311,6661,6662],{},"L'illusion du Shell :"," Quand vous lancez le shell, DevBox modifie temporairement votre ",[65,6665,6666],{},"PATH"," pour pointer vers ces versions isolées.",[26,6669,6670,6673],{},[1311,6671,6672],{},"Le Cache Mutualisé :"," C'est là que DevBox brille. Si vous avez cinq projets en Node 18, DevBox ne le télécharge qu'une seule fois. Contrairement à Docker qui réinstalle l'OS à chaque image, DevBox partage les binaires communs. Gain de place, gain de temps.",[39,6675,6677],{"id":6676},"docker-devbox-le-bon-outil-au-bon-endroit",[1311,6678,6679],{},"Docker & DevBox : Le bon outil au bon endroit",[19,6681,6682],{},"DevBox et Docker ne sont pas concurrents. Ce sont deux couches différentes de votre stack. Voyez DevBox comme votre Boîte à Outils ultime (Toolbox). Son rôle est de préparer votre établi. Elle vous apporte les langages (Python, Go) et les clients (CLI) nécessaires pour travailler.",[19,6684,6685],{},"Et devinez quoi ? Parmi ces outils, DevBox peut parfaitement installer et configurer... Docker ou Kubernetes.",[19,6687,6688,6691],{},[1311,6689,6690],{},"Le scénario idéal pour simuler la Production :"," vous voulez reproduire un environnement de production complexe (Microservices sur K8s) ?",[1306,6693,6694,6716,6725],{},[26,6695,6696,6698,6699,6702,6703,1023,6706,1023,6709,1023,6712,6715],{},[1311,6697,6428],{}," installe les ",[1613,6700,6701],{},"commandes"," : ",[65,6704,6705],{},"kubectl",[65,6707,6708],{},"helm",[65,6710,6711],{},"terraform",[65,6713,6714],{},"aws-cli"," (dans les versions exactes de la prod).",[26,6717,6718,6720,6721,6724],{},[1311,6719,6428],{}," lance les ",[1613,6722,6723],{},"scripts"," : Il configure l'accès à votre cluster local.",[26,6726,6727,6730,6731,6734],{},[1311,6728,6729],{},"Docker\u002FK8s"," exécutent les ",[1613,6732,6733],{},"services"," : Ils font tourner les bases de données et les conteneurs applicatifs.",[19,6736,6737],{},"DevBox est le chef d'orchestre, Docker est l'un des instruments.",[6739,6740,6741,6757],"table",{},[6742,6743,6744],"thead",{},[6745,6746,6747,6751,6754],"tr",{},[6748,6749,6750],"th",{},"Rôle",[6748,6752,6753],{},"DevBox (La Boîte à Outils)",[6748,6755,6756],{},"Docker \u002F Kubernetes (L'Infrastructure)",[6758,6759,6760,6769,6780,6791],"tbody",{},[6745,6761,6762,6765,6767],{},[6763,6764,6750],"td",{},[6763,6766,6753],{},[6763,6768,6756],{},[6745,6770,6771,6774,6777],{},[6763,6772,6773],{},"Mission",[6763,6775,6776],{},"Gérer l'outillage.  S'assurer que le développeur a les bonnes clés à molette.",[6763,6778,6779],{},"Exécuter les services.  Faire tourner les briques logicielles lourdes.",[6745,6781,6782,6785,6788],{},[6763,6783,6784],{},"Ce qu'il contient",[6763,6786,6787],{},"Compilateurs (Go, Rust), Interpréteurs (Node, Python), CLI ( docker ,  kubectl ,  terraform ).",[6763,6789,6790],{},"Bases de données (Postgres, Redis), Message Brokers (Kafka), Applications conteneurisées.",[6745,6792,6793,6796,6799],{},[6763,6794,6795],{},"Philosophie",[6763,6797,6798],{},"\"Je prépare ton environnement de travail.\"",[6763,6800,6801],{},"\"Je simule ton environnement de production.\"",[39,6803,6805],{"id":6804},"conclusion-un-json-to-run-them-all","Conclusion :  un JSON to run them All",[19,6807,6808],{},"L'infrastructure-as-code a révolutionné la production. Il est temps qu'elle révolutionne l'onboarding. Vos équipes ne devraient pas perdre de temps à configurer des outils. Elles devraient mettre leur énergie sur les tâches où elles ont un maximum de valeurs.",[1166,6810,6811],{},"html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html pre.shiki code .s9uIt, html code.shiki .s9uIt{--shiki-default:#A5D6FF}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);}",{"title":63,"searchDepth":86,"depth":86,"links":6813},[6814,6815,6820,6821,6822],{"id":6435,"depth":86,"text":6436},{"id":6511,"depth":86,"text":6512,"children":6816},[6817,6818,6819],{"id":6545,"depth":99,"text":6546},{"id":6567,"depth":99,"text":6568},{"id":6594,"depth":99,"text":6595},{"id":6634,"depth":86,"text":6635},{"id":6676,"depth":86,"text":6679},{"id":6804,"depth":86,"text":6805},"2026-01-14T14:07:08.940Z","**Le scénario est un classique du film d'horreur en entreprise.**  C'est le \"Day 1\" d'un nouveau développeur. Il est motivé, son café est chaud, sa machine est prête. On lui donne l'accès au repo. Il ",{},"\u002Fblogs\u002F2026-01-14-devbox-ou-comment-enfin-onboarder-un-dev-en-5min-chrono-sur-son-projet",[6828],{"id":1190,"name":1191,"image":6829,"linkedin":1193,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F82ebd0fe-de28-43f3-ab7b-0431af41baad\u002FPhoto_HoppR.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466WN3QLNCE%2F20260114%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260114T140708Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEFYaCXVzLXdlc3QtMiJHMEUCIFD3v6TLnk5GmyhshqA5BlfwHhBJHcTihKFMH1xh8FAAAiEA6B%2F6vytfoyOMPoOmMOnmMsfzWqvIbVIVyk9SxRBOb5Uq%2FwMIHxAAGgw2Mzc0MjMxODM4MDUiDL6nPHU6OObivJIC6yrcAw%2BGMSR0gWkd909KwYy29%2BOOtd07YmkbUBPfyKQy5r44ubZUvnyvSe0bsnZlS%2BiruXV%2BAGH1i6YY8%2FfO0eW3%2F6bHTqgYg4jmlDqi3uQUyvSa%2B9ROYDaQonWsR1xQGJFRh3Q0FO67nIAv%2FDdPhejHVyc3bu5Yw6WNUkEb8av9H113ExC32mV6U3wm1Glrfc4rYfmQRxxrI9tyYnqjb17BDGot7eqAk5vTw2WgEqj1yLJfwOvOc9Vuvj2TDCT6XHPDxcraBQrDGZdVPhGEWQVOha%2BX0ZXI4ITciElRdlYY%2B4Xxf56q8ql1BFwBeWrjA7zO89T1ldzL4DYM6VVsTI%2BVD8ynF7EoZSRQoDt3FBWNyq0c1mkW%2BJKMcE8sYeppX8dTMf2fLezwknNN0aLAHVZnK4XxoAraRFbu3BRS1JQYOiMEle%2FtcErTA3X1Ta1llKaHRPRxdhMXr5uxW5D9%2BLLE%2B32QkD41lsPHdo%2FOOIPaaf8nG6mGZ6ZBUZnwpIjT8dntPtp3pI2c8d7f7VkQvVivaHWIc72JqjEavGmr4f01RxRFzPXzBA2c5Of4DJfXt3ZzRfhvCzATG5HpgWu5GVBFuYViCkRLytXHpet%2BTTZFhl5dlnBcBH%2Bnqkyy%2FJoJMJy%2FnssGOqUBaV15zB5j5%2BzO0TjePo5cVh5oNvLqVlrJGOL%2BiCjTFmTh%2Bq9TqZAWqhBDL%2Fow4sOiBPVNznBM5914QXUCD%2BT%2BryQHcsg5HeY2UQ6h1yy0DP4ktlenXm4U%2BcfXro6crhACbchkrLUpf2qANNrGa2I1SHj2%2F0yVPzquFLuh52qN6R9WsGQToQ9%2FlG3dzMlsgTMwiceoH4DcDiVxprDPl%2BIIjFNGb2lK&X-Amz-Signature=ba45c2556cf07a2dee7a1066cc21084b7f2a43679687bbef04b8c401bf1a63e5&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"title":6364,"description":6824},"blogs\u002F2026-01-14-devbox-ou-comment-enfin-onboarder-un-dev-en-5min-chrono-sur-son-projet\u002Findex",[1552,1549,1208,5893],"8FMs8rBbU5SfyDHKyjU0dSydcWohVaiLs6FQ28TlhKY",{"id":6835,"title":6836,"alt":6837,"authors":6838,"body":6840,"date":6972,"description":6973,"extension":1178,"image":1179,"meta":6974,"navigation":102,"ogImage":1179,"path":6975,"published":102,"reviewers":6976,"seo":6979,"stem":6980,"tags":6981,"__hash__":6983},"blogs\u002Fblogs\u002F2026-02-03-le-manifeste-du-platform-craftsmanship-lengagement-hoppr\u002Findex.md","Le Manifeste du Platform Craftsmanship : L'engagement HoppR","Platform Craftsmanship Manifesto par HoppR - L'alliance de l'artisanat et du Cloud DevOps",[6839],{"id":1201,"name":1202,"image":6368,"linkedin":1204,"x":1188},{"type":16,"value":6841,"toc":6965},[6842,6851,6857,6860,6864,6869,6876,6884,6888,6893,6896,6906,6910,6915,6918,6932,6936,6941,6944,6951,6954,6958],[19,6843,6844,6845,6847,6848,6850],{},"Dans le monde du développement, le ",[1613,6846,4537],{}," a rappelé que le code n'est pas une simple commodité, mais un artisanat. Chez ",[1311,6849,1607],{},", nous pensons qu'il est temps d'appliquer cette même exigence à l'infrastructure.",[19,6852,5997,6853,6856],{},[1311,6854,6855],{},"Platform Engineering"," ne doit pas être une usine à gaz technique, mais un ouvrage d'art au service de ceux qui créent la valeur : les équipes de développement.",[19,6858,6859],{},"Voici les quatre piliers de notre manifeste.",[39,6861,6863],{"id":6862},"_1-lexpérience-au-delà-de-la-technique","1. L'expérience au-delà de la technique",[479,6865,6866],{},[19,6867,6868],{},"Pas seulement des logiciels bien conçus, mais aussi des plateformes au service des équipes de développement.",[19,6870,6871,6872,6875],{},"Une infrastructure peut être techniquement parfaite mais inutilisable. L'équipe plateforme ne se contente pas de \"monter un cluster K8s\". Elle conçoit une ",[1311,6873,6874],{},"Internal Developer Platform (IDP)"," pensée pour la fluidité et l'autonomie.",[23,6877,6878],{},[26,6879,6880,6883],{},[1311,6881,6882],{},"La vision HoppR :"," Si une personne de l'équipe de développement doit ouvrir un ticket pour obtenir une base de données, la plateforme a échoué. Le Craftsmanship, c'est offrir le \"Self-Service\" sans sacrifier la sécurité.",[39,6885,6887],{"id":6886},"_2-la-vision-au-delà-de-la-livraison","2. La vision au-delà de la livraison",[479,6889,6890],{},[19,6891,6892],{},"Pas seulement l’ajout constant de valeur, mais aussi une vision continue.",[19,6894,6895],{},"Livrer des fonctionnalités infra ne suffit pas si on avance à l'aveugle. Le Craftsmanship impose une transparence totale du système.",[23,6897,6898],{},[26,6899,6900,6902,6903,6905],{},[1311,6901,6882],{}," Nous intégrons l’",[1311,6904,1551],{}," nativement. Une plateforme \"Crafted\" est un système transparent et mesurable, où chaque décision est guidée par la donnée et alignée sur la stratégie long terme de l'entreprise.",[39,6907,6909],{"id":6908},"_3-la-responsabilité-au-delà-de-la-performance","3. La responsabilité au-delà de la performance",[479,6911,6912],{},[19,6913,6914],{},"Pas seulement une communauté de professionnels, mais aussi des personnes responsables.",[19,6916,6917],{},"L'ingénierie plateforme moderne ne peut plus ignorer l'impact de son travail. L'équipe plateforme est responsable de son empreinte.",[23,6919,6920],{},[26,6921,6922,6924,6925,6928,6929,6931],{},[1311,6923,6882],{}," Nous intégrons le ",[1311,6926,6927],{},"FinOps"," (contrôle des coûts) et le ",[1311,6930,1293],{}," (durabilité) dès la conception. Agir de manière responsable, c'est garantir que la plateforme est aussi efficiente pour le budget de l'entreprise que pour la planète.",[39,6933,6935],{"id":6934},"_4-le-sens-au-delà-de-la-tendance","4. Le sens au-delà de la tendance",[479,6937,6938],{},[19,6939,6940],{},"Pas seulement des partenariats productifs, mais aussi une innovation porteuse de sens.",[19,6942,6943],{},"L'IA et les technologies émergentes sont partout, mais le Craftsmanship refuse le \"hype-driven development\".",[23,6945,6946],{},[26,6947,6948,6950],{},[1311,6949,6882],{}," Nous utilisons l’IA comme un levier d’accélération et non comme un gadget. Innover avec sens, c'est choisir l'outil qui apporte un résultat concret, pas celui qui fait simplement parler sur LinkedIn.",[6952,6953],"hr",{},[39,6955,6957],{"id":6956},"pourquoi-ce-manifeste-change-la-donne-pour-nos-clients","Pourquoi ce manifeste change la donne pour nos clients ?",[19,6959,6960,6961,6964],{},"En recherchant les éléments de gauche: la qualité, la valeur, le professionnalisme et le partenariat, nous avons réalisé que les éléments de droite: DevEx, Observabilité, FinOps, Innovation choisie, sont les véritables piliers de la réussite moderne.\nLe ",[1311,6962,6963],{},"Platform Craftsmanship"," n'est pas qu'une philosophie ; c'est notre méthode de travail chez HoppR pour transformer votre infrastructure en un avantage compétitif durable.",{"title":63,"searchDepth":86,"depth":86,"links":6966},[6967,6968,6969,6970,6971],{"id":6862,"depth":86,"text":6863},{"id":6886,"depth":86,"text":6887},{"id":6908,"depth":86,"text":6909},{"id":6934,"depth":86,"text":6935},{"id":6956,"depth":86,"text":6957},"2026-02-03T11:10:29.021Z","Dans le monde du développement, le _Software Craftsmanship_ a rappelé que le code n'est pas une simple commodité, mais un artisanat. Chez **HoppR**, nous pensons qu'il est temps d'appliquer cette même",{},"\u002Fblogs\u002F2026-02-03-le-manifeste-du-platform-craftsmanship-lengagement-hoppr",[6977],{"id":1184,"name":1185,"image":6978,"linkedin":1187,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc88f5dfa-16db-4e6f-acf1-34dd80ee8766\u002Femma_hoppr.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466UOV6GZ74%2F20260203%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260203T111028Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEDMaCXVzLXdlc3QtMiJHMEUCIQD6Xyjo9rBgI%2BiSNB06ysaRWlEErYThlHdyupC4mg6OfwIgeNXezDDUaziy4g%2F8AJ24c4Uu0XH2LvdJI%2FDifqoDE5sqiAQI%2FP%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAAGgw2Mzc0MjMxODM4MDUiDImm6RPPUAAURsSINyrcA5yvDNzDU25JinN6%2FCQ0N5f5pLF62qj6gCNdwKdSFNsFgZvPmi%2B5XmL3yRjR0YHUbqR8C%2FGSTnhvnNHLQeo3aSM9p5Y%2BA40LpF9ZmshckX4DpZThaH5MiaDl4S6XeGjBMqrnfQgSIBoPxbJxbdzXVu9GGQHxG5ZGiDXR3k%2FeRoFKG02CMxVBIeXnjcZrRExNaoh3bkl5edW1TggLVcOOlinHNG8nX2BGeTRD230AktYA0mfa%2FPb7K4zrE4W7xiv5MqOF3Sqf86hJIgDpJsm9nZkiRwR%2B45OOxp4VrB64L4D9lPKNBlRcaoLvlls2%2BBE5ONDWwYDeye3tbgGrl894pomySdjg%2BoCUPylDKExU2W4CaQXBLxBSO1ib2E2w5nlKUqLeaEuX%2B85Kp9QHpfCT7J5TrOJW96aT5kJf8mx1EaFzMD7XJ3YrYJbJ8Lq2Vwyf5C%2FWgoxnQLIjgv2IJpNhdHonnJooOPUbVQHXmKCjvxi3e%2F6xCL0tWsv2fJezKpr9Srt%2FFFH%2FjrBiVnxLR41yrkMcoKBBXfjF5qwPezkdSPvjwBeDG3YdZh0BRzEOpKjjl9QAYUFuQq7JdvmKV1tN%2Bl3sPvgGp87rug0GymKWAGT5IoyAa9LGY6EqbIgeMJiih8wGOqUBhj0Jgyltd2MkQOGI%2BPkpkl3EnVNkjxMFs5hzA7FfunDz3RMyq83%2BjRJBMRkguGb0%2FYNEK%2FzeeVARgjo%2FQIeQDSwqz5y1kMvQwnESP99TfcR%2BeFkbAB9ytqe26Lrz1Ngd2iVyANLEvo5sVw5rTrG6JjSG60McNLMPXS2FlwCtZZ2NRrnZj8AQbGPbdmeCFLfkO9FLKB%2FAgmEPSk%2BWurHfw7O8S0u9&X-Amz-Signature=be52c0a8f0df4d8ac737692d68ab0319fb85093713bb4b58bf60cfc58a95814c&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"title":6836,"description":6973},"blogs\u002F2026-02-03-le-manifeste-du-platform-craftsmanship-lengagement-hoppr\u002Findex",[6982,1549,1208,1552],"plateform engineering","gpC_Mg6MulH0mGD8IsrguZQezIcZ4y9hx-0N1N5hxLY",{"id":6985,"title":6986,"alt":6987,"authors":6988,"body":6992,"date":8577,"description":8578,"extension":1178,"image":1179,"meta":8579,"navigation":102,"ogImage":1179,"path":8580,"published":102,"reviewers":8581,"seo":8588,"stem":8589,"tags":8590,"__hash__":8591},"blogs\u002Fblogs\u002F2026-04-08-architecture-hexagonale-en-programmation-fonctionnelle-mythe-ou-ralit\u002Findex.md","Architecture hexagonale en programmation fonctionnelle, mythe ou réalité ?","Quand l’architecture hexagonale rencontre la programmation fonctionnelle",[6989,6990],{"id":10,"name":11,"image":12,"linkedin":13,"x":14},{"id":4509,"name":4510,"image":6991,"linkedin":1188,"x":1188},"\u002Fdefault-author-image.webp",{"type":16,"value":6993,"toc":8568},[6994,6997,7004,7007,7010,7014,7022,7673,7676,7679,7696,7715,7733,7736,7840,7850,7857,7860,7864,7876,7879,7983,7986,7990,7993,8075,8078,8082,8085,8550,8553,8556,8558,8565],[19,6995,6996],{},"À mesure que les systèmes logiciels gagnent en complexité, les approches traditionnelles de conception montrent leurs limites. Couplage excessif, difficulté à tester, rigidité face au changement : autant de symptômes qui ralentissent l’évolution des applications modernes. Dans ce contexte, deux courants se distinguent par leur capacité à apporter clarté, robustesse et maintenabilité : l’architecture hexagonale et la programmation fonctionnelle.",[19,6998,6999,7000,7003],{},"L’architecture hexagonale, aussi appelée ",[1613,7001,7002],{},"Ports and Adapters",", propose de recentrer le cœur métier en le protégeant des dépendances techniques. Elle favorise une séparation nette entre la logique métier et les interactions externes, rendant les systèmes plus modulaires et testables.\nDe son côté, la programmation fonctionnelle introduit des principes comme l’immuabilité, les fonctions pures et la composition, permettant de construire des logiciels plus prévisibles et plus faciles à raisonner.",[19,7005,7006],{},"Bien que ces deux approches soient issues de contextes différents, elles partagent une même ambition : réduire la complexité accidentelle pour laisser place à l’essentiel. Leur combinaison ouvre la voie à des architectures élégantes, où le code devient non seulement plus fiable, mais aussi plus expressif.",[19,7008,7009],{},"Dans cet article, nous explorerons comment ces deux paradigmes peuvent se compléter, et en quoi leur adoption conjointe peut transformer en profondeur la manière de concevoir et de faire évoluer nos applications.",[39,7011,7013],{"id":7012},"le-langage-ubiquitaire-au-centre-de-larchitecture","Le langage ubiquitaire au centre de l'architecture",[19,7015,7016,7017,393],{},"Nous allons illustrer tout ça avec le calcul de package de ",[979,7018,7021],{"href":7019,"rel":7020},"http:\u002F\u002Fhoppr.tech\u002F",[983],"Hoppr",[68,7023,7026],{"className":7024},[7025],"katex-display",[68,7027,7030],{"className":7028},[7029],"katex",[68,7031,7035,7141,7253,7356,7430,7520,7609],{"className":7032,"ariaHidden":7034},[7033],"katex-html","true",[68,7036,7039,7044,7051,7055,7059,7063,7129,7134,7138],{"className":7037},[7038],"base",[68,7040],{"className":7041,"style":7043},[7042],"strut","height:0.9694em;vertical-align:-0.2861em;",[68,7045,7050],{"className":7046,"style":7049},[7047,7048],"mord","mathnormal","margin-right:0.05017em;","B",[68,7052,7054],{"className":7053},[7047,7048],"o",[68,7056,7058],{"className":7057},[7047,7048],"n",[68,7060,7062],{"className":7061},[7047,7048],"u",[68,7064,7066,7070],{"className":7065},[7047],[68,7067,7069],{"className":7068},[7047,7048],"s",[68,7071,7074],{"className":7072},[7073],"msupsub",[68,7075,7079,7120],{"className":7076},[7077,7078],"vlist-t","vlist-t2",[68,7080,7083,7115],{"className":7081},[7082],"vlist-r",[68,7084,7088],{"className":7085,"style":7087},[7086],"vlist","height:0.3283em;",[68,7089,7091,7096],{"style":7090},"top:-2.55em;margin-left:0em;margin-right:0.05em;",[68,7092],{"className":7093,"style":7095},[7094],"pstrut","height:2.7em;",[68,7097,7103],{"className":7098},[7099,7100,7101,7102],"sizing","reset-size6","size3","mtight",[68,7104,7106,7110],{"className":7105},[7047,7102],[68,7107,7109],{"className":7108},[7047,7048,7102],"Ho",[68,7111,7114],{"className":7112,"style":7113},[7047,7048,7102],"margin-right:0.00773em;","ppR",[68,7116,7119],{"className":7117},[7118],"vlist-s","​",[68,7121,7123],{"className":7122},[7082],[68,7124,7127],{"className":7125,"style":7126},[7086],"height:0.2861em;",[68,7128],{},[68,7130],{"className":7131,"style":7133},[7132],"mspace","margin-right:0.2778em;",[68,7135,437],{"className":7136},[7137],"mrel",[68,7139],{"className":7140,"style":7133},[7132],[68,7142,7144,7148,7156,7160,7164,7167,7170,7174,7178,7241,7245,7250],{"className":7143},[7038],[68,7145],{"className":7146,"style":7147},[7042],"height:1.6em;vertical-align:-0.55em;",[68,7149,7155],{"className":7150,"style":7154},[7151,7152,7153],"mop","op-symbol","large-op","position:relative;top:0em;","∑",[68,7157],{"className":7158,"style":7159},[7132],"margin-right:0.1667em;",[68,7161,7163],{"className":7162},[7047,7048],"m",[68,7165,7054],{"className":7166},[7047,7048],[68,7168,7058],{"className":7169},[7047,7048],[68,7171,7173],{"className":7172},[7047,7048],"t",[68,7175,7177],{"className":7176},[7047,7048],"an",[68,7179,7181,7184],{"className":7180},[7047],[68,7182,7173],{"className":7183},[7047,7048],[68,7185,7187],{"className":7186},[7073],[68,7188,7190,7233],{"className":7189},[7077,7078],[68,7191,7193,7230],{"className":7192},[7082],[68,7194,7197],{"className":7195,"style":7196},[7086],"height:0.3361em;",[68,7198,7199,7202],{"style":7090},[68,7200],{"className":7201,"style":7095},[7094],[68,7203,7205],{"className":7204},[7099,7100,7101,7102],[68,7206,7208,7213,7216,7220,7223,7226],{"className":7207},[7047,7102],[68,7209,7212],{"className":7210,"style":7211},[7047,7048,7102],"margin-right:0.10764em;","f",[68,7214,979],{"className":7215},[7047,7048,7102],[68,7217,7219],{"className":7218},[7047,7048,7102],"c",[68,7221,7173],{"className":7222},[7047,7048,7102],[68,7224,7062],{"className":7225},[7047,7048,7102],[68,7227,7229],{"className":7228},[7047,7048,7102],"re",[68,7231,7119],{"className":7232},[7118],[68,7234,7236],{"className":7235},[7082],[68,7237,7239],{"className":7238,"style":7126},[7086],[68,7240],{},[68,7242],{"className":7243,"style":7244},[7132],"margin-right:0.2222em;",[68,7246,7249],{"className":7247},[7248],"mbin","−",[68,7251],{"className":7252,"style":7244},[7132],[68,7254,7256,7260,7264,7267,7270,7275,7279,7283,7346,7349,7353],{"className":7255},[7038],[68,7257],{"className":7258,"style":7259},[7042],"height:1em;vertical-align:-0.25em;",[68,7261,1050],{"className":7262},[7263],"mopen",[68,7265,7069],{"className":7266},[7047,7048],[68,7268,979],{"className":7269},[7047,7048],[68,7271,7274],{"className":7272,"style":7273},[7047,7048],"margin-right:0.01968em;","l",[68,7276,7278],{"className":7277},[7047,7048],"ai",[68,7280,2167],{"className":7281,"style":7282},[7047,7048],"margin-right:0.02778em;",[68,7284,7286,7290],{"className":7285},[7047],[68,7287,7289],{"className":7288},[7047,7048],"e",[68,7291,7293],{"className":7292},[7073],[68,7294,7296,7337],{"className":7295},[7077,7078],[68,7297,7299,7334],{"className":7298},[7082],[68,7300,7302],{"className":7301,"style":7196},[7086],[68,7303,7304,7307],{"style":7090},[68,7305],{"className":7306,"style":7095},[7094],[68,7308,7310],{"className":7309},[7099,7100,7101,7102],[68,7311,7313,7316,7319,7322,7325,7328,7331],{"className":7312},[7047,7102],[68,7314,7163],{"className":7315},[7047,7048,7102],[68,7317,7289],{"className":7318},[7047,7048,7102],[68,7320,7058],{"className":7321},[7047,7048,7102],[68,7323,7069],{"className":7324},[7047,7048,7102],[68,7326,7062],{"className":7327},[7047,7048,7102],[68,7329,7289],{"className":7330},[7047,7048,7102],[68,7332,7274],{"className":7333,"style":7273},[7047,7048,7102],[68,7335,7119],{"className":7336},[7118],[68,7338,7340],{"className":7339},[7082],[68,7341,7344],{"className":7342,"style":7343},[7086],"height:0.15em;",[68,7345],{},[68,7347],{"className":7348,"style":7244},[7132],[68,7350,7352],{"className":7351},[7248],"×",[68,7354],{"className":7355,"style":7244},[7132],[68,7357,7359,7363,7366,7421,7424,7427],{"className":7358},[7038],[68,7360],{"className":7361,"style":7362},[7042],"height:0.8444em;vertical-align:-0.15em;",[68,7364,7058],{"className":7365},[7047,7048],[68,7367,7369,7373],{"className":7368},[7047],[68,7370,7372],{"className":7371},[7047,7048],"b",[68,7374,7376],{"className":7375},[7073],[68,7377,7379,7413],{"className":7378},[7077,7078],[68,7380,7382,7410],{"className":7381},[7082],[68,7383,7386],{"className":7384,"style":7385},[7086],"height:0.3117em;",[68,7387,7388,7391],{"style":7090},[68,7389],{"className":7390,"style":7095},[7094],[68,7392,7394],{"className":7393},[7099,7100,7101,7102],[68,7395,7397,7400,7403,7407],{"className":7396},[7047,7102],[68,7398,7163],{"className":7399},[7047,7048,7102],[68,7401,7054],{"className":7402},[7047,7048,7102],[68,7404,7406],{"className":7405},[7047,7048,7102],"i",[68,7408,7069],{"className":7409},[7047,7048,7102],[68,7411,7119],{"className":7412},[7118],[68,7414,7416],{"className":7415},[7082],[68,7417,7419],{"className":7418,"style":7343},[7086],[68,7420],{},[68,7422],{"className":7423,"style":7244},[7132],[68,7425,7352],{"className":7426},[7248],[68,7428],{"className":7429,"style":7244},[7132],[68,7431,7433,7437,7440,7443,7446,7507,7511,7514,7517],{"className":7432},[7038],[68,7434],{"className":7435,"style":7436},[7042],"height:1.0361em;vertical-align:-0.2861em;",[68,7438,7173],{"className":7439},[7047,7048],[68,7441,979],{"className":7442},[7047,7048],[68,7444,7062],{"className":7445},[7047,7048],[68,7447,7449,7453],{"className":7448},[7047],[68,7450,7452],{"className":7451},[7047,7048],"x",[68,7454,7456],{"className":7455},[7073],[68,7457,7459,7499],{"className":7458},[7077,7078],[68,7460,7462,7496],{"className":7461},[7082],[68,7463,7465],{"className":7464,"style":7196},[7086],[68,7466,7467,7470],{"style":7090},[68,7468],{"className":7469,"style":7095},[7094],[68,7471,7473],{"className":7472},[7099,7100,7101,7102],[68,7474,7476,7479,7482,7486,7490,7493],{"className":7475},[7047,7102],[68,7477,7289],{"className":7478},[7047,7048,7102],[68,7480,7163],{"className":7481},[7047,7048,7102],[68,7483,7485],{"className":7484,"style":7273},[7047,7048,7102],"pl",[68,7487,7489],{"className":7488},[7047,7048,7102],"oye",[68,7491,7062],{"className":7492},[7047,7048,7102],[68,7494,2167],{"className":7495,"style":7282},[7047,7048,7102],[68,7497,7119],{"className":7498},[7118],[68,7500,7502],{"className":7501},[7082],[68,7503,7505],{"className":7504,"style":7126},[7086],[68,7506],{},[68,7508,1395],{"className":7509},[7510],"mclose",[68,7512],{"className":7513,"style":7244},[7132],[68,7515,7249],{"className":7516},[7248],[68,7518],{"className":7519,"style":7244},[7132],[68,7521,7523,7526,7529,7532,7535,7538,7541,7544,7600,7603,7606],{"className":7522},[7038],[68,7524],{"className":7525,"style":7436},[7042],[68,7527,1050],{"className":7528},[7263],[68,7530,7485],{"className":7531,"style":7273},[7047,7048],[68,7533,979],{"className":7534},[7047,7048],[68,7536,7212],{"className":7537,"style":7211},[7047,7048],[68,7539,7054],{"className":7540},[7047,7048],[68,7542,7058],{"className":7543},[7047,7048],[68,7545,7547,7550],{"className":7546},[7047],[68,7548,2180],{"className":7549},[7047,7048],[68,7551,7553],{"className":7552},[7073],[68,7554,7556,7592],{"className":7555},[7077,7078],[68,7557,7559,7589],{"className":7558},[7082],[68,7560,7563],{"className":7561,"style":7562},[7086],"height:0.1514em;",[68,7564,7565,7568],{"style":7090},[68,7566],{"className":7567,"style":7095},[7094],[68,7569,7571],{"className":7570},[7099,7100,7101,7102],[68,7572,7574,7578,7581,7586],{"className":7573},[7047,7102],[68,7575,7577],{"className":7576},[7047,7048,7102],"ma",[68,7579,2167],{"className":7580,"style":7282},[7047,7048,7102],[68,7582,7585],{"className":7583,"style":7584},[7047,7048,7102],"margin-right:0.03588em;","g",[68,7587,7289],{"className":7588},[7047,7048,7102],[68,7590,7119],{"className":7591},[7118],[68,7593,7595],{"className":7594},[7082],[68,7596,7598],{"className":7597,"style":7126},[7086],[68,7599],{},[68,7601],{"className":7602,"style":7244},[7132],[68,7604,7352],{"className":7605},[7248],[68,7607],{"className":7608,"style":7244},[7132],[68,7610,7612,7615,7618,7670],{"className":7611},[7038],[68,7613],{"className":7614,"style":7259},[7042],[68,7616,7058],{"className":7617},[7047,7048],[68,7619,7621,7624],{"className":7620},[7047],[68,7622,7372],{"className":7623},[7047,7048],[68,7625,7627],{"className":7626},[7073],[68,7628,7630,7662],{"className":7629},[7077,7078],[68,7631,7633,7659],{"className":7632},[7082],[68,7634,7636],{"className":7635,"style":7385},[7086],[68,7637,7638,7641],{"style":7090},[68,7639],{"className":7640,"style":7095},[7094],[68,7642,7644],{"className":7643},[7099,7100,7101,7102],[68,7645,7647,7650,7653,7656],{"className":7646},[7047,7102],[68,7648,7163],{"className":7649},[7047,7048,7102],[68,7651,7054],{"className":7652},[7047,7048,7102],[68,7654,7406],{"className":7655},[7047,7048,7102],[68,7657,7069],{"className":7658},[7047,7048,7102],[68,7660,7119],{"className":7661},[7118],[68,7663,7665],{"className":7664},[7082],[68,7666,7668],{"className":7667,"style":7343},[7086],[68,7669],{},[68,7671,1395],{"className":7672},[7510],[19,7674,7675],{},"Vous y avez compris quelque chose ? Ne vous en faites pas, on va expliciter tout ça.",[19,7677,7678],{},"Gardons en tête que:",[23,7680,7681,7684,7687,7690,7693],{},[26,7682,7683],{},"Le plafond de la marge HoppR par mois est fixe.",[26,7685,7686],{},"Le chiffre d'affaire est la somme du montant des factures, une facture peut être négative (note de frais par exemple).",[26,7688,7689],{},"Le coût employeur est un taux fixe.",[26,7691,7692],{},"Le package est l'addition du salaire et du bonus HoppR.",[26,7694,7695],{},"Si le bonus HoppR est négatif, il est considéré comme nul, autrement dit, le package sera le salaire brut.",[19,7697,7698,7699,7704,7705,7710,7711,7714],{},"Nous avons décidé de partir sur ",[979,7700,7703],{"href":7701,"rel":7702},"https:\u002F\u002Fgleam.run\u002F",[983],"Gleam",", un langage fonctionnel, strictement typé, basé sur ",[979,7706,7709],{"href":7707,"rel":7708},"https:\u002F\u002Felixir-lang.org\u002F",[983],"Elixir",". Ce langage offre la possibilité de créer des alias de type, par exemple ",[65,7712,7713],{},"type MaString = string",". Cette mécanique est une vraie force car elle permet de mettre en évidence les mots clés du métier (ici celui du Package chez HoppR).",[19,7716,7717,7718,1023,7721,1023,7724,1023,7727,1023,7730,393],{},"On retrouve ainsi des types comme ",[65,7719,7720],{},"ConsultantId",[65,7722,7723],{},"Revenue",[65,7725,7726],{},"GrossSalary",[65,7728,7729],{},"EmployerRate",[65,7731,7732],{},"GrossMargin",[19,7734,7735],{},"Notre service métier de calcul de package va quant à lui s'exprimer sous la forme d'une fonction.",[58,7737,7741],{"className":7738,"code":7739,"language":7740,"meta":63,"style":63},"language-elixir shiki shiki-themes github-dark-default","pub fn calculate_annual_packaging(\n  revenues: List(Revenue),\n  gross_salary: GrossSalary,\n  employer_rate: EmployerRate,\n  max_margin: GrossMargin,\n) -> Package {\n  let employer_cost = gross_salary |> to_employer_cost(employer_rate)\n\n  revenues\n  |> cumulate\n  |> to_gross_margin(employer_cost)\n  |> cap_at(max_margin)\n  |> to_bonus(employer_rate)\n  |> to_package(gross_salary)\n}\n\n\u002F\u002F Quelques exemples de ce à quoi peuvent ressembler les tests du domaine\npub fn only_one_mission_full_time_test() {...}\npub fn three_missions_during_the_year_test() {...}\npub fn package_is_the_gross_salary_when_the_company_loses_money_test() {...}\n","elixir",[65,7742,7743,7748,7753,7758,7763,7768,7773,7778,7782,7787,7792,7797,7802,7807,7812,7816,7820,7825,7830,7835],{"__ignoreMap":63},[68,7744,7745],{"class":70,"line":71},[68,7746,7747],{},"pub fn calculate_annual_packaging(\n",[68,7749,7750],{"class":70,"line":86},[68,7751,7752],{},"  revenues: List(Revenue),\n",[68,7754,7755],{"class":70,"line":99},[68,7756,7757],{},"  gross_salary: GrossSalary,\n",[68,7759,7760],{"class":70,"line":106},[68,7761,7762],{},"  employer_rate: EmployerRate,\n",[68,7764,7765],{"class":70,"line":112},[68,7766,7767],{},"  max_margin: GrossMargin,\n",[68,7769,7770],{"class":70,"line":148},[68,7771,7772],{},") -> Package {\n",[68,7774,7775],{"class":70,"line":153},[68,7776,7777],{},"  let employer_cost = gross_salary |> to_employer_cost(employer_rate)\n",[68,7779,7780],{"class":70,"line":166},[68,7781,103],{"emptyLinePlaceholder":102},[68,7783,7784],{"class":70,"line":177},[68,7785,7786],{},"  revenues\n",[68,7788,7789],{"class":70,"line":182},[68,7790,7791],{},"  |> cumulate\n",[68,7793,7794],{"class":70,"line":202},[68,7795,7796],{},"  |> to_gross_margin(employer_cost)\n",[68,7798,7799],{"class":70,"line":217},[68,7800,7801],{},"  |> cap_at(max_margin)\n",[68,7803,7804],{"class":70,"line":223},[68,7805,7806],{},"  |> to_bonus(employer_rate)\n",[68,7808,7809],{"class":70,"line":228},[68,7810,7811],{},"  |> to_package(gross_salary)\n",[68,7813,7814],{"class":70,"line":248},[68,7815,466],{},[68,7817,7818],{"class":70,"line":266},[68,7819,103],{"emptyLinePlaceholder":102},[68,7821,7822],{"class":70,"line":280},[68,7823,7824],{},"\u002F\u002F Quelques exemples de ce à quoi peuvent ressembler les tests du domaine\n",[68,7826,7827],{"class":70,"line":286},[68,7828,7829],{},"pub fn only_one_mission_full_time_test() {...}\n",[68,7831,7832],{"class":70,"line":291},[68,7833,7834],{},"pub fn three_missions_during_the_year_test() {...}\n",[68,7836,7837],{"class":70,"line":311},[68,7838,7839],{},"pub fn package_is_the_gross_salary_when_the_company_loses_money_test() {...}\n",[19,7841,7842,7843,3570,7846,7849],{},"Ici la lecture de la règle de calcul se voit facilitée par l'usage du ",[1613,7844,7845],{},"pipe operator",[65,7847,7848],{},"|>"," qui envoie dans le premier argument de la fonction du dessous le résultat de la fonction du dessus.",[19,7851,7852,7853,7856],{},"On peut donc lire le code comme on le ferait en langage naturel: on cumule les ",[65,7854,7855],{},"revenues"," auquel on applique une marge brute capée à la marge max. On applique le bonus si bonus il y a et on transforme le tout en package final.",[19,7858,7859],{},"La logique métier est explicite, pure et déterministe.",[582,7861,7863],{"id":7862},"aparté-sur-les-value-objects-et-le-pipe-operator","Aparté sur les value objects et le pipe operator",[19,7865,7866,7867,7872,7873,393],{},"Pour éviter le ",[979,7868,7871],{"href":7869,"rel":7870},"https:\u002F\u002Frefactoring.guru\u002Ffr\u002Fsmells\u002Fprimitive-obsession",[983],"primitive obsession"," et mettre en évidence les concepts du vocabulaire métier, nous créons ce que l'on appelle des ",[1311,7874,7875],{},"value objects",[19,7877,7878],{},"Dans la programmation orientée objet, nous aurions quelque chose comme:",[58,7880,7882],{"className":60,"code":7881,"language":62,"meta":63,"style":63},"record Revenue(BigDecimal amount) {}\n\nrecord Revenues(List\u003CRevenue> revenues) {\n    public Revenue cumulate() {\n        return new Revenue(revenues.stream()\n                .map(Revenue::amount)\n                .reduce(BigDecimal.ZERO, BigDecimal::add));\n    }\n}\n",[65,7883,7884,7895,7899,7918,7930,7946,7960,7975,7979],{"__ignoreMap":63},[68,7885,7886,7889,7892],{"class":70,"line":71},[68,7887,7888],{"class":78},"record",[68,7890,7891],{"class":92}," Revenue",[68,7893,7894],{"class":74},"(BigDecimal amount) {}\n",[68,7896,7897],{"class":70,"line":86},[68,7898,103],{"emptyLinePlaceholder":102},[68,7900,7901,7903,7906,7909,7911,7913,7915],{"class":70,"line":99},[68,7902,7888],{"class":78},[68,7904,7905],{"class":92}," Revenues",[68,7907,7908],{"class":74},"(List",[68,7910,127],{"class":78},[68,7912,7723],{"class":74},[68,7914,2033],{"class":78},[68,7916,7917],{"class":74}," revenues) {\n",[68,7919,7920,7922,7925,7928],{"class":70,"line":106},[68,7921,185],{"class":78},[68,7923,7924],{"class":74}," Revenue ",[68,7926,7927],{"class":196},"cumulate",[68,7929,199],{"class":74},[68,7931,7932,7934,7936,7938,7941,7944],{"class":70,"line":112},[68,7933,205],{"class":78},[68,7935,142],{"class":78},[68,7937,7891],{"class":196},[68,7939,7940],{"class":74},"(revenues.",[68,7942,7943],{"class":196},"stream",[68,7945,308],{"class":74},[68,7947,7948,7950,7952,7955,7957],{"class":70,"line":148},[68,7949,314],{"class":74},[68,7951,673],{"class":196},[68,7953,7954],{"class":74},"(Revenue",[68,7956,679],{"class":78},[68,7958,7959],{"class":74},"amount)\n",[68,7961,7962,7964,7967,7970,7972],{"class":70,"line":153},[68,7963,314],{"class":74},[68,7965,7966],{"class":196},"reduce",[68,7968,7969],{"class":74},"(BigDecimal.ZERO, BigDecimal",[68,7971,679],{"class":78},[68,7973,7974],{"class":74},"add));\n",[68,7976,7977],{"class":70,"line":166},[68,7978,220],{"class":74},[68,7980,7981],{"class":70,"line":177},[68,7982,466],{"class":74},[19,7984,7985],{},"L'objet expose ses propres méthodes pour pouvoir faire des opérations métiers. Cela impose que toute nouvelle opération soit portée par le dit value object. Le pipe operator permet d'étendre les possibilités du concept sans modifier le concept lui-même.\nCeci offre une vraie flexibilité, de la lisibilité. C'est la composition fonctionnelle !",[39,7987,7989],{"id":7988},"services-purs-et-dépendances-explicites","Services purs et dépendances explicites",[19,7991,7992],{},"Le use case principal dépend de données externes (financier + paie). En programmation fonctionnelle, ces dépendances sont injectées via des fonctions :",[58,7994,7996],{"className":7738,"code":7995,"language":7740,"meta":63,"style":63},"pub type GetFinancialDataPort =\n  fn(ConsultantId) -> FinancialData\npub type GetCompanyPayrollDataPort =\n  fn() -> CompanyPayrollData\n\npub fn calculate_consultant_annual_packaging(\n  consultant_id: ConsultantId,\n  get_financial_data: GetFinancialDataPort,\n  get_company_payroll_data: GetCompanyPayrollDataPort,\n) {\n  \u002F\u002F ici l'implémentation de la fonction va chercher l'info dans une api externe\n  let CompanyPayrollDataPort(employer_rate, max_margin) = get_company_payroll_data()\n  \u002F\u002F ici l'implémentation de la fonction va chercher l'info dans une base de données\n  let FinancialDataPort(revenues, gross_salary) = get_financial_data(consultant_id)\n  calculate_annual_packaging(revenues, gross_salary, employer_rate, max_margin)\n}\n",[65,7997,7998,8003,8008,8013,8018,8022,8027,8032,8037,8042,8046,8051,8056,8061,8066,8071],{"__ignoreMap":63},[68,7999,8000],{"class":70,"line":71},[68,8001,8002],{},"pub type GetFinancialDataPort =\n",[68,8004,8005],{"class":70,"line":86},[68,8006,8007],{},"  fn(ConsultantId) -> FinancialData\n",[68,8009,8010],{"class":70,"line":99},[68,8011,8012],{},"pub type GetCompanyPayrollDataPort =\n",[68,8014,8015],{"class":70,"line":106},[68,8016,8017],{},"  fn() -> CompanyPayrollData\n",[68,8019,8020],{"class":70,"line":112},[68,8021,103],{"emptyLinePlaceholder":102},[68,8023,8024],{"class":70,"line":148},[68,8025,8026],{},"pub fn calculate_consultant_annual_packaging(\n",[68,8028,8029],{"class":70,"line":153},[68,8030,8031],{},"  consultant_id: ConsultantId,\n",[68,8033,8034],{"class":70,"line":166},[68,8035,8036],{},"  get_financial_data: GetFinancialDataPort,\n",[68,8038,8039],{"class":70,"line":177},[68,8040,8041],{},"  get_company_payroll_data: GetCompanyPayrollDataPort,\n",[68,8043,8044],{"class":70,"line":182},[68,8045,245],{},[68,8047,8048],{"class":70,"line":202},[68,8049,8050],{},"  \u002F\u002F ici l'implémentation de la fonction va chercher l'info dans une api externe\n",[68,8052,8053],{"class":70,"line":217},[68,8054,8055],{},"  let CompanyPayrollDataPort(employer_rate, max_margin) = get_company_payroll_data()\n",[68,8057,8058],{"class":70,"line":223},[68,8059,8060],{},"  \u002F\u002F ici l'implémentation de la fonction va chercher l'info dans une base de données\n",[68,8062,8063],{"class":70,"line":228},[68,8064,8065],{},"  let FinancialDataPort(revenues, gross_salary) = get_financial_data(consultant_id)\n",[68,8067,8068],{"class":70,"line":248},[68,8069,8070],{},"  calculate_annual_packaging(revenues, gross_salary, employer_rate, max_margin)\n",[68,8072,8073],{"class":70,"line":266},[68,8074,466],{},[19,8076,8077],{},"Les ports sont des types de fonctions, et la composition des règles reste locale au domaine. Le métier ne dépend d'aucune techno (HTTP, DB, etc.).",[582,8079,8081],{"id":8080},"et-en-programmation-orientée-objet","Et en programmation orientée objet",[19,8083,8084],{},"En Java, on représente les mêmes concepts via des records, classes et interfaces. Le vocabulaire métier est identique, mais l'encapsulation se fait par objets plutôt que par des fonctions.",[58,8086,8088],{"className":60,"code":8087,"language":62,"meta":63,"style":63},"public interface FinancialDataPort {\n  FinancialData forConsultant(ConsultantId consultantId);\n}\n\npublic interface CompanyPayrollPort {\n  CompanyPayrollData get();\n}\n\npublic final class PackageCalculator {\n  private final FinancialDataPort financialDataPort;\n  private final CompanyPayrollPort payrollDataPort;\n\n  public PackageCalculator(FinancialDataPort financialDataPort,\n                           CompanyPayrollPort payrollDataPort) {\n    this.financialDataPort = financialDataPort;\n    this.payrollDataPort = payrollDataPort;\n  }\n\n  public Package calculateFor(ConsultantId consultantId) {\n    var payroll = payrollDataPort.get();\n    var financial = financialDataPort.forConsultant(consultantId);\n    return calculateAnnualPackaging(\n      financial.revenues(),\n      financial.grossSalary(),\n      payroll.employerRate(),\n      payroll.maxMargin()\n    );\n  }\n\n  private Package calculateAnnualPackaging(\n    Revenues revenues,\n    GrossSalary grossSalary,\n    EmployerRate employerRate,\n    GrossMargin maxMargin\n  ) {\n    var employerCost = grossSalary.multiply(employerRate);\n    var grossMargin = revenues.subtract(employerCost);\n    var cappedMargin = grossMargin.subtract(maxMargin);\n    var bonus = cappedMargin.apply(employerRate);\n    var total = grossSalary.add(bonus);\n\n    return new Package(\n      bonus,\n      grossSalary,\n      total\n    );\n  }\n}\n",[65,8089,8090,8103,8119,8123,8127,8138,8147,8151,8155,8169,8181,8192,8196,8212,8222,8234,8245,8250,8254,8270,8287,8304,8313,8323,8332,8342,8351,8356,8360,8364,8375,8384,8393,8402,8410,8415,8433,8451,8468,8484,8500,8505,8517,8523,8529,8535,8540,8545],{"__ignoreMap":63},[68,8091,8092,8095,8098,8101],{"class":70,"line":71},[68,8093,8094],{"class":78},"public",[68,8096,8097],{"class":78}," interface",[68,8099,8100],{"class":92}," FinancialDataPort",[68,8102,96],{"class":74},[68,8104,8105,8108,8111,8114,8117],{"class":70,"line":86},[68,8106,8107],{"class":74},"  FinancialData ",[68,8109,8110],{"class":196},"forConsultant",[68,8112,8113],{"class":74},"(ConsultantId ",[68,8115,8116],{"class":92},"consultantId",[68,8118,552],{"class":74},[68,8120,8121],{"class":70,"line":99},[68,8122,466],{"class":74},[68,8124,8125],{"class":70,"line":106},[68,8126,103],{"emptyLinePlaceholder":102},[68,8128,8129,8131,8133,8136],{"class":70,"line":112},[68,8130,8094],{"class":78},[68,8132,8097],{"class":78},[68,8134,8135],{"class":92}," CompanyPayrollPort",[68,8137,96],{"class":74},[68,8139,8140,8143,8145],{"class":70,"line":148},[68,8141,8142],{"class":74},"  CompanyPayrollData ",[68,8144,3633],{"class":196},[68,8146,348],{"class":74},[68,8148,8149],{"class":70,"line":153},[68,8150,466],{"class":74},[68,8152,8153],{"class":70,"line":166},[68,8154,103],{"emptyLinePlaceholder":102},[68,8156,8157,8159,8161,8164,8167],{"class":70,"line":177},[68,8158,8094],{"class":78},[68,8160,118],{"class":78},[68,8162,8163],{"class":78}," class",[68,8165,8166],{"class":92}," PackageCalculator",[68,8168,96],{"class":74},[68,8170,8171,8174,8176,8178],{"class":70,"line":182},[68,8172,8173],{"class":78},"  private",[68,8175,118],{"class":78},[68,8177,8100],{"class":74},[68,8179,8180],{"class":74}," financialDataPort;\n",[68,8182,8183,8185,8187,8189],{"class":70,"line":202},[68,8184,8173],{"class":78},[68,8186,118],{"class":78},[68,8188,8135],{"class":74},[68,8190,8191],{"class":74}," payrollDataPort;\n",[68,8193,8194],{"class":70,"line":217},[68,8195,103],{"emptyLinePlaceholder":102},[68,8197,8198,8201,8203,8206,8209],{"class":70,"line":223},[68,8199,8200],{"class":78},"  public",[68,8202,8166],{"class":196},[68,8204,8205],{"class":74},"(FinancialDataPort ",[68,8207,8208],{"class":92},"financialDataPort",[68,8210,8211],{"class":74},",\n",[68,8213,8214,8217,8220],{"class":70,"line":228},[68,8215,8216],{"class":74},"                           CompanyPayrollPort ",[68,8218,8219],{"class":92},"payrollDataPort",[68,8221,245],{"class":74},[68,8223,8224,8227,8230,8232],{"class":70,"line":248},[68,8225,8226],{"class":260},"    this",[68,8228,8229],{"class":74},".financialDataPort ",[68,8231,437],{"class":78},[68,8233,8180],{"class":74},[68,8235,8236,8238,8241,8243],{"class":70,"line":266},[68,8237,8226],{"class":260},[68,8239,8240],{"class":74},".payrollDataPort ",[68,8242,437],{"class":78},[68,8244,8191],{"class":74},[68,8246,8247],{"class":70,"line":280},[68,8248,8249],{"class":74},"  }\n",[68,8251,8252],{"class":70,"line":286},[68,8253,103],{"emptyLinePlaceholder":102},[68,8255,8256,8258,8261,8264,8266,8268],{"class":70,"line":291},[68,8257,8200],{"class":78},[68,8259,8260],{"class":74}," Package ",[68,8262,8263],{"class":196},"calculateFor",[68,8265,8113],{"class":74},[68,8267,8116],{"class":92},[68,8269,245],{"class":74},[68,8271,8272,8275,8278,8280,8283,8285],{"class":70,"line":311},[68,8273,8274],{"class":78},"    var",[68,8276,8277],{"class":74}," payroll",[68,8279,139],{"class":78},[68,8281,8282],{"class":74}," payrollDataPort.",[68,8284,3633],{"class":196},[68,8286,348],{"class":74},[68,8288,8289,8291,8294,8296,8299,8301],{"class":70,"line":323},[68,8290,8274],{"class":78},[68,8292,8293],{"class":74}," financial",[68,8295,139],{"class":78},[68,8297,8298],{"class":74}," financialDataPort.",[68,8300,8110],{"class":196},[68,8302,8303],{"class":74},"(consultantId);\n",[68,8305,8306,8308,8311],{"class":70,"line":340},[68,8307,3974],{"class":78},[68,8309,8310],{"class":196}," calculateAnnualPackaging",[68,8312,1821],{"class":74},[68,8314,8315,8318,8320],{"class":70,"line":351},[68,8316,8317],{"class":74},"      financial.",[68,8319,7855],{"class":196},[68,8321,8322],{"class":74},"(),\n",[68,8324,8325,8327,8330],{"class":70,"line":356},[68,8326,8317],{"class":74},[68,8328,8329],{"class":196},"grossSalary",[68,8331,8322],{"class":74},[68,8333,8334,8337,8340],{"class":70,"line":362},[68,8335,8336],{"class":74},"      payroll.",[68,8338,8339],{"class":196},"employerRate",[68,8341,8322],{"class":74},[68,8343,8344,8346,8349],{"class":70,"line":377},[68,8345,8336],{"class":74},[68,8347,8348],{"class":196},"maxMargin",[68,8350,308],{"class":74},[68,8352,8353],{"class":70,"line":382},[68,8354,8355],{"class":74},"    );\n",[68,8357,8358],{"class":70,"line":388},[68,8359,8249],{"class":74},[68,8361,8362],{"class":70,"line":401},[68,8363,103],{"emptyLinePlaceholder":102},[68,8365,8366,8368,8370,8373],{"class":70,"line":406},[68,8367,8173],{"class":78},[68,8369,8260],{"class":74},[68,8371,8372],{"class":196},"calculateAnnualPackaging",[68,8374,1821],{"class":74},[68,8376,8377,8380,8382],{"class":70,"line":411},[68,8378,8379],{"class":74},"    Revenues ",[68,8381,7855],{"class":92},[68,8383,8211],{"class":74},[68,8385,8386,8389,8391],{"class":70,"line":429},[68,8387,8388],{"class":74},"    GrossSalary ",[68,8390,8329],{"class":92},[68,8392,8211],{"class":74},[68,8394,8395,8398,8400],{"class":70,"line":447},[68,8396,8397],{"class":74},"    EmployerRate ",[68,8399,8339],{"class":92},[68,8401,8211],{"class":74},[68,8403,8404,8407],{"class":70,"line":452},[68,8405,8406],{"class":74},"    GrossMargin ",[68,8408,8409],{"class":92},"maxMargin\n",[68,8411,8412],{"class":70,"line":457},[68,8413,8414],{"class":74},"  ) {\n",[68,8416,8417,8419,8422,8424,8427,8430],{"class":70,"line":463},[68,8418,8274],{"class":78},[68,8420,8421],{"class":74}," employerCost",[68,8423,139],{"class":78},[68,8425,8426],{"class":74}," grossSalary.",[68,8428,8429],{"class":196},"multiply",[68,8431,8432],{"class":74},"(employerRate);\n",[68,8434,8435,8437,8440,8442,8445,8448],{"class":70,"line":953},[68,8436,8274],{"class":78},[68,8438,8439],{"class":74}," grossMargin",[68,8441,139],{"class":78},[68,8443,8444],{"class":74}," revenues.",[68,8446,8447],{"class":196},"subtract",[68,8449,8450],{"class":74},"(employerCost);\n",[68,8452,8453,8455,8458,8460,8463,8465],{"class":70,"line":962},[68,8454,8274],{"class":78},[68,8456,8457],{"class":74}," cappedMargin",[68,8459,139],{"class":78},[68,8461,8462],{"class":74}," grossMargin.",[68,8464,8447],{"class":196},[68,8466,8467],{"class":74},"(maxMargin);\n",[68,8469,8470,8472,8475,8477,8480,8482],{"class":70,"line":967},[68,8471,8274],{"class":78},[68,8473,8474],{"class":74}," bonus",[68,8476,139],{"class":78},[68,8478,8479],{"class":74}," cappedMargin.",[68,8481,396],{"class":196},[68,8483,8432],{"class":74},[68,8485,8486,8488,8491,8493,8495,8497],{"class":70,"line":972},[68,8487,8274],{"class":78},[68,8489,8490],{"class":74}," total",[68,8492,139],{"class":78},[68,8494,8426],{"class":74},[68,8496,371],{"class":196},[68,8498,8499],{"class":74},"(bonus);\n",[68,8501,8503],{"class":70,"line":8502},41,[68,8504,103],{"emptyLinePlaceholder":102},[68,8506,8508,8510,8512,8515],{"class":70,"line":8507},42,[68,8509,3974],{"class":78},[68,8511,142],{"class":78},[68,8513,8514],{"class":196}," Package",[68,8516,1821],{"class":74},[68,8518,8520],{"class":70,"line":8519},43,[68,8521,8522],{"class":74},"      bonus,\n",[68,8524,8526],{"class":70,"line":8525},44,[68,8527,8528],{"class":74},"      grossSalary,\n",[68,8530,8532],{"class":70,"line":8531},45,[68,8533,8534],{"class":74},"      total\n",[68,8536,8538],{"class":70,"line":8537},46,[68,8539,8355],{"class":74},[68,8541,8543],{"class":70,"line":8542},47,[68,8544,8249],{"class":74},[68,8546,8548],{"class":70,"line":8547},48,[68,8549,466],{"class":74},[19,8551,8552],{},"Le cœur métier reste le même : règles, vocabulaire, invariants. Les dépendances sont injectées depuis l'extérieur du domaine.",[19,8554,8555],{},"Finalement, seule la forme change.",[39,8557,1161],{"id":1160},[19,8559,8560,8561,8564],{},"En programmation fonctionnelle, les règles sont pures et les dépendances explicites.En programmation objet, elles sont encapsulées dans des objets et des interfaces.  Dans l’un comme dans l’autre, l’architecture hexagonale s’y applique de manière égale: le métier et son vocabulaire au centre de l’application. Autrement dit, c",[1311,8562,8563],{},"e n'est pas parce qu'on ne fait que des fonctions qu'il n'y a pas d'architecture en couches: on peut clairement distinguer le domaine métier de l'infrastructure."," La programmation fonctionnelle n'est donc pas un obstacle à la valorisation du métier dans le code. Elle peut au contraire rendre le vocabulaire plus lisible et les règles plus testables.",[1166,8566,8567],{},"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 pre.shiki code .suJrU, html code.shiki .suJrU{--shiki-default:#FF7B72}html pre.shiki code .sQhOw, html code.shiki .sQhOw{--shiki-default:#FFA657}html pre.shiki code .sZEs4, html code.shiki .sZEs4{--shiki-default:#E6EDF3}html pre.shiki code .sc3cj, html code.shiki .sc3cj{--shiki-default:#D2A8FF}html pre.shiki code .sFSAA, html code.shiki .sFSAA{--shiki-default:#79C0FF}",{"title":63,"searchDepth":86,"depth":86,"links":8569},[8570,8573,8576],{"id":7012,"depth":86,"text":7013,"children":8571},[8572],{"id":7862,"depth":99,"text":7863},{"id":7988,"depth":86,"text":7989,"children":8574},[8575],{"id":8080,"depth":99,"text":8081},{"id":1160,"depth":86,"text":1161},"2026-04-08T07:34:28.327Z","À mesure que les systèmes logiciels gagnent en complexité, les approches traditionnelles de conception montrent leurs limites. Couplage excessif, difficulté à tester, rigidité face au changement : aut",{},"\u002Fblogs\u002F2026-04-08-architecture-hexagonale-en-programmation-fonctionnelle-mythe-ou-ralit",[8582,8584,8586],{"id":1184,"name":1185,"image":8583,"linkedin":1187,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc88f5dfa-16db-4e6f-acf1-34dd80ee8766\u002Femma_hoppr.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466YZGE5L65%2F20260408%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260408T073428Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEC4aCXVzLXdlc3QtMiJHMEUCIQC89XiI4LV2nPeP5GwelaGxITa1i8vxE7nDPN%2FCOOIGPAIgIMuadU2XMtNuvRqanOfvVI6kHru%2B2YCLP1W9z9BRL6UqiAQI9%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAAGgw2Mzc0MjMxODM4MDUiDMWjV%2FO1zt1H6KDcMCrcA7q8rYPD7JArUtRE2XmtlGFi0Gb1u2jY%2B9Y3SPAbwzgUQRWQ1LEqXfyjuHcnYUj6O%2BiWWlqVuXmzWt1l7lG5Y7arPfzt6AjsXPmIzlmAvYMctlE1uESBOaThTr8QcjJR42skKrqwlIVe%2FFOfTIaN80P8yI23zbwYPGbrre3Q34lTWnE77vQ1FHlDAEnF4q6vCNLknS7i4J4vDz%2FPhtjrQMHSV1BSwg4czuhoUylgbdW1CL8HRfEeFraXH3AJ4wGhuMoxGCakNRvrw7KrF0jpzkULBEC4PwztMJcaWUnD6T2miI%2FUuL5g7ASMT7NtTPiiRFtg3beSatik6dY6JMEdSVElz5yspss5kTYq8YsIQlT8tLRejU4zgp78leO47JOHzmJnro2wd9YdGm%2BEqqiHPsku1HbLTV3oHwYEtPB9FBFqSC9V8AbocksXRXhfmXn8WnZbebEXMu7XiIp%2BiZjIcQC9Brq%2BicBi7iHaUQoAljN8RHny0jm%2FGsm4PRQNiCYbWJnmahrKNRWNkP5RiXqGZnjsXHInBSqkupWnekY9Bd9Vd11JiKSuGkj2ebN%2BuuF4KEtNARclVCjiTEQDt6jwpswqxeH4fTDflh5XXLwZvxU6RIJ1tjcQWwAxRLGRMObb184GOqUB6QApw17fi31uRcyevbEhsIVcF7cbhz5DT9rK2wxOH0%2FJF55Z%2BYpWz%2FPU%2B5b2R7H0t6SkCITFdxrerbjTKeo3ANn6Maeo3qDIWvnyFCzBZT4MaZW3wqjClgUj7X3KTeNPimG82xZWapTks839ysnOLg36HeDcQuWXHFJ73l4hJVzt84hWxgfsDVAj2LWIvdLzbEUnbJwlOUNalA4bSFXi3%2B%2BZSVGf&X-Amz-Signature=b23db6b30ffaf694e97c7283799bfd5930ff0c8de4e984d08997a4e68c32080e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"id":1195,"name":1196,"image":8585,"linkedin":1198,"x":1199},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc69d0b59-558d-4e48-879f-bea3fec1fdef\u002FLinkedin_Profile.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466XRONKPNZ%2F20260408%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260408T073428Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEDAaCXVzLXdlc3QtMiJIMEYCIQDm83y7rflR2VODU5aP5Qz692idMY%2BBAiAN%2BSxy%2BIDx8QIhAOa4gMORM%2FaT6TWEmlAYAB1EFUTqVOd8ObAa36VRLn1ZKogECPn%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQABoMNjM3NDIzMTgzODA1Igx2JIBqc1%2BH7MVdyPMq3AMwmiKyWYDt3nYnDnPmTWKFpHTuLOkgcCjGctfi8lEGyGwb8HnDFp3AGSj883xVmpSFG3ov9Gr0jbfhSvKTLITWxqJygrQWcaGjzsOJf2a%2BCJ1YrRQsGcOq9WQmDm%2BZhHrG5pNgk31O2f2hxAQP8kGkOC8Jxgz5i%2ByBwCv2fghEPKWCxHIu%2FnLv%2FhXKVET9jUK6L6WptG48fnveHprIygQH86y8dPYXMgAN9T0vx6kBoo%2B08NKPdTqNPAVqVyc9pqmixVE5%2FYYWUF9bJj3TbgGOM7nD84%2FvSBbwXZ6tNimPrdF1CmwsAID4sHly9nv3xvml6AWX09lwlYBpvChHrg88DKCJyzlX3fQoIJuUFMssgpP06IgkjTu%2Fnr3Xn5uGFTrIfL5rQ%2BzaYK%2FZEydEOzHikjrCo7K6xcZ52KaSP60Vn9BeslZ59%2BgDLrczqnXYc%2BKmYDxA%2BOsXObXQid1ODmY8uyRcVIogTAZZXhZ0A%2Bi4Jab9uw4tYXRJe%2BrKHn2xyU6qVUV8Yk%2BfK6ZiFWDz2V%2BwgHyegcnDoE%2BS2x1ckw6xTTeoJTZLnLrQOfKKsfbUnZhS%2Bz9vc1iZ%2BvyfWl4WCQPtFwu3AbDHGveoR2wvwgkrm1njJEU%2BkGJmdJJuszC5iNjOBjqkAYLjzrk9PZ61PMzHm%2FL6BGmd9EAJ05SyolUKGsI0gCLqm4I0mFF9BvGTn7y679fSfLS7vBM%2FDT1t2h5TD0H1yXt66PKQIFXPPMIFEQkFoMF4JwsmgNsAwbBRkURagTfGs50qrviYMBbEFKlnmiB4x%2FWI0IOonbk9v0ZBs7OlLZJHOwHgH5nN9Z1KuBEUGhdP9V%2B4zpiZTAlCThrQxBqa1eSxr%2FZb&X-Amz-Signature=ebff0fd05509337077f565c3ea9b944db6f15a3995bdd1bdfb7c90fa020e82fb&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"id":1190,"name":1191,"image":8587,"linkedin":1193,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F1f0a6cf7-edf2-44dc-babb-f1624e4f52b0\u002FPhoto_profil.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466WIK4JRTG%2F20260408%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260408T073428Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEC4aCXVzLXdlc3QtMiJHMEUCICuqELPi893QSyfwnyFTnMqcLUW8j4oiAxjFNZ3gFDtCAiEAgcg%2BHl1dGG5cM0uNfUCb0JlwgkzZ9eZmwtsFG5AyCuMqiAQI9%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAAGgw2Mzc0MjMxODM4MDUiDGHa9MlsDbZfN%2FYCKircA5gcGnNJ%2FBjl5mcv6g1TMFzbf7dYZESahFgAifbuK%2BBonIuiW%2FfR%2BTBODtvfbiNDgpbOjHteJv7%2FLOgZgpSP1epkw0qahEvYFogVnIgmzLd1roYHs626IPYVOS0RunsFyTGcQwSmc6hVyS4pfODT4OKwUE7EiGUqaQWg%2Fj7j12kZ2vv1FnXda7kxpzNDmLuLzlZp70ozboHb6%2FOUf3BhqdOVAAb1Y%2BK2xHnkdYAr%2BPVJ0v5qUPazBQAtUzMVwAgYxG2JugfVSn2sofk97v9b5pcJIhED4A4Cob%2F21PV2d9RiMTHjuKP2GoO6t96A%2BwpXcyFgb4GLlGugrJ%2B40w0Joq8w3Pfd0SayKfc72LE3%2B0NgPDdD3%2BPBxX8e6RYAxjGHSwwxoqXn0rwbRoa5e94lavTTwWEGXmlhJK6vyL7oMNAh76VCuiKU8zQR9iO4PwyYCOeyYXmwY9uSsjv1Xhnl5Jm6VsP%2BKqUR%2Bm8WmG3%2FSIxpZt0kkz2aF82DYYtBr0odjMLSWzxkezG0APvFa%2F%2FG%2B05zRflCS2EHTC7c3KyVP%2FO0Oz7QkF5vuXU%2F2fPrV4W1%2FSaX6M1s4y8UbTneRMlxLoqt5JSLJAsGyo6eo50VIh%2FeKCkQymrPWjIEIkaIMO7a184GOqUBK3sKSMU%2BE8%2FdgcSKNfCHNWUjsc3Q4%2FwCLEpB3gKjjU2rM9gS1cUnM9ROHr0wmcL3WKMsNhQ0m48hnO45ibaQ7AuaY9YmTTVOucTGGtqI6%2BQ%2FzksdZtEIgKbuMSFIR4TAp0QnGLKg%2B38rtJH3GS%2FB41z21vB74NsTbOLmVnnRN7b%2BUxPfPhInd7VMo9OVUSCVgqOXPGht2WN3qTzRkojiBxAGb%2FIq&X-Amz-Signature=03e3a75c38948c108cfd727f230ff460bccfea1ad49530d77d843f9e4556e253&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"title":6986,"description":8578},"blogs\u002F2026-04-08-architecture-hexagonale-en-programmation-fonctionnelle-mythe-ou-ralit\u002Findex",[1208],"5kjyA9V_-oP7-hiszOmgMcw7zYpEN_O9T0BmQwPEiJQ",{"id":8593,"title":8594,"alt":8595,"authors":8596,"body":8598,"date":8857,"description":8858,"extension":1178,"image":1179,"meta":8859,"navigation":102,"ogImage":1179,"path":8860,"published":102,"reviewers":8861,"seo":8872,"stem":8873,"tags":8874,"__hash__":8876},"blogs\u002Fblogs\u002F2026-04-14-rex-environnements-phmres\u002Findex.md","REX: Environnements éphémères","Image d’illustration, une usine à environnements éphémères qui sont créés, validés puis détruits",[8597],{"id":1190,"name":1191,"image":1560,"linkedin":1193,"x":1188},{"type":16,"value":8599,"toc":8847},[8600,8603,8619,8626,8632,8642,8646,8653,8656,8674,8678,8684,8688,8711,8727,8730,8736,8745,8750,8759,8763,8770,8777,8783,8787,8809,8813,8824,8830,8832,8841,8844],[19,8601,8602],{},"Tout au long de ma carrière de développeur web, une question s’est toujours posée : comment tester une nouvelle fonctionnalité développée ? Une anomalie corrigée ?",[19,8604,8605,8606,8610,8611,8614,8615,8618],{},"Certes les tests automatisés répondent en partie à la problématique, par exemple grâce au ",[979,8607,5343],{"href":8608,"rel":8609},"https:\u002F\u002Fhoppr-tech.notion.site\u002FFormation-TDD-HoppR-19ff4462cd388034b74eecb49e31630d",[983],", mais ",[1311,8612,8613],{},"quid de la validation humaine",", par un ",[1613,8616,8617],{},"product owner",", un testeur, voire un utilisateur final ?",[19,8620,8621,8622,8625],{},"Beaucoup de projets utilisent des environnements de recette. Mais ceux-ci finissent rapidement avec des données bancales\u002Fobsolètes, des configurations bricolées, et où plusieurs fonctionnalités mergées récemment s’entremêlent. Et qui n’a jamais entendu une phrase du type “",[1613,8623,8624],{},"N’utilisez pas la recette, je fais des tests dessus","”, bloquant ainsi l’ensemble de l’équipe et réduisant la productivité de celle-ci.",[19,8627,8628,8629,8631],{},"De plus, si le développement mergé ne répond pas au besoin, cela entraîne deux choses : soit un commit “parasite” a été introduit sur la branche ",[65,8630,3336],{}," testée, soit l’évitement de ce problème nécessite un travail sur plusieurs branches Git parallèles et autant de complexité ajoutée.",[19,8633,8634,8635,8638,8639,8641],{},"Dès lors, comment tester manuellement une fonctionnalité de manière isolée ? Sur un environnement propre ? Ou des tests end-to-end peuvent être joués sans difficultés pour chaque ",[1613,8636,8637],{},"merge request"," ? Sur une MR qui sera validée avant de partir sur une branche ",[65,8640,3336],{}," ?",[39,8643,8645],{"id":8644},"quest-ce-quun-environnement-éphémère","Qu’est-ce qu’un environnement éphémère ?",[19,8647,8648,8649,8652],{},"Comme son nom l’indique, ",[1311,8650,8651],{},"un environnement éphémère est une instance qui a une durée de vie limitée",", par exemple à la MR à laquelle il est rattaché. Il est construit dans une configuration au plus proche de celle de production, avec des données de test préchargées si nécessaire, pour faciliter les tests automatisés (end-to-end par exemple) et manuels.",[19,8654,8655],{},"Une fois que l’on sait créer un environnement éphémère facilement, plusieurs cas d’usage se présentent :",[23,8657,8658,8668,8671],{},[26,8659,8660,8661,8663,8664,8667],{},"Celui que j’ai le plus utilisé, une ",[1613,8662,8637],{}," = un environnement éphémère. Ainsi, n’importe qui dans l’équipe peut visualiser et valider les modifications avant ",[1613,8665,8666],{},"merge"," de la branche",[26,8669,8670],{},"Création d’un environnement dédié propre pour des démonstrations, qu’elles soient internes ou aux utilisateurs",[26,8672,8673],{},"Un environnement créé pour des tests end-to-end, détruit ensuite",[39,8675,8677],{"id":8676},"retour-dexpérience","Retour d’expérience",[19,8679,8680,8681,393],{},"Je vais vous faire mon retour d’expérience de l’utilisation de ces environnements éphémères, lors de mon projet actuel chez mon client. Ces environnements sont systématiquement utilisés, pour chaque MR ",[1311,8682,8683],{},"sans exception",[582,8685,8687],{"id":8686},"la-mise-en-place","La mise en place",[19,8689,8690,8691,1023,8696,5102,8701,8706,8707,8710],{},"Sur le projet, nous utilisons les outils suivants : ",[979,8692,8695],{"href":8693,"rel":8694},"https:\u002F\u002Faws.amazon.com\u002Ffr\u002F",[983],"AWS",[979,8697,8700],{"href":8698,"rel":8699},"https:\u002F\u002Fdeveloper.hashicorp.com\u002Fterraform",[983],"Terraform",[979,8702,8705],{"href":8703,"rel":8704},"https:\u002F\u002Fabout.gitlab.com\u002F",[983],"Gitlab",". Cela nous permet de facilement automatiser la construction des environnements avec l’",[1311,8708,8709],{},"infrastructure-as-code"," (IaC).",[19,8712,8713,8714,8716,8717,8722,8723,8726],{},"Pour avoir un environnement disponible à la création de la ",[1613,8715,8637],{},", nous utilisons une ",[979,8718,8721],{"href":8719,"rel":8720},"https:\u002F\u002Fdocs.gitlab.com\u002Fci\u002Fvariables\u002Fpredefined_variables\u002F",[983],"variable prédéfinie de GitLab"," nommée ",[65,8724,8725],{},"CI_MERGE_REQUEST_IID"," , un identifiant de la MR spécifique au projet. Cela nous permet d’intégrer la construction de l’environnement à la pipeline de CI\u002FCD.",[19,8728,8729],{},"Bien entendu, hors de question d’installer un environnement inutilement pour un développement qui ne passe pas les tests unitaires. La pipeline ressemble donc  grossièrement à ceci :",[19,8731,8732],{},[1374,8733],{"alt":8734,"src":8735},"Exemple de pipeline : build ⇒ TU ⇒ environnement éphémère","\u002Fcontent-assets\u002F2026-04-14-rex-environnements-phmres\u002Fassets\u002Fimg1.webp",[19,8737,8738,8739,8744],{},"A noter que l’environnement éphémère créé à la volée possède ses propres ressources AWS, (dans notre cas, des buckets S3, des queues SQS, etc.). Un jeu de données spécifique à ces environnements est mis en place via un script ",[979,8740,8743],{"href":8741,"rel":8742},"https:\u002F\u002Fwww.liquibase.com\u002Fhow-liquibase-works",[983],"liquibase"," pour un référentiel de données nécessaire aux tests. Ainsi naît un environnement avec une URL du type",[19,8746,8747],{},[65,8748,8749],{},"https:\u002F\u002Fenv-${CI_MERGE_REQUEST_IID}.client.fr",[19,8751,8752,8753,8758],{},"Bien évidemment, il faut faire attention à ne pas oublier de détruire automatiquement l’environnement à la fermeture de la MR, qu’elle soit mergée ou non (avec l’aide de ",[979,8754,8757],{"href":8755,"rel":8756},"https:\u002F\u002Fdocs.gitlab.com\u002Fuser\u002Fproject\u002Fintegrations\u002Fwebhook_events\u002F#merge-request-events",[983],"webhooks Gitlab","). On pourra également donner une durée de vie maximum à l’environnement. Cela nécessite également une bonne observabilité pour ne pas garder d’environnements “fantômes” coûteux.",[582,8760,8762],{"id":8761},"la-mise-en-situation","La mise en situation",[19,8764,8765,8766,8769],{},"Maintenant que nous avons des environnements éphémères pour chaque ",[1613,8767,8768],{},"merge request,","  comment allons-nous les utiliser ?",[19,8771,8772,8773,8776],{},"Notre principale idée ici est d’intégrer les ",[1613,8774,8775],{},"product owners"," dans la construction de chaque feature. Ainsi, là où la majorité des projets ne demande qu’une validation technique par un pair technique, nous demandons également une validation fonctionnelle par un PO ou un UX, suivant le sujet.",[19,8778,8779],{},[1374,8780],{"alt":8781,"src":8782},"Exemple sur une MR sur GitLab : le PO ET un autre dev ont validé la MR, qui a ensuite pu être mergée","\u002Fcontent-assets\u002F2026-04-14-rex-environnements-phmres\u002Fassets\u002Fimg2.webp",[582,8784,8786],{"id":8785},"les-avantages","Les avantages",[23,8788,8789,8792,8795,8798,8801],{},[26,8790,8791],{},"Chaque MR dispose d’un environnement dédié, évitant les interférences entre les fonctionnalités en développement",[26,8793,8794],{},"Terraform garantit que chaque environnement est identique (mêmes configurations, versions, dépendances), et permet de provisionner\u002Fdétruire l’infrastructure à la demande.",[26,8796,8797],{},"D’ailleurs, cela permet la génération de multiples environnements avec la même config, plus besoin de maintenir chaque environnement de recette\u002Fformation\u002Fdémo séparément",[26,8799,8800],{},"Facilite le développement, l’intégration et le déploiement continu",[26,8802,8803,8804],{},"Permet une amélioration de vos ",[979,8805,8808],{"href":8806,"rel":8807},"https:\u002F\u002Fblog.hoppr.tech\u002Fblogs\u002F2024-10-31-dora-metrics-valuer-la-performance-de-livraison-logicielle#quest-ce-que-les-m%C3%A9triques-dora",[983],"DORA Metrics",[582,8810,8812],{"id":8811},"les-inconvénients","Les inconvénients",[23,8814,8815,8818,8821],{},[26,8816,8817],{},"La mise en place du fonctionnement est complexe, et nécessite un investissement initial important en début de projet. De plus, cela nécessite une bonne maîtrise des outils utilisés",[26,8819,8820],{},"La construction automatique d’un environnement complet peut prendre du temps, et rallonger la pipeline",[26,8822,8823],{},"Avoir un métier (PO\u002FUX) pleinement impliqué et disponible pour faire des revues fonctionnelles.",[19,8825,8826,8827,393],{},"Pour ce dernier point, c’est le cas sur ce produit, donc pas d’inquiétude là-dessus. Il faut de toute façon toujours impliquer le métier dans le développement et inversement, c’est la clé de la réussite de tous vos projets, et c’est cela que l’on pousse systématiquement chez ",[979,8828,1607],{"href":1605,"rel":8829},[983],[39,8831,1161],{"id":1160},[19,8833,8834,8835,8837,8838,8840],{},"Toute l’équipe est ravie de ce système de double validation technique\u002Fmétier sur chaque ",[1613,8836,8637],{}," fonctionnelle. Les ",[1613,8839,8775],{}," et les développeurs travaillent en symbiose sur le produit, ce qui est nécessaire dans une équipe pluri-disciplinaire soudée.",[19,8842,8843],{},"L’investissement initial à la mise en place des environnements éphémères est largement rentabilisé, fluidifiant le processus de développement. Ce système est d’ailleurs petit à petit propagé à d’autres produits chez le même client, suite à nos retours d’expérience positifs.",[19,8845,8846],{},"Alors, qu’attendez-vous pour employer les environnements éphémères, et mieux impliquer le métier dans vos développements ?",{"title":63,"searchDepth":86,"depth":86,"links":8848},[8849,8850,8856],{"id":8644,"depth":86,"text":8645},{"id":8676,"depth":86,"text":8677,"children":8851},[8852,8853,8854,8855],{"id":8686,"depth":99,"text":8687},{"id":8761,"depth":99,"text":8762},{"id":8785,"depth":99,"text":8786},{"id":8811,"depth":99,"text":8812},{"id":1160,"depth":86,"text":1161},"2026-04-14T07:29:50.497Z","Tout au long de ma carrière de développeur web, une question s’est toujours posée : comment tester une nouvelle fonctionnalité développée ? Une anomalie corrigée ?  Certes les tests automatisés répond",{},"\u002Fblogs\u002F2026-04-14-rex-environnements-phmres",[8862,8864,8867],{"id":1184,"name":1185,"image":8863,"linkedin":1187,"x":1188},"https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002Fc88f5dfa-16db-4e6f-acf1-34dd80ee8766\u002Femma_hoppr.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4663EVYACVJ%2F20260414%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260414T072949Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEL7%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLXdlc3QtMiJHMEUCIGZtaRbA4cSdTwdKuo6iJWkH8%2F7Mf4%2F7P2W6PXte3kjGAiEAok6aw4VityW2XGt3RF9AGdcuCRas9ZE5wwRFKdD8whcqiAQIh%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAAGgw2Mzc0MjMxODM4MDUiDJYMPZlPCPZ%2B%2BEq4XyrcA0HVbqp5SrmxfwBpOmpGhgMIg2gTLjV3M6tzdLTdz1DY3m5Zswt94ObBgZT2zhyiePmuVZBd1Yg1lc3W5l4Plvp1OTzIp4BRtM5hZrAXZfoBHQWnigkX1G6zQbPZUIgWCLbbCRfsX0MMT0k49aDA2T2%2B8kuBS8gWNYR1P1hwkZtrA02V4rtsmCfaFrkZZB7EItp9LNTxhmjx9Z7YNBJx9n%2F5ZNY2MIIwLM%2BoEhgaCs4ReNV608nFYY8f0%2F9QzMZye3PM5WlCN9Sgf1O%2FfCO31or9a0z%2FCfxAL37zIa6parPGRxTaGuGT1V2q%2Ftn%2FD%2FVaFbFq0tsFOU5vDyhrQRTjHVOC9i0Bxr%2Bu3Ak9jtAgSZk5gcMpBe1Gwvsu0G8ZWVcPFk0gio%2BfOHd%2BPPJaDoGo5eGVGw5gyBGbovOThBvyvMT6eXcrDWWntNs4IA8iJgFkhRDOI0WPwf3Z%2BASr9UFvNBIGFwOJWG6nRfqJ1c2e7z9yFAhtmFO%2FIAu3p0QMoZ7diWXeFiPcptFAary1LxEwUC9ajIIaF92J%2FIcsZzX%2BUujpZZXDCM7clyCayH%2BgBBdk5ZCXXtuxzLHQG9CWt%2F4HOmafqFTCwhwO3l1BU8fjUNzofspm0KV4sf8cP4yrMKq0984GOqUB4D0QZc81PuEfU5nOGA8R6pZbugM8G6tdhcqL%2BDIjVIXKvGD7pUh8JrnWiiJttpmlBsSRPNT712UTe6nfpUsgIoYecV5eladz3QX4lKzPcT6DFOK1QyILpjC%2BYfO4yoLETTJ0kMmTpOEZMJDhT%2B1qbyLmH3tunIsjs5KTzObbxMOPWCum76IZMm%2Ft7V53%2BjfXdBwkLQRu7hgyITrMqz2603DNgwUd&X-Amz-Signature=52a40968af7f41c867e910c4f5f34edb18ae5a028da26372aaa8b48db773f820&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject",{"id":8865,"name":8866,"image":1188,"linkedin":1188,"x":1188},"320f4462-cd38-8071-8eb7-f90621a068a3","Marjorie Dieusart",{"id":8868,"name":8869,"image":8870,"linkedin":8871,"x":1188},"33bf4462-cd38-80da-845c-c63b2fd024bf","Florian Hirson","https:\u002F\u002Fprod-files-secure.s3.us-west-2.amazonaws.com\u002F5863e833-64f2-4f13-9f7a-2c92c72b5bbf\u002F2b6052d3-047d-4e1d-9256-fea2c2adc9e6\u002FPhoto_Profil_CV_1200px_%289%29.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB4663SBXKDNX%2F20260414%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260414T072949Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEL7%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLXdlc3QtMiJHMEUCIQDoQ2zeta%2FyHTI540wsMH0HxSAY%2BATUU8y1DXJo49%2FR3QIgfgYpE8rnxVjQjdXQSdfSs4FkbukBGNmM4mYPxzHzNsoqiAQIh%2F%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAAGgw2Mzc0MjMxODM4MDUiDH%2FXA22vPQpx%2FkhClCrcA479MmEEV6I9eigvXgytlLjiNUm7v9pTEdM6CFlyZps94bpHWFfG1VfrFw5wOqXSNRFgPrU%2BgGOGKxBi49qeUtd1KdV%2FAPniG8hILB88TLq0EP1yYuBzeSzZmQ%2FqW2T8t92AsCFIedtwHr2DEc%2BKS9q4BREb75QOIKQukTD4iyXiMTaQOHGbu1wYT7I0NUnIHCYv6MyctXcxvPMrjVuxcHmU9EqjvvBHhzpZSzZWHdoR7NhEiNn3ia5TEEurHsuCHtwJP33t86%2FWvgmpv3eqDKI1h8U84OHcINLxPkHCjNytazhXEsDQii%2FC1ejswGUQajdRdLTT6orMRQ1jiUqWYYLtHUUluWzswhoWbBQF2Gd0DaN5AlEh4LjA92uMSg7Ooz6EkIEX2IfuKc6P%2B6v2BVD3wrrJwaCJUJK38ponmF03%2B5WYj0BOvHc6QpkSqO3ptcz2m2NTsHSJJQIdXInRtGGkcfIpLKPeRVr%2F4ueugg%2BTdv05AwuLXcwhOiBDx%2FiSPKxu8DzESmpt1KL7097NPp33PkgeOi%2BoMuGjNn0mJQCL6aTfQHl%2FGwVCu8fC04fqlmgDI4bGR9uT0VWcLEtS93V%2FWCVctavIuyFfTjm3bW3YnQqmDWvoTF5bglFAMMi1984GOqUBpwexbiOU10bG1oXGEC8pAoGR1qleIYaJFatZ3Hn2l02%2ByqIleJqpNHHJunG9BF04s4vtqS4IOIhpUc6y63prUVltgzFyGsM7q46WZwKClOYQ%2BHR9DQTSdAv7ujF3tdq%2BezDPIBWNstYKoWlRBL7cGoqdzL9Rs0SnZtD4ZSEFTkSEXVLeb8klio4AlBqqsVoslyJMFnZurcUaJ0HQyVxlldG%2FFXzY&X-Amz-Signature=760c14d682562a02818ac9dd16ece58d593677ef6cde7d10ff42bcb67502a42e&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fflorian-hirson\u002F",{"title":8594,"description":8858},"blogs\u002F2026-04-14-rex-environnements-phmres\u002Findex",[1208,8875],"rex","l4AE9RbTPfGFFrIE5a0c5Yvcj9eYR9PrSGRNYuMi4qg",1776760465347]