added labs recap, http stuff, oak routing and middleware
3
.gitignore
vendored
@@ -299,3 +299,6 @@ TSWLatexianTemp*
|
|||||||
# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
|
# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
|
||||||
# Uncomment the next line to have this generated file ignored.
|
# Uncomment the next line to have this generated file ignored.
|
||||||
#*Notes.bib
|
#*Notes.bib
|
||||||
|
|
||||||
|
# draw.io
|
||||||
|
*.drawio.bkp
|
||||||
|
|||||||
BIN
build/slides.pdf
BIN
img/architecture.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
img/async.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
img/rest-wikipedia.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
img/roy_fielding_OSCON_2008.jpg
Normal file
|
After Width: | Height: | Size: 582 KiB |
BIN
img/screenshot.png
Normal file
|
After Width: | Height: | Size: 52 KiB |
BIN
img/type-guard-1.png
Normal file
|
After Width: | Height: | Size: 380 KiB |
BIN
img/type-guard-2.png
Normal file
|
After Width: | Height: | Size: 826 KiB |
BIN
img/type-guard-3.png
Normal file
|
After Width: | Height: | Size: 836 KiB |
BIN
img/type-guard-4.png
Normal file
|
After Width: | Height: | Size: 1021 KiB |
BIN
img/type-guard-5.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
@@ -1,45 +0,0 @@
|
|||||||
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/28.2.5 Chrome/138.0.7204.251 Electron/37.6.1 Safari/537.36" version="28.2.5" pages="2">
|
|
||||||
<diagram name="Page-1" id="WE2OBRBfjwrgRAvIH2Xi">
|
|
||||||
<mxGraphModel dx="1106" dy="963" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="0" />
|
|
||||||
<mxCell id="1" parent="0" />
|
|
||||||
<mxCell id="njQ_imjs8zr5ET9f7Rwc-4" value="réponse HTTP" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="njQ_imjs8zr5ET9f7Rwc-1" target="njQ_imjs8zr5ET9f7Rwc-2">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="njQ_imjs8zr5ET9f7Rwc-5" value="WebSocket" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" edge="1" parent="1" source="njQ_imjs8zr5ET9f7Rwc-1" target="njQ_imjs8zr5ET9f7Rwc-2">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="njQ_imjs8zr5ET9f7Rwc-1" value="Serveur web (Deno)" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="130" y="410" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="njQ_imjs8zr5ET9f7Rwc-3" value="requête HTTP" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="njQ_imjs8zr5ET9f7Rwc-2" target="njQ_imjs8zr5ET9f7Rwc-1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="njQ_imjs8zr5ET9f7Rwc-2" value="Client web (navigateur)" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="520" y="410" width="120" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="1ptE3tQJX40rlnvv1j5o-1" value="Application client<div>HTML, CSS, TypeScript</div>" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="630" y="290" width="120" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="1ptE3tQJX40rlnvv1j5o-2" value="Application serveur<div>Deno, Oak</div>" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="70" y="280" width="120" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="1ptE3tQJX40rlnvv1j5o-3" value="Base de données" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="240" y="270" width="60" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="1ptE3tQJX40rlnvv1j5o-4" value="Cache" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="384" y="270" width="60" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
<diagram id="wkPXxGK0bhSZwc4x9mIX" name="Page-2">
|
|
||||||
<mxGraphModel grid="1" page="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="0" />
|
|
||||||
<mxCell id="1" parent="0" />
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
</mxfile>
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/28.2.5 Chrome/138.0.7204.251 Electron/37.6.1 Safari/537.36" version="28.2.5">
|
|
||||||
<diagram name="Page-1" id="o2Z0k-pMrQSQu8LE0CHS">
|
|
||||||
<mxGraphModel dx="2066" dy="1235" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="0" />
|
|
||||||
<mxCell id="1" parent="0" />
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-2" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.uTurnArrow;dy=11;arrowHead=43;dx2=25;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="380" y="360" width="310" height="300" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-10" value="Register callback" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0;exitDx=144.75;exitDy=300;exitPerimeter=0;entryX=0.25;entryY=0;entryDx=0;entryDy=0;fontFamily=Lucida Console;" edge="1" parent="1" source="I0g5zxyyZJP6Qi3c9r4H-3" target="I0g5zxyyZJP6Qi3c9r4H-6">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-3" value="" style="html=1;shadow=0;dashed=0;align=center;verticalAlign=middle;shape=mxgraph.arrows2.uTurnArrow;dy=11;arrowHead=43;dx2=25;flipV=1;flipH=1;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="570" y="370" width="310" height="300" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-4" value="Event Loop" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontFamily=Lucida Console;fontSize=48;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="480" y="450" width="300" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-5" value="<span style=""><font style="font-size: 24px;">(1 thread)</font></span>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="530" y="540" width="200" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;fontFamily=Lucida Console;" edge="1" parent="1" source="I0g5zxyyZJP6Qi3c9r4H-6" target="I0g5zxyyZJP6Qi3c9r4H-12">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-36" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="I0g5zxyyZJP6Qi3c9r4H-6" target="I0g5zxyyZJP6Qi3c9r4H-35">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-6" value="I/O<div>Operations</div>" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;fontFamily=Lucida Console;verticalAlign=top;fontSize=24;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="960" y="382.5" width="210" height="275" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-7" value="FS" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="990" y="470" width="60" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-8" value="DB" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="1080" y="470" width="60" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-9" value="NET" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="1035" y="560" width="60" height="80" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-11" value="Operation complete" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.25;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0;entryDx=227.375;entryDy=300;entryPerimeter=0;fontFamily=Lucida Console;" edge="1" parent="1" source="I0g5zxyyZJP6Qi3c9r4H-6" target="I0g5zxyyZJP6Qi3c9r4H-2">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-12" value="Internal Thread Pool" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;align=center;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="1210" y="410" width="120" height="178.75" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-13" value="Call Stack" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;fontFamily=Lucida Console;verticalAlign=bottom;labelPosition=center;verticalLabelPosition=top;align=center;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="200" y="382.5" width="120" height="277.5" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-19" value="Thread 1" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="1222.5" y="428.75" width="95" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-22" value="Thread n" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="1223.5" y="528.75" width="95" height="40" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-23" value="..." style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="1241" y="490" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-28" value="fn1<span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">(...)</span>" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="212.5" y="401.25" width="95" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-29" value="fn2(...)" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="212.5" y="461.25" width="95" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-30" value="callback(...)" style="rounded=0;whiteSpace=wrap;html=1;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="212.5" y="581.25" width="95" height="60" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-31" value="..." style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="230" y="541.25" width="60" height="30" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-33" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.081;entryY=0.24;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="I0g5zxyyZJP6Qi3c9r4H-28" target="I0g5zxyyZJP6Qi3c9r4H-2">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-34" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.113;entryY=0.84;entryDx=0;entryDy=0;entryPerimeter=0;startArrow=classic;startFill=1;endArrow=none;endFill=0;" edge="1" parent="1" source="I0g5zxyyZJP6Qi3c9r4H-30" target="I0g5zxyyZJP6Qi3c9r4H-2">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="I0g5zxyyZJP6Qi3c9r4H-35" value="OS async syscalls" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;verticalAlign=middle;labelPosition=center;verticalLabelPosition=middle;align=center;fontFamily=Lucida Console;" vertex="1" parent="1">
|
|
||||||
<mxGeometry x="1210" y="610" width="120" height="50" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
</mxfile>
|
|
||||||
@@ -1,35 +1,41 @@
|
|||||||
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/28.2.5 Chrome/138.0.7204.251 Electron/37.6.1 Safari/537.36" version="28.2.5" pages="2">
|
<mxfile host="Electron" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/28.2.5 Chrome/138.0.7204.251 Electron/37.6.1 Safari/537.36" version="28.2.5" pages="2">
|
||||||
<diagram name="Architecture (locale)" id="WE2OBRBfjwrgRAvIH2Xi">
|
<diagram name="Architecture (locale)" id="WE2OBRBfjwrgRAvIH2Xi">
|
||||||
<mxGraphModel dx="1106" dy="963" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
<mxGraphModel dx="826" dy="494" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||||
<root>
|
<root>
|
||||||
<mxCell id="0" />
|
<mxCell id="0" />
|
||||||
<mxCell id="1" parent="0" />
|
<mxCell id="1" parent="0" />
|
||||||
<mxCell id="njQ_imjs8zr5ET9f7Rwc-4" value="réponse HTTP" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="njQ_imjs8zr5ET9f7Rwc-1" target="njQ_imjs8zr5ET9f7Rwc-2">
|
<mxCell id="njQ_imjs8zr5ET9f7Rwc-4" value="réponse HTTP" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;fontFamily=Lucida Console;" edge="1" parent="1" source="njQ_imjs8zr5ET9f7Rwc-1" target="njQ_imjs8zr5ET9f7Rwc-2">
|
||||||
<mxGeometry relative="1" as="geometry" />
|
<mxGeometry relative="1" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="njQ_imjs8zr5ET9f7Rwc-5" value="WebSocket" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;" edge="1" parent="1" source="njQ_imjs8zr5ET9f7Rwc-1" target="njQ_imjs8zr5ET9f7Rwc-2">
|
<mxCell id="njQ_imjs8zr5ET9f7Rwc-5" value="WebSocket" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;fontFamily=Lucida Console;" edge="1" parent="1" source="njQ_imjs8zr5ET9f7Rwc-1" target="njQ_imjs8zr5ET9f7Rwc-2">
|
||||||
<mxGeometry relative="1" as="geometry" />
|
<mxGeometry relative="1" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="njQ_imjs8zr5ET9f7Rwc-1" value="Serveur web (Deno)" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1">
|
<mxCell id="njQ_imjs8zr5ET9f7Rwc-1" value="Serveur web (Deno)" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;fontFamily=Lucida Console;" vertex="1" parent="1">
|
||||||
<mxGeometry x="130" y="410" width="120" height="60" as="geometry" />
|
<mxGeometry x="130" y="410" width="120" height="60" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="njQ_imjs8zr5ET9f7Rwc-3" value="requête HTTP" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="1" source="njQ_imjs8zr5ET9f7Rwc-2" target="njQ_imjs8zr5ET9f7Rwc-1">
|
<mxCell id="njQ_imjs8zr5ET9f7Rwc-3" value="requête HTTP" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;fontFamily=Lucida Console;" edge="1" parent="1" source="njQ_imjs8zr5ET9f7Rwc-2" target="njQ_imjs8zr5ET9f7Rwc-1">
|
||||||
<mxGeometry relative="1" as="geometry" />
|
<mxGeometry relative="1" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="njQ_imjs8zr5ET9f7Rwc-2" value="Client web (navigateur)" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;" vertex="1" parent="1">
|
<mxCell id="-peOsC0kaZLfhGvFLIQP-3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;startArrow=classic;startFill=1;fontFamily=Lucida Console;" edge="1" parent="1" source="njQ_imjs8zr5ET9f7Rwc-2" target="1ptE3tQJX40rlnvv1j5o-1">
|
||||||
<mxGeometry x="520" y="410" width="120" height="60" as="geometry" />
|
<mxGeometry relative="1" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="1ptE3tQJX40rlnvv1j5o-1" value="Application client<div>HTML, CSS, TypeScript</div>" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
<mxCell id="njQ_imjs8zr5ET9f7Rwc-2" value="Client web (navigateur)" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;fontFamily=Lucida Console;" vertex="1" parent="1">
|
||||||
<mxGeometry x="630" y="290" width="120" height="80" as="geometry" />
|
<mxGeometry x="414" y="410" width="120" height="60" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="1ptE3tQJX40rlnvv1j5o-2" value="Application serveur<div>Deno, Oak</div>" style="ellipse;whiteSpace=wrap;html=1;" vertex="1" parent="1">
|
<mxCell id="1ptE3tQJX40rlnvv1j5o-1" value="Application client<div>HTML, CSS, TypeScript</div>" style="ellipse;whiteSpace=wrap;html=1;fontFamily=Lucida Console;" vertex="1" parent="1">
|
||||||
<mxGeometry x="70" y="280" width="120" height="80" as="geometry" />
|
<mxGeometry x="504" y="280" width="120" height="80" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="1ptE3tQJX40rlnvv1j5o-3" value="Base de données" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
|
<mxCell id="-peOsC0kaZLfhGvFLIQP-1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;startArrow=classic;startFill=1;fontFamily=Lucida Console;" edge="1" parent="1" source="1ptE3tQJX40rlnvv1j5o-2" target="njQ_imjs8zr5ET9f7Rwc-1">
|
||||||
<mxGeometry x="240" y="270" width="60" height="80" as="geometry" />
|
<mxGeometry relative="1" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
<mxCell id="1ptE3tQJX40rlnvv1j5o-4" value="Cache" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" vertex="1" parent="1">
|
<mxCell id="1ptE3tQJX40rlnvv1j5o-2" value="Application serveur<div>Deno, Oak</div>" style="ellipse;whiteSpace=wrap;html=1;fontFamily=Lucida Console;" vertex="1" parent="1">
|
||||||
<mxGeometry x="384" y="270" width="60" height="80" as="geometry" />
|
<mxGeometry x="40" y="280" width="120" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="1ptE3tQJX40rlnvv1j5o-3" value="Base de données" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fontFamily=Lucida Console;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="180" y="270" width="60" height="80" as="geometry" />
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="-peOsC0kaZLfhGvFLIQP-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;startArrow=classic;startFill=1;fontFamily=Lucida Console;" edge="1" parent="1" source="1ptE3tQJX40rlnvv1j5o-2" target="1ptE3tQJX40rlnvv1j5o-3">
|
||||||
|
<mxGeometry relative="1" as="geometry" />
|
||||||
</mxCell>
|
</mxCell>
|
||||||
</root>
|
</root>
|
||||||
</mxGraphModel>
|
</mxGraphModel>
|
||||||
|
|||||||
85
res/async.drawio
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<mxfile host="65bd71144e" scale="3" border="0">
|
||||||
|
<diagram id="AZTYWFn2qbMvXExw4gZ1" name="Page-1">
|
||||||
|
<mxGraphModel dx="919" dy="988" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="5" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;fontFamily=Lucida Console;fontSize=14;" edge="1" parent="1" source="2">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="180" y="640" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="2" value="main()" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;fontFamily=Lucida Console;fontSize=14;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="120" y="360" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="6" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;fontFamily=Lucida Console;fontSize=14;" edge="1" parent="1" source="3">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="350" y="630" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="3" value="taskA()" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;fontFamily=Lucida Console;fontSize=14;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="290" y="360" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="14" value="attente" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontFamily=Lucida Console;fontSize=14;fillColor=#ffe6cc;strokeColor=#d79b00;" edge="1" parent="1" source="7" target="10">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="7" value="" style="rounded=0;whiteSpace=wrap;html=1;rotation=90;fillColor=#ffe6cc;strokeColor=#d79b00;fontFamily=Lucida Console;fontSize=14;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="167.5" y="440" width="50" height="10" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="15" value="retour" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fontFamily=Lucida Console;fontSize=14;fillColor=#dae8fc;strokeColor=#6c8ebf;" edge="1" parent="1" source="10" target="12">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="10" value="" style="rounded=0;whiteSpace=wrap;html=1;rotation=90;fillColor=#dae8fc;strokeColor=#6c8ebf;fontFamily=Lucida Console;fontSize=14;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="310" y="520" width="50" height="10" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="12" value="" style="rounded=0;whiteSpace=wrap;html=1;rotation=90;fillColor=#ffe6cc;strokeColor=#d79b00;fontFamily=Lucida Console;fontSize=14;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="167.5" y="600" width="50" height="10" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="16" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;fontFamily=Lucida Console;fontSize=14;" edge="1" parent="1" source="17">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="540" y="640" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="17" value="main()" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;fontFamily=Lucida Console;fontSize=14;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="480" y="360" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="18" style="edgeStyle=none;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;fontFamily=Lucida Console;fontSize=14;" edge="1" parent="1" source="19">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<mxPoint x="710" y="630" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="19" value="taskA()" style="shape=process;whiteSpace=wrap;html=1;backgroundOutline=1;fontFamily=Lucida Console;fontSize=14;fontStyle=1" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="650" y="360" width="120" height="60" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="25" value="callback" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=0.25;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;fillColor=#ffe6cc;strokeColor=#d79b00;fontFamily=Lucida Console;fontSize=14;" edge="1" parent="1" source="21" target="23">
|
||||||
|
<mxGeometry relative="1" as="geometry">
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="695" y="473"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="21" value="" style="rounded=0;whiteSpace=wrap;html=1;rotation=90;fillColor=#ffe6cc;strokeColor=#d79b00;fontFamily=Lucida Console;fontSize=14;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="446.25" y="521.25" width="212.5" height="10" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="26" value="événement" style="edgeStyle=orthogonalEdgeStyle;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0.75;entryY=0;entryDx=0;entryDy=0;fillColor=#dae8fc;strokeColor=#6c8ebf;fontFamily=Lucida Console;fontSize=14;" edge="1" parent="1" source="23" target="21">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="23" value="" style="rounded=0;whiteSpace=wrap;html=1;rotation=90;fillColor=#dae8fc;strokeColor=#6c8ebf;fontFamily=Lucida Console;fontSize=14;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="670" y="520" width="50" height="10" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="27" value="" style="endArrow=none;dashed=1;html=1;dashPattern=1 3;strokeWidth=2;" edge="1" parent="1">
|
||||||
|
<mxGeometry width="50" height="50" relative="1" as="geometry">
|
||||||
|
<mxPoint x="450" y="640" as="sourcePoint"/>
|
||||||
|
<mxPoint x="450" y="280" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="28" value="Synchrone" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontFamily=Lucida Console;fontStyle=0;fontSize=16;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="217.5" y="290" width="110" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="29" value="Asynchrone" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontFamily=Lucida Console;fontStyle=0;fontSize=16;fillColor=#e1d5e7;strokeColor=#9673a6;" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="570" y="290" width="110" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
262
res/type-guard.drawio
Normal file
53
slides.bib
@@ -2,14 +2,16 @@
|
|||||||
title = {The {{Concise TypeScript Book}}},
|
title = {The {{Concise TypeScript Book}}},
|
||||||
author = {Poggiali, Simone},
|
author = {Poggiali, Simone},
|
||||||
year = 2026,
|
year = 2026,
|
||||||
howpublished = {https://gibbok.github.io/typescript-book/book/the-concise-typescript-book/},
|
url = {https://gibbok.github.io/typescript-book/book/the-concise-typescript-book/},
|
||||||
|
urldate = {2026-01-19}
|
||||||
}
|
}
|
||||||
|
|
||||||
@book{HandbookTypeScriptHandbook,
|
@book{HandbookTypeScriptHandbook,
|
||||||
title = {The {{TypeScript Handbook}}},
|
title = {The {{TypeScript Handbook}}},
|
||||||
author = {Microsoft},
|
author = {Microsoft},
|
||||||
year = 2026,
|
year = 2026,
|
||||||
howpublished = {https://www.typescriptlang.org/docs/handbook/intro.html},
|
url = {https://www.typescriptlang.org/docs/handbook/intro.html},
|
||||||
|
urldate = {2026-01-19}
|
||||||
}
|
}
|
||||||
|
|
||||||
@article{greggFlameGraph2016,
|
@article{greggFlameGraph2016,
|
||||||
@@ -23,7 +25,52 @@
|
|||||||
pages = {48--57},
|
pages = {48--57},
|
||||||
issn = {0001-0782, 1557-7317},
|
issn = {0001-0782, 1557-7317},
|
||||||
doi = {10.1145/2909476},
|
doi = {10.1145/2909476},
|
||||||
urldate = {2026-01-16},
|
|
||||||
abstract = {This visualization of software execution is a new necessity for performance profiling and debugging.},
|
abstract = {This visualization of software execution is a new necessity for performance profiling and debugging.},
|
||||||
langid = {english},
|
langid = {english},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@misc{fowlerMicroservices,
|
||||||
|
title = {Microservices},
|
||||||
|
author = {Lewis, James and Fowler, Martin},
|
||||||
|
journal = {martinfowler.com},
|
||||||
|
abstract = {Defining the microservices architectural style by describing their nine common characteristics},
|
||||||
|
url = {https://martinfowler.com/articles/microservices.html},
|
||||||
|
urldate = {2026-01-19}
|
||||||
|
}
|
||||||
|
|
||||||
|
@misc{ParseDontValidate2019,
|
||||||
|
author = {King, Alexis},
|
||||||
|
title = {Parse, Don’t Validate},
|
||||||
|
year = {2019},
|
||||||
|
month = nov,
|
||||||
|
url = {https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/},
|
||||||
|
urldate = {2026-01-27}
|
||||||
|
}
|
||||||
|
|
||||||
|
@book{kleppmannDesigningDataIntensiveApplications2017,
|
||||||
|
author = {Kleppmann, Martin},
|
||||||
|
title = {Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems},
|
||||||
|
publisher = {O'Reilly Media},
|
||||||
|
address = {Sebastopol, CA},
|
||||||
|
year = {2017},
|
||||||
|
edition = {1st},
|
||||||
|
isbn = {978-1449373320},
|
||||||
|
pages = {xix + 590}
|
||||||
|
}
|
||||||
|
|
||||||
|
@book{sharvitDataOrientedProgramming2022,
|
||||||
|
author = {Sharvit, Yehonathan},
|
||||||
|
title = {Data-Oriented Programming: Reduce Software Complexity},
|
||||||
|
publisher = {Manning Publications},
|
||||||
|
address = {Shelter Island, NY},
|
||||||
|
year = {2022},
|
||||||
|
isbn = {978-1617298578},
|
||||||
|
pages = {325}
|
||||||
|
}
|
||||||
|
|
||||||
|
@book{fielding2000architectural,
|
||||||
|
title={Architectural styles and the design of network-based software architectures},
|
||||||
|
author={Fielding, Roy Thomas},
|
||||||
|
year={2000},
|
||||||
|
publisher={University of California, Irvine}
|
||||||
|
}
|
||||||
|
|||||||
BIN
slides.pdf
746
slides.tex
@@ -44,19 +44,19 @@
|
|||||||
|
|
||||||
\begin{frame}{Introduction}
|
\begin{frame}{Introduction}
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\url{https://info-demo-sor.univ-brest.fr}
|
\url{http://info-demo-sor.univ-brest.fr:3000}
|
||||||
\end{center}
|
\end{center}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{framefont}{\small}
|
\begin{framefont}{\footnotesize}
|
||||||
\begin{frame}[t]{Plan du cours}
|
\begin{frame}[t]{Plan du cours}
|
||||||
\begin{columns}
|
\begin{columns}
|
||||||
\column{0.5\textwidth}
|
\column{0.6\textwidth}
|
||||||
\tableofcontents%[hideallsubsections]
|
\tableofcontents%[hideallsubsections]
|
||||||
|
|
||||||
\column{0.5\textwidth}
|
\column{0.4\textwidth}
|
||||||
\begin{center}
|
\begin{center}
|
||||||
\includegraphics[width=0.9\columnwidth]{img/logos.png}
|
\includegraphics[width=\columnwidth]{img/screenshot.png}
|
||||||
\end{center}
|
\end{center}
|
||||||
\end{columns}
|
\end{columns}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
\item Objectifs :
|
\item Objectifs :
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item Développer une application suivant l'\textbf{architecture trois tiers}, s'appuyant sur des communications via \textbf{HTTP} et \textbf{WebSockets} ;
|
\item Développer une application suivant l'\textbf{architecture trois tiers}, s'appuyant sur des communications via \textbf{HTTP} et \textbf{WebSockets} ;
|
||||||
\item Comprendre les mécanismes pour l'\textbf{authentification} (avec ou sans état) d'un client auprès d'un serveur ;
|
\item Comprendre les mécanismes de l'\textbf{authentification} (avec ou sans état) d'un client auprès d'un serveur ;
|
||||||
\item S'initier au \textbf{déploiement} d'une application répartie à l'aide d'un \textit{reverse proxy}.
|
\item S'initier au \textbf{déploiement} d'une application répartie à l'aide d'un \textit{reverse proxy}.
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
\item Prérequis :
|
\item Prérequis :
|
||||||
@@ -167,7 +167,7 @@
|
|||||||
\end{itemize}
|
\end{itemize}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\section{Cas d'usage}
|
\section{Cas d'étude applicatif}
|
||||||
|
|
||||||
\begin{frame}{Présentation de l'application}
|
\begin{frame}{Présentation de l'application}
|
||||||
\begin{block}{Description}
|
\begin{block}{Description}
|
||||||
@@ -210,20 +210,26 @@
|
|||||||
\section{Architecture : modèle client/serveur}
|
\section{Architecture : modèle client/serveur}
|
||||||
|
|
||||||
\begin{frame}{Vue d'ensemble}
|
\begin{frame}{Vue d'ensemble}
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=.9\textwidth]{img/architecture.png}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}{Monolithes ou microservices}
|
\begin{frame}{Monolithes ou microservices}
|
||||||
\centering
|
\centering
|
||||||
\includegraphics[width=.8\textwidth]{img/scaling-monolith.png}
|
\includegraphics[width=.8\textwidth]{img/scaling-monolith.png}
|
||||||
|
|
||||||
|
\addtocounter{footnote}{1}
|
||||||
|
\footnotetext{\fullcite{fowlerMicroservices}}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}{Monolithes ou microservices}
|
\begin{frame}{Monolithes ou microservices}
|
||||||
\centering
|
\centering
|
||||||
\includegraphics[width=.8\textwidth]{img/scaling-uservices.png}
|
\includegraphics[width=.8\textwidth]{img/scaling-uservices.png}
|
||||||
|
|
||||||
|
\footnotetext{\fullcite{fowlerMicroservices}}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\section{JavaScript, TypeScript et runtime}
|
\section{JavaScript, TypeScript et \textit{runtime}}
|
||||||
|
|
||||||
\begin{frame}{JavaScript}
|
\begin{frame}{JavaScript}
|
||||||
\centering
|
\centering
|
||||||
@@ -267,7 +273,8 @@
|
|||||||
\centering
|
\centering
|
||||||
\includegraphics[width=.7\columnwidth]{img/jetbrains-ecosystem-2025-platforms.png}
|
\includegraphics[width=.7\columnwidth]{img/jetbrains-ecosystem-2025-platforms.png}
|
||||||
|
|
||||||
\footnote[]{\url{https://devecosystem-2025.jetbrains.com/tools-and-trends}}
|
\addtocounter{footnote}{1}
|
||||||
|
\footnotetext{\url{https://devecosystem-2025.jetbrains.com/tools-and-trends}}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}{TypeScript}
|
\begin{frame}{TypeScript}
|
||||||
@@ -313,6 +320,8 @@
|
|||||||
\end{itemize}
|
\end{itemize}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Typage statique et dynamique}
|
||||||
|
|
||||||
\begin{frame}{TypeScript : typage du code JavaScript}
|
\begin{frame}{TypeScript : typage du code JavaScript}
|
||||||
Types de base disponibles dans TypeScript~\footnote{\url{https://www.typescriptlang.org/docs/handbook/2/everyday-types.html}} :
|
Types de base disponibles dans TypeScript~\footnote{\url{https://www.typescriptlang.org/docs/handbook/2/everyday-types.html}} :
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
@@ -382,6 +391,26 @@ console.log(o.other);
|
|||||||
\end{minted}
|
\end{minted}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{POJO et listes : \textit{Spread operator}}
|
||||||
|
\begin{minted}{ts}
|
||||||
|
const o1 = { "abc": 123 };
|
||||||
|
// Ajout d'une nouvelle valeur dans `o1`
|
||||||
|
o1["def"] = 456;
|
||||||
|
// Nouvel objet avec copie des valeurs de `o1`
|
||||||
|
const o2 = { ...o1, "ghi": 789 };
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
\vspace{.5cm}
|
||||||
|
|
||||||
|
\begin{minted}{ts}
|
||||||
|
const l1 = [123];
|
||||||
|
// Ajout d'une nouvelle valeur à la fin de `l1`
|
||||||
|
l1.append(456);
|
||||||
|
// Nouvelle liste avec copies des valeurs de `l1`
|
||||||
|
const l2 = [...l1, 789];
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}[fragile]{TypeScript : typer les objets avec \texttt{Record}}
|
\begin{frame}[fragile]{TypeScript : typer les objets avec \texttt{Record}}
|
||||||
\begin{minted}{js}
|
\begin{minted}{js}
|
||||||
const o: Record<string, string | number | boolean> = {
|
const o: Record<string, string | number | boolean> = {
|
||||||
@@ -583,12 +612,6 @@ const makeNoise = (animal: Animal) => {
|
|||||||
\end{minted}
|
\end{minted}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}[fragile]{Généricité}
|
|
||||||
\begin{itemize}
|
|
||||||
\item \texttt{SomeContainer<T extends SomeData>}
|
|
||||||
\end{itemize}
|
|
||||||
\end{frame}
|
|
||||||
|
|
||||||
\begin{frame}[fragile]{Exercice 3 : types au \textit{runtime}}
|
\begin{frame}[fragile]{Exercice 3 : types au \textit{runtime}}
|
||||||
\begin{minted}[fontsize=\footnotesize]{ts}
|
\begin{minted}[fontsize=\footnotesize]{ts}
|
||||||
interface Dog {
|
interface Dog {
|
||||||
@@ -612,22 +635,157 @@ makeNoise(dog);
|
|||||||
\end{minted}
|
\end{minted}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}{Gestion des erreurs : exceptions}
|
\begin{frame}[fragile]{Type Guards : \textit{compile time} et \textit{run time}}
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item Exceptions
|
\item \textit{Type narrowing} : \textit{compile time}
|
||||||
\item \texttt{try}/\texttt{catch}
|
\item \textit{Type assertion} : \textit{run time}
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
|
\vspace{.5cm}
|
||||||
|
|
||||||
|
\begin{minted}{ts}
|
||||||
|
interface Something { id: string };
|
||||||
|
|
||||||
|
export function isSomething(obj: unknown): obj is Something {
|
||||||
|
return !!obj
|
||||||
|
&& typeof obj === "object"
|
||||||
|
&& "id" in obj && typeof obj.id === "string";
|
||||||
|
}
|
||||||
|
|
||||||
|
const bla = { id: "blabla" };
|
||||||
|
|
||||||
|
\end{minted}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}{Programmation asynchrone}
|
\subsection{Généricité}
|
||||||
TODO: Exemple (requête API) avec figure
|
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Généricité}
|
||||||
|
\begin{itemize}
|
||||||
|
\item \texttt{SomeContainer<T extends SomeData>}
|
||||||
|
\begin{itemize}
|
||||||
|
\item \texttt{T} est un type paramètre contraint à hériter ou implémenter \texttt{SomeData}
|
||||||
|
\item Permet de réutiliser le même conteneur pour différents types de données
|
||||||
|
\end{itemize}
|
||||||
|
\item Objectifs :
|
||||||
|
\begin{itemize}
|
||||||
|
\item Définir des structures et fonctions abstraites sur un type variable
|
||||||
|
\item Maintenir les contraintes de type
|
||||||
|
\end{itemize}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\begin{columns}
|
||||||
|
\column{.6\textwidth}
|
||||||
|
\begin{minted}[fontsize=\footnotesize]{ts}
|
||||||
|
interface SomeData {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpecializedData extends SomeData {
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SomeContainer<T extends SomeData> {
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
\column{.4\textwidth}
|
||||||
|
\begin{minted}[fontsize=\footnotesize]{ts}
|
||||||
|
const c: SomeContainer<
|
||||||
|
SpecializedData
|
||||||
|
> = {
|
||||||
|
data: {
|
||||||
|
id: "foo",
|
||||||
|
name: "Foo Bar",
|
||||||
|
value: 42,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
\end{minted}
|
||||||
|
\end{columns}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Gestion des erreurs}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Gestion des erreurs : exceptions}
|
||||||
|
\begin{minted}{ts}
|
||||||
|
const randInt = Math.floor(Math.random() * 3) + 1; // [1, 3]
|
||||||
|
const error =
|
||||||
|
randInt === 1 ? new TypeError() :
|
||||||
|
randInt === 2 ? new RangeError() :
|
||||||
|
new EvalError();
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw error;
|
||||||
|
} catch {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Gestion des erreurs : exceptions}
|
||||||
|
\begin{minted}{ts}
|
||||||
|
const randInt = Math.floor(Math.random() * 3) + 1; // [1, 3]
|
||||||
|
const error =
|
||||||
|
randInt === 1 ? new TypeError() :
|
||||||
|
randInt === 2 ? new RangeError() :
|
||||||
|
new EvalError();
|
||||||
|
|
||||||
|
try {
|
||||||
|
throw error;
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TypeError) {
|
||||||
|
// handle TypeError
|
||||||
|
} else if (e instanceof RangeError) {
|
||||||
|
// handle RangeError
|
||||||
|
} else if (e instanceof EvalError) {
|
||||||
|
// handle EvalError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Gestion des erreurs : exceptions}
|
||||||
|
\begin{minted}{ts}
|
||||||
|
try {
|
||||||
|
const file = await Deno.open("example.txt", { read: true });
|
||||||
|
|
||||||
|
for await (const chunk of Deno.iter(file)) {
|
||||||
|
console.log(new TextDecoder().decode(chunk));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error reading file:", e);
|
||||||
|
} finally {
|
||||||
|
// always close the file handle
|
||||||
|
if (file) {
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Asynchronisme et parallélisme}
|
||||||
|
|
||||||
|
\begin{frame}{Programmation asynchrone}
|
||||||
Comment manipuler des données...
|
Comment manipuler des données...
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item que l'on n'a pas encore ?
|
\item que l'on n'a pas encore ?
|
||||||
\item que l'on n'aura peut-être jamais ?
|
\item que l'on n'aura peut-être jamais ?
|
||||||
\item qui l'on obtient dans un temps variable ?
|
\item qui l'on obtient dans un temps variable ?
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
|
Par exemple...
|
||||||
|
\begin{itemize}
|
||||||
|
\item le résultat d'une requête à une API ?
|
||||||
|
\item le contenu d'un fichier volumineux sur le disque ?
|
||||||
|
\item le résultat d'une requête à une base de données ?
|
||||||
|
\end{itemize}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}{Programmation asynchrone}
|
||||||
|
\centering
|
||||||
|
|
||||||
|
\includegraphics[width=.9\textwidth]{img/async.png}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}{Programmation asynchrone}
|
\begin{frame}{Programmation asynchrone}
|
||||||
@@ -644,7 +802,8 @@ makeNoise(dog);
|
|||||||
\centering
|
\centering
|
||||||
\includegraphics[width=.8\textwidth]{img/promises.png}
|
\includegraphics[width=.8\textwidth]{img/promises.png}
|
||||||
|
|
||||||
\footnote[]{\url{https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise}}
|
\addtocounter{footnote}{1}
|
||||||
|
\footnotetext{\url{https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise}}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}[fragile]{Programmation asynchrone}
|
\begin{frame}[fragile]{Programmation asynchrone}
|
||||||
@@ -742,7 +901,23 @@ try {
|
|||||||
\begin{frame}{Programmation parallèle}
|
\begin{frame}{Programmation parallèle}
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item \texttt{Promise.all}
|
\item \texttt{Promise.all}
|
||||||
|
\begin{itemize}
|
||||||
|
\item exécute toutes les Promises en \textbf{parallèle}
|
||||||
|
\item résout quand toutes les Promises ont \textbf{réussi}
|
||||||
|
\item rejette immédiatement si \textbf{une seule échoue}
|
||||||
|
\item retourne un tableau des résultats dans l'ordre initial des Promises
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
\item \texttt{Promise.allSettled}
|
\item \texttt{Promise.allSettled}
|
||||||
|
\item \begin{itemize}
|
||||||
|
\item exécute toutes les Promises en \textbf{parallèle}
|
||||||
|
\item résout quand toutes les Promises sont terminées, \textbf{succès ou échec}
|
||||||
|
\item ne rejette \textbf{jamais}
|
||||||
|
\item retourne un tableau d'objets de type :
|
||||||
|
\begin{itemize}
|
||||||
|
\item \texttt{{status: 'fulfilled' | 'rejected', value | reason}}
|
||||||
|
\end{itemize}
|
||||||
|
\end{itemize}
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
@@ -871,6 +1046,8 @@ contents.forEach((result, i) => {
|
|||||||
\end{block}
|
\end{block}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Environnement d'exécution}
|
||||||
|
|
||||||
\begin{frame}{Exécution : machine virtuelle}
|
\begin{frame}{Exécution : machine virtuelle}
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item JavaScript : langage interprété dans une VM
|
\item JavaScript : langage interprété dans une VM
|
||||||
@@ -919,9 +1096,35 @@ contents.forEach((result, i) => {
|
|||||||
\begin{frame}{Gestion des dépendances}
|
\begin{frame}{Gestion des dépendances}
|
||||||
\begin{columns}
|
\begin{columns}
|
||||||
\column{.8\textwidth}
|
\column{.8\textwidth}
|
||||||
Outils (npm, etc.)
|
Outils et registres :
|
||||||
|
\begin{itemize}
|
||||||
|
\item \texttt{npmjs.org} : registre historique des paquets JavaScript
|
||||||
|
\begin{itemize}
|
||||||
|
\item Gestionnaires de paquets : \textbf{npm / yarn}
|
||||||
|
\end{itemize}
|
||||||
|
\item \texttt{jsr.org} : "nouveau" registre, orienté TypeScript
|
||||||
|
\begin{itemize}
|
||||||
|
\item Gestionnaire de paquets : \textbf{jsr}, intégré dans Deno
|
||||||
|
\end{itemize}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
Modules (ESM vs CommonJS) et imports
|
\vspace{.5cm}
|
||||||
|
|
||||||
|
Modules et imports :
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textbf{CommonJS (CJS)} : compatibilité historique
|
||||||
|
\begin{itemize}
|
||||||
|
\item Import : \texttt{const fs = require('fs');}
|
||||||
|
\item Export : \texttt{module.exports = \{ readFile \};}
|
||||||
|
\item Chargement synchrone
|
||||||
|
\end{itemize}
|
||||||
|
\item \textbf{ECMAScript Modules (ESM)} : standard moderne
|
||||||
|
\begin{itemize}
|
||||||
|
\item Import : \texttt{import \{ readFile \} from 'fs/promises';}
|
||||||
|
\item Export : \texttt{export const myFunc = () => \{...\};}
|
||||||
|
\item Chargement statique ou dynamique (asynchrone)
|
||||||
|
\end{itemize}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
\column{.2\textwidth}
|
\column{.2\textwidth}
|
||||||
\includegraphics[width=\columnwidth]{img/npm.png}
|
\includegraphics[width=\columnwidth]{img/npm.png}
|
||||||
@@ -931,11 +1134,156 @@ contents.forEach((result, i) => {
|
|||||||
|
|
||||||
\section{Serveur web}
|
\section{Serveur web}
|
||||||
|
|
||||||
\begin{frame}{HTTP}
|
\begin{frame}{\textit{It's a series of tubes...}}
|
||||||
\centering
|
\centering
|
||||||
\includegraphics[width=.6\textwidth]{img/series-of-tubes.png}
|
\includegraphics[width=.6\textwidth]{img/series-of-tubes.png}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}{Résumé de la méthode en TP}
|
||||||
|
\begin{enumerate}
|
||||||
|
\item La base de données retourne des \textbf{enregistrements} : ce sont des données arbitraires
|
||||||
|
\begin{itemize}
|
||||||
|
\item Élément unique : \texttt{Record<string, SQLOutputValue> | undefined}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Peut être \texttt{undefined} si introuvable !
|
||||||
|
\end{itemize}
|
||||||
|
\item Liste : \texttt{Record<string, SQLOutputValue>[]}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Peut être vide !
|
||||||
|
\end{itemize}
|
||||||
|
\end{itemize}
|
||||||
|
\item On doit \textit{vérifier le typage des données} pour que l'objet se conforme à son \textit{interface base de données} (\texttt{PollRow}, ...)
|
||||||
|
\item On peut alors \textit{convertir l'enregistrement} en objet de notre \textit{API} (\texttt{Poll}, ...)
|
||||||
|
\item L'objet peut être retourné au client de manière \textit{sûre}
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textit{Compile time} : typage statique des données dans l'application client
|
||||||
|
\item \textit{Run time} : pas d'erreur à l'exécution pour l'utilisateur
|
||||||
|
\end{itemize}
|
||||||
|
\end{enumerate}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=\textwidth]{img/type-guard-1.png}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[noframenumbering]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=\textwidth]{img/type-guard-2.png}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[noframenumbering]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=\textwidth]{img/type-guard-3.png}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[noframenumbering]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=\textwidth]{img/type-guard-4.png}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[noframenumbering]
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=\textwidth]{img/type-guard-5.png}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Résumé de la méthode en TP}
|
||||||
|
\begin{columns}
|
||||||
|
\column{.5\textwidth}
|
||||||
|
\begin{minted}{ts}
|
||||||
|
export interface PollRow {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description: string | null;
|
||||||
|
user_id: string | null;
|
||||||
|
created_at: string;
|
||||||
|
expires_at: string | null;
|
||||||
|
is_active: number;
|
||||||
|
[key: string]: SQLOutputValue;
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
\column{.5\textwidth}
|
||||||
|
\begin{minted}{ts}
|
||||||
|
export interface Poll {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
options: PollOption[];
|
||||||
|
userId?: string;
|
||||||
|
createdAt: string;
|
||||||
|
expiresAt?: string;
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\end{columns}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Résumé de la méthode en TP}
|
||||||
|
\begin{minted}[fontsize=\small]{ts}
|
||||||
|
export function isPollRow(
|
||||||
|
obj: Record<string, SQLOutputValue>
|
||||||
|
): obj is PollRow {
|
||||||
|
return !!obj &&
|
||||||
|
typeof obj === "object" &&
|
||||||
|
"id" in obj && typeof obj.id === "string" &&
|
||||||
|
"title" in obj && typeof obj.title === "string" &&
|
||||||
|
"description" in obj &&
|
||||||
|
(typeof obj.description === "string" || obj.description === null) &&
|
||||||
|
"user_id" in obj &&
|
||||||
|
(typeof obj.user_id === "string" || obj.user_id === null) &&
|
||||||
|
"created_at" in obj && typeof obj.created_at === "string" &&
|
||||||
|
"expires_at" in obj &&
|
||||||
|
(typeof obj.expires_at === "string" || obj.expires_at === null) &&
|
||||||
|
"is_active" in obj && typeof obj.is_active === "number";
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Résumé de la méthode en TP}
|
||||||
|
\begin{minted}{ts}
|
||||||
|
export function pollRowToApi(
|
||||||
|
row: PollRow,
|
||||||
|
optionRows: PollOptionRow[],
|
||||||
|
): Poll {
|
||||||
|
return {
|
||||||
|
id: row.id,
|
||||||
|
title: row.title,
|
||||||
|
description: row.description ?? undefined,
|
||||||
|
createdAt: row.created_at,
|
||||||
|
expiresAt: row.expires_at ?? undefined,
|
||||||
|
isActive: row.is_active === 1,
|
||||||
|
options: optionRows.map(pollOptionRowToApi),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Résumé de la méthode en TP}
|
||||||
|
\begin{minted}{ts}
|
||||||
|
export function pollApiToRow(
|
||||||
|
poll: Poll
|
||||||
|
): [PollRow, PollOptionRow[]] {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: poll.id,
|
||||||
|
title: poll.title,
|
||||||
|
description: poll.description ?? null,
|
||||||
|
user_id: poll.userId ?? null,
|
||||||
|
created_at: poll.createdAt,
|
||||||
|
expires_at: poll.expiresAt ?? null,
|
||||||
|
is_active: Number(poll.isActive),
|
||||||
|
},
|
||||||
|
poll.options.map((pollOption) => pollOptionApiToRow(
|
||||||
|
poll.id, pollOption
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Protocole HTTP}
|
||||||
|
|
||||||
\begin{frame}{HTTP}
|
\begin{frame}{HTTP}
|
||||||
\begin{columns}
|
\begin{columns}
|
||||||
\column{.6\textwidth}
|
\column{.6\textwidth}
|
||||||
@@ -998,6 +1346,27 @@ contents.forEach((result, i) => {
|
|||||||
\end{itemize}
|
\end{itemize}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}{HTTP : architecture REST}
|
||||||
|
\begin{columns}
|
||||||
|
\column{.7\textwidth}
|
||||||
|
\begin{itemize}
|
||||||
|
\item \textit{REpresentational State Transfer}~\cite{fielding2000architectural} : défini par Roy Fielding en 2000, conçu pendant son doctorat (1996 - 1999)
|
||||||
|
\item Modèle d'architecture logicielle pour applications réseau
|
||||||
|
\item Manipuler des ressources distantes via des représentations textuelles
|
||||||
|
\item Opérations uniformes, prédéfinies, et \textbf{sans état}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\column{.3\textwidth}
|
||||||
|
\includegraphics[width=\columnwidth]{img/roy_fielding_OSCON_2008.jpg}
|
||||||
|
\end{columns}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=.8\textwidth]{img/rest-wikipedia.png}
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}[fragile]{Exercice 5 : un serveur HTTP minimal avec TypeScript}
|
\begin{frame}[fragile]{Exercice 5 : un serveur HTTP minimal avec TypeScript}
|
||||||
\begin{block}{Lire et écrire sur un socket TCP}
|
\begin{block}{Lire et écrire sur un socket TCP}
|
||||||
Ci-dessous, la boucle principale pour ouvrir et écouter sur un socket TCP avec Deno. Dès la connexion d'un \textbf{client}, on passe le socket à la fonction \texttt{handleConn}.
|
Ci-dessous, la boucle principale pour ouvrir et écouter sur un socket TCP avec Deno. Dès la connexion d'un \textbf{client}, on passe le socket à la fonction \texttt{handleConn}.
|
||||||
@@ -1038,14 +1407,14 @@ async function handleConn(conn: Deno.Conn) {
|
|||||||
\begin{frame}[fragile]{Exercice 5 : un serveur HTTP minimal avec TypeScript}
|
\begin{frame}[fragile]{Exercice 5 : un serveur HTTP minimal avec TypeScript}
|
||||||
\begin{minted}{sh}
|
\begin{minted}{sh}
|
||||||
curl -X POST http://localhost:8080 \
|
curl -X POST http://localhost:8080 \
|
||||||
-H "Content-Type: text/plain" \
|
-H "Content-Type: text/plain" \
|
||||||
--data "hello world"
|
-d "hello world"
|
||||||
\end{minted}
|
\end{minted}
|
||||||
|
|
||||||
\vspace{1cm}
|
\vspace{1cm}
|
||||||
|
|
||||||
\begin{columns}
|
\begin{columns}
|
||||||
\column{.5\textwidth}
|
\column{.4\textwidth}
|
||||||
\begin{minted}{text}
|
\begin{minted}{text}
|
||||||
POST / HTTP/1.1
|
POST / HTTP/1.1
|
||||||
Host: localhost:8080
|
Host: localhost:8080
|
||||||
@@ -1057,17 +1426,19 @@ Content-Length: 11
|
|||||||
hello world
|
hello world
|
||||||
\end{minted}
|
\end{minted}
|
||||||
|
|
||||||
\column{.5\textwidth}
|
\column{.6\textwidth}
|
||||||
\begin{alertblock}{Questions}
|
\begin{alertblock}{Questions}
|
||||||
\begin{enumerate}
|
\begin{enumerate}
|
||||||
\item Quels sont les champs à inclure dans la réponse que l'on va retourner au client ?
|
\item Quels sont les champs à inclure dans la réponse que l'on va retourner au client ?
|
||||||
\item Compléter la fonction \texttt{handleConn} pour signifier une réponse OK comportant du texte au format JSON.
|
\item Compléter la fonction \texttt{handleConn} pour signifier une réponse OK comportant du texte au format JSON.
|
||||||
\item TODO: new Date().toUTCString();
|
\item Comment va-t-on gérer l'horodatage ?
|
||||||
\end{enumerate}
|
\end{enumerate}
|
||||||
\end{alertblock}
|
\end{alertblock}
|
||||||
\end{columns}
|
\end{columns}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Mise en œuvre}
|
||||||
|
|
||||||
\begin{frame}{Framework pour serveur HTTP : Oak}
|
\begin{frame}{Framework pour serveur HTTP : Oak}
|
||||||
\begin{columns}
|
\begin{columns}
|
||||||
\column{.8\textwidth}
|
\column{.8\textwidth}
|
||||||
@@ -1079,6 +1450,7 @@ hello world
|
|||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item Pour un point d'entrée donné, appliquer telle fonction, retourner telle valeur
|
\item Pour un point d'entrée donné, appliquer telle fonction, retourner telle valeur
|
||||||
\item Requête : \texttt{GET http://blabla.com/item/123}
|
\item Requête : \texttt{GET http://blabla.com/item/123}
|
||||||
|
\item Méthode : \texttt{GET}
|
||||||
\item Route : \texttt{/item/:itemId}
|
\item Route : \texttt{/item/:itemId}
|
||||||
\item Fonction : \texttt{getItem(id)}
|
\item Fonction : \texttt{getItem(id)}
|
||||||
\item Réponse : \texttt{\{ id: 123, value: "..." \}}
|
\item Réponse : \texttt{\{ id: 123, value: "..." \}}
|
||||||
@@ -1090,22 +1462,233 @@ hello world
|
|||||||
\end{columns}
|
\end{columns}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}{JSON}
|
\begin{frame}[fragile]{Framework pour serveur HTTP : Oak}
|
||||||
|
\begin{minted}[fontsize=\footnotesize]{ts}
|
||||||
|
// Configuration
|
||||||
|
const BASE_URL = "http://localhost:8000";
|
||||||
|
// Initialisation
|
||||||
|
const app = new Application();
|
||||||
|
const router = new Router();
|
||||||
|
// Middlewares
|
||||||
|
app.use(router.routes()); // Encore faut-il définir des routes ! ...
|
||||||
|
app.use(router.allowedMethods());
|
||||||
|
// Événements
|
||||||
|
app.addEventListener(
|
||||||
|
"listen", () => console.log(`Server listening on ${BASE_URL}`),
|
||||||
|
);
|
||||||
|
app.addEventListener(
|
||||||
|
"error", (e) => console.log(`Uncaught error: ${e.message}`),
|
||||||
|
);
|
||||||
|
// Boucle principale
|
||||||
|
if (import.meta.main) {
|
||||||
|
await app.listen({ hostname: "localhost", port: 8000 });
|
||||||
|
}
|
||||||
|
// Export
|
||||||
|
export { app };
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Routage avec Oak : paramètres de la requête}
|
||||||
|
\begin{itemize}
|
||||||
|
\item L'utilisateur envoie une requête \texttt{GET}
|
||||||
|
\item Adresse : \texttt{http://localhost:8000/polls/abcd}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\vspace{.5cm}
|
||||||
|
|
||||||
|
\begin{minted}{ts}
|
||||||
|
router.get("/:pollId", (ctx) => {
|
||||||
|
const pollId = ctx.params.pollId; // abcd
|
||||||
|
});
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Routage avec Oak : paramètres de la requête}
|
||||||
|
\begin{itemize}
|
||||||
|
\item L'utilisateur envoie une requête \texttt{POST}
|
||||||
|
\item Corps de la requête : un objet JSON
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\vspace{.5cm}
|
||||||
|
|
||||||
|
\begin{minted}{json}
|
||||||
|
{ "foo": "bar" }
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
\begin{itemize}
|
||||||
|
\item Adresse : \texttt{http://localhost:8000/polls}
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\vspace{.5cm}
|
||||||
|
|
||||||
|
\begin{minted}{ts}
|
||||||
|
router.post("/polls", async (ctx) => {
|
||||||
|
const createPollRequest = await ctx.request.body.json();
|
||||||
|
// { "foo": "bar" }
|
||||||
|
});
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Routage avec Oak : réponse à la requête}
|
||||||
|
\begin{minted}{ts}
|
||||||
|
export interface APISuccess<T> {
|
||||||
|
success: true;
|
||||||
|
data: T;
|
||||||
|
error?: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface APIFailure {
|
||||||
|
success: false;
|
||||||
|
data?: never;
|
||||||
|
error: APIError;
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Routage avec Oak : réponse à la requête}
|
||||||
|
\begin{minted}{ts}
|
||||||
|
export enum APIErrorCode {
|
||||||
|
NOT_FOUND = "NOT_FOUND",
|
||||||
|
SERVER_ERROR = "SERVER_ERROR",
|
||||||
|
TIMEOUT = "TIMEOUT",
|
||||||
|
UNAUTHORIZED = "UNAUTHORIZED",
|
||||||
|
VALIDATION_ERROR = "VALIDATION_ERROR",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface APIError {
|
||||||
|
code: APIErrorCode;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Routage avec Oak : réponse à la requête}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Et finalement, le type de \textbf{toute} réponse de l'API...
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\vspace{.5cm}
|
||||||
|
|
||||||
|
\begin{minted}{ts}
|
||||||
|
export type APIResponse<T> = APISuccess<T> | APIFailure;
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Routage avec Oak : réponse à la requête}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Si l'entité demandée est trouvée :
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\vspace{.5cm}
|
||||||
|
|
||||||
|
\begin{minted}{ts}
|
||||||
|
const responseBody: APIResponse<Poll> = {
|
||||||
|
success: true,
|
||||||
|
data: poll,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Oak donne passe implicitement le code HTTP OK
|
||||||
|
ctx.response.body = responseBody;
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Routage avec Oak : réponse à la requête}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Si l'entité demandée est introuvable :
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\vspace{.5cm}
|
||||||
|
|
||||||
|
\begin{minted}{ts}
|
||||||
|
const responseBody: APIResponse<Poll> = {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
code: APIErrorCode.NOT_FOUND,
|
||||||
|
message: "Poll not found",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.response.status = 404;
|
||||||
|
ctx.response.body = responseBody;
|
||||||
|
\end{minted}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Routage avec Oak : réponse à la requête}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Le serveur rencontre une erreur :
|
||||||
|
\end{itemize}
|
||||||
|
|
||||||
|
\vspace{.5cm}
|
||||||
|
|
||||||
|
\begin{minted}{ts}
|
||||||
|
const responseBody: APIResponse<Poll> = {
|
||||||
|
success: false,
|
||||||
|
error: {
|
||||||
|
code: APIErrorCode.SERVER_ERROR,
|
||||||
|
message: "Failed to fetch poll",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.response.status = 500;
|
||||||
|
ctx.response.body = responseBody;
|
||||||
|
\end{minted}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}{Middleware}
|
\begin{frame}{Middleware}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Code applicatif qui se positionne \textbf{entre la requête et la réponse}
|
||||||
|
\item \textbf{Intercepte} les données pour leur appliquer des modifications ou effectuer des effets de bord
|
||||||
|
\item En pratique :
|
||||||
|
\begin{itemize}
|
||||||
|
\item Une \textbf{fonction} qui prend en paramètre le \textbf{contexte} (\texttt{ctx}) et la \textbf{prochaine fonction} (\texttt{next})
|
||||||
|
\item Invoquée dans une \textbf{chaîne} de traitement, ordonnée, pour chaque requête
|
||||||
|
\item Capable d'observer, modifier, court-circuiter ou ignorer le cycle de vie requête-réponse
|
||||||
|
\item Appelée par le framework (Oak) lors de la réception d'une requête
|
||||||
|
\end{itemize}
|
||||||
|
\end{itemize}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}{Middleware : CORS}
|
\begin{frame}[fragile]{Middleware}
|
||||||
TODO: à mettre côté client ?
|
\begin{minted}[fontsize=\small]{ts}
|
||||||
|
interface HelloWorldState {
|
||||||
|
hello?: string;
|
||||||
|
world?: string;
|
||||||
|
}
|
||||||
|
async function hello(ctx: Context<HelloWorldState>, next: Next) {
|
||||||
|
ctx.state.hello = "Hello";
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
async function world(ctx: Context<HelloWorldState>, next: Next) {
|
||||||
|
ctx.state.world = "world";
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
const router = new Router<HelloWorldState>();
|
||||||
|
router.get("/hello", hello, world, (ctx) => {
|
||||||
|
ctx.response.body = `${ctx.state.hello}, ${ctx.state.world}!`;
|
||||||
|
});
|
||||||
|
const app = new Application<HelloWorldState>();
|
||||||
|
\end{minted}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}{Middleware : Gestion des erreurs}
|
\begin{frame}[fragile]{Exercice 6 : gestion des erreurs du serveur}
|
||||||
|
\begin{block}{Un unique point de sortie pour les réponses d'erreur}
|
||||||
|
On veut éviter de dupliquer du code pour retourner des erreurs au client dans les fonctions de nos routes. On va donc utiliser un \textbf{middleware} qui sera chargé de remplir la réponse en cas d'erreur.
|
||||||
|
\end{block}
|
||||||
|
|
||||||
|
\begin{minted}{ts}
|
||||||
|
export async function errorMiddleware(ctx: Context, next: Next) { }
|
||||||
|
\end{minted}
|
||||||
|
|
||||||
|
\begin{alertblock}{Questions}
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Sur quel mécanisme s'appuyer pour intercepter les erreurs levées dans les routes de l'application ?
|
||||||
|
\item Comment différencier une erreur \textit{prévue} d'une erreur \textit{inattendue} ?
|
||||||
|
\end{enumerate}
|
||||||
|
\end{alertblock}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Gestion de l'état}
|
||||||
|
|
||||||
\begin{frame}{Base de données}
|
\begin{frame}{Base de données}
|
||||||
\begin{columns}
|
\begin{columns}
|
||||||
\column{.8\textwidth}
|
\column{.8\textwidth}
|
||||||
@@ -1147,7 +1730,7 @@ CREATE TABLE thing_options (
|
|||||||
\vspace{.5cm}
|
\vspace{.5cm}
|
||||||
|
|
||||||
\begin{minted}[fontsize=\footnotesize]{ts}
|
\begin{minted}[fontsize=\footnotesize]{ts}
|
||||||
// Persist vote
|
// Enregistrer le vote
|
||||||
const voteId = crypto.randomUUID();
|
const voteId = crypto.randomUUID();
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
db.prepare(
|
db.prepare(
|
||||||
@@ -1156,7 +1739,7 @@ db.prepare(
|
|||||||
"VALUES (?, ?, ?, ?, ?);",
|
"VALUES (?, ?, ?, ?, ?);",
|
||||||
).run(voteId, pollId, optionId, userId || null, now);
|
).run(voteId, pollId, optionId, userId || null, now);
|
||||||
|
|
||||||
// Increment tally
|
// Incrémenter le compteur
|
||||||
db.prepare(
|
db.prepare(
|
||||||
"UPDATE poll_options SET vote_count = vote_count + 1 WHERE id = ?;",
|
"UPDATE poll_options SET vote_count = vote_count + 1 WHERE id = ?;",
|
||||||
).run(optionId);
|
).run(optionId);
|
||||||
@@ -1173,18 +1756,47 @@ db.prepare(
|
|||||||
|
|
||||||
\begin{minted}{ts}
|
\begin{minted}{ts}
|
||||||
try {
|
try {
|
||||||
db.prepare("BEGIN").run(); // Start transaction
|
db.prepare("BEGIN").run(); // Début de transaction
|
||||||
|
|
||||||
db.prepare("...").run(); // Query 1
|
db.prepare("...").run(); // Requête 1
|
||||||
db.prepare("...").run(); // Query 2
|
db.prepare("...").run(); // Requête 2
|
||||||
|
|
||||||
db.prepare("COMMIT").run(); // Commit transaction
|
db.prepare("COMMIT").run(); // Transaction validée
|
||||||
} catch {
|
} catch {
|
||||||
db.prepare("ROLLBACK").run(); // Rollback on any error
|
db.prepare("ROLLBACK").run(); // Transaction annulée
|
||||||
}
|
}
|
||||||
\end{minted}
|
\end{minted}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}{Exercice 7 : gestion des erreurs du serveur}
|
||||||
|
\begin{alertblock}{Besoin d'atomicité}
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Quelles opérations doivent être \textbf{atomiques} dans notre application de sondage ?
|
||||||
|
\item Pourquoi ? C'est-à-dire : quels problèmes peuvent survenir si les sous-opérations, disjointes, échouent ?
|
||||||
|
\end{enumerate}
|
||||||
|
\end{alertblock}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Architecture}
|
||||||
|
|
||||||
|
\begin{frame}{Organisation du dépôt}
|
||||||
|
% TODO: pas de paradigme précis, responsabilité côté developpeur
|
||||||
|
|
||||||
|
% TODO: reprendre la terminologie du framework (routes, middleware, etc.)
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=.5\textwidth]{img/point.png}
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Exercice : ...}
|
||||||
|
TODO: proposer une organisation
|
||||||
|
|
||||||
|
TODO: montrer les directives export / import associées
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Fiabilité et tests}
|
||||||
|
|
||||||
\begin{frame}[fragile]{Tests}
|
\begin{frame}[fragile]{Tests}
|
||||||
\begin{minted}{ts}
|
\begin{minted}{ts}
|
||||||
import { assertEquals, assert } from "@std/assert";
|
import { assertEquals, assert } from "@std/assert";
|
||||||
@@ -1205,6 +1817,10 @@ Deno.test({
|
|||||||
\end{minted}
|
\end{minted}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}[fragile]{Exercice ...}
|
||||||
|
TODO: écrire des tests pour une ou deux routes
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
\section{Client web}
|
\section{Client web}
|
||||||
|
|
||||||
\begin{frame}{HTTP, encore}
|
\begin{frame}{HTTP, encore}
|
||||||
@@ -1266,10 +1882,6 @@ Deno.test({
|
|||||||
|
|
||||||
\section{Interactions}
|
\section{Interactions}
|
||||||
|
|
||||||
\begin{frame}{REST}
|
|
||||||
|
|
||||||
\end{frame}
|
|
||||||
|
|
||||||
\begin{frame}{Data Transfer Objects}
|
\begin{frame}{Data Transfer Objects}
|
||||||
|
|
||||||
\end{frame}
|
\end{frame}
|
||||||
@@ -1304,6 +1916,18 @@ Deno.test({
|
|||||||
\end{itemize}
|
\end{itemize}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}{Nom d'hôte, port, adresse}
|
||||||
|
\begin{center}
|
||||||
|
\texttt{%
|
||||||
|
{\color{BrickRed}https}://%
|
||||||
|
{\color{MidnightBlue}foo.bar}.%
|
||||||
|
{\color{Orchid}com}%
|
||||||
|
[:{\color{LimeGreen}443}]/%
|
||||||
|
{\color{YellowOrange}baz.html}%
|
||||||
|
}
|
||||||
|
\end{center}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
\begin{frame}[fragile]{Reverse proxy}
|
\begin{frame}[fragile]{Reverse proxy}
|
||||||
\begin{minted}[fontsize=\footnotesize]{text}
|
\begin{minted}[fontsize=\footnotesize]{text}
|
||||||
http {
|
http {
|
||||||
@@ -1330,8 +1954,33 @@ http {
|
|||||||
\end{itemize}
|
\end{itemize}
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
|
\begin{frame}{CORS}
|
||||||
|
TODO:
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
\section{Débuggage et profilage}
|
\section{Débuggage et profilage}
|
||||||
|
|
||||||
|
\begin{frame}{\textit{What could go wrong...}}
|
||||||
|
\begin{itemize}
|
||||||
|
\item Niveau client
|
||||||
|
\item Niveau serveur
|
||||||
|
\item Niveau base de données
|
||||||
|
\item Interactions API / BDD
|
||||||
|
\item Infrastructure
|
||||||
|
\item Sécurité
|
||||||
|
\item Déploiement
|
||||||
|
\item Gestion de l'état
|
||||||
|
\end{itemize}
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Outillage}
|
||||||
|
|
||||||
|
\begin{frame}{\textit{Logging}}
|
||||||
|
TODO:
|
||||||
|
\end{frame}
|
||||||
|
|
||||||
|
\subsection{Profilage des performances}
|
||||||
|
|
||||||
\begin{frame}{Côté serveur}
|
\begin{frame}{Côté serveur}
|
||||||
\begin{columns}
|
\begin{columns}
|
||||||
\column{.5\textwidth}
|
\column{.5\textwidth}
|
||||||
@@ -1361,6 +2010,7 @@ http {
|
|||||||
\section{Bibliographie}
|
\section{Bibliographie}
|
||||||
|
|
||||||
\begin{frame}[allowframebreaks]{Références}
|
\begin{frame}[allowframebreaks]{Références}
|
||||||
|
\nocite{*}
|
||||||
\printbibliography[heading=none]
|
\printbibliography[heading=none]
|
||||||
\end{frame}
|
\end{frame}
|
||||||
|
|
||||||
|
|||||||