Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
2276 lines (2081 loc) · 168 KB
/
Copy pathindex.html
File metadata and controls
2276 lines (2081 loc) · 168 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="color-scheme" content="dark">
<title>The Architecture Decoder: ten software architecture terms, three real questions</title>
<meta name="description" content="Monorepo, modular monolith, micro-frontends, hexagonal, clean, onion, event-driven, pipeline, layered, distributed. A software architecture field guide that shows these ten terms answer three unrelated questions, with plain-language explanations, three depth levels and code examples.">
<meta name="theme-color" content="#0F1319">
<link rel="icon" href="favicon.svg" type="image/svg+xml">
<link rel="icon" href="favicon.png" type="image/png" sizes="32x32">
<link rel="apple-touch-icon" href="apple-touch-icon.png">
<meta property="og:type" content="article">
<meta property="og:url" content="https://coder-rt.github.io/architecture-decoder/">
<meta property="og:title" content="The Architecture Decoder: ten software architecture terms, three real questions">
<meta property="og:description" content="Software architecture terms are not ten competing options. They answer three unrelated questions. Learn which question each word belongs to and the confusion stops.">
<meta property="og:image" content="https://coder-rt.github.io/architecture-decoder/architecture-decoder-card.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Ten architecture words. Three real questions.">
<meta name="twitter:image" content="https://coder-rt.github.io/architecture-decoder/architecture-decoder-card.png">
<meta name="twitter:card" content="summary_large_image">
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"TechArticle",
"headline":"The Architecture Decoder: ten software architecture terms, three real questions",
"description":"A field guide showing that ten commonly confused software architecture terms answer three unrelated questions.",
"author":{"@type":"Person","name":"Rachit Thariani","sameAs":"https://www.linkedin.com/in/rachit-thariani/"},
"datePublished":"2026-08-18","dateModified":"2026-08-18",
"inLanguage":"en","image":"https://coder-rt.github.io/architecture-decoder/architecture-decoder-card.png"}
</script>
<meta name="twitter:title" content="The Architecture Decoder">
<meta name="twitter:description" content="Ten software architecture terms. Three real questions. A field guide with ELI5 explanations and three depth levels per concept.">
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,[email protected],400;12..96,600;12..96,800&family=Instrument+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root{
--ink:#0F1319; --ink2:#151B24; --ink3:#1C2532;
--line:#2A3441; --line2:#3A4757;
--paper:#EDE6D6; --paperink:#2B2418;
--text:#E7E5E0; --dim:#98A1B1; --dim2:#6C7688;
--a1:#8FA6F5; --a2:#E8A94D; --a3:#58C2A0; --warn:#F0776A;
--disp:'Bricolage Grotesque','Trebuchet MS','Segoe UI',Roboto,system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;
--body:'Instrument Sans','Segoe UI',Roboto,system-ui,-apple-system,'Helvetica Neue',Arial,sans-serif;
--mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,'Liberation Mono','Courier New',monospace;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth;color-scheme:dark}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}*{animation-duration:.01ms!important;transition-duration:.01ms!important}}
body{margin:0;background:var(--ink);color:var(--text);font-family:var(--body);
font-size:16.5px;line-height:1.62;-webkit-font-smoothing:antialiased}
body::before{content:"";position:fixed;inset:0;pointer-events:none;z-index:0;opacity:.5;
background-image:linear-gradient(var(--line) .5px,transparent .5px),linear-gradient(90deg,var(--line) .5px,transparent .5px);
background-size:88px 88px;
-webkit-mask-image:radial-gradient(ellipse 90% 60% at 50% 0%,#000 0%,transparent 75%);
mask-image:radial-gradient(ellipse 90% 60% at 50% 0%,#000 0%,transparent 75%)}
#prog{position:fixed;top:0;left:0;height:2px;width:0;z-index:80;
background:linear-gradient(90deg,var(--a1),var(--a2) 50%,var(--a3));transition:width .12s linear}
#top{position:sticky;top:0;z-index:60;background:rgba(15,19,25,.92);backdrop-filter:blur(14px);border-bottom:1px solid var(--line);padding-top:env(safe-area-inset-top)}
.topin{max-width:1180px;margin:0 auto;padding:10px 22px;display:flex;align-items:center;gap:18px}
.brand{font-family:var(--disp);font-weight:800;font-size:15px;letter-spacing:-.02em;white-space:nowrap}
.brand span{color:var(--dim2);font-family:var(--mono);font-weight:400;font-size:11px;letter-spacing:.06em;text-transform:uppercase;margin-left:9px}
.compass{display:flex;gap:5px;margin-left:auto;align-items:center}
.compass b{font-family:var(--mono);font-size:9.5px;letter-spacing:.1em;color:var(--dim2);text-transform:uppercase;font-weight:400;margin-right:4px}
.cbar{width:38px;height:5px;border-radius:3px;background:var(--line);transition:background .3s,box-shadow .3s}
.cbar.on1{background:var(--a1);box-shadow:0 0 12px -2px var(--a1)}
.cbar.on2{background:var(--a2);box-shadow:0 0 12px -2px var(--a2)}
.cbar.on3{background:var(--a3);box-shadow:0 0 12px -2px var(--a3)}
.tbtn{font-family:var(--mono);font-size:11px;letter-spacing:.04em;background:var(--ink3);color:var(--dim);
border:1px solid var(--line2);border-radius:100px;padding:6px 13px;cursor:pointer;transition:.18s;white-space:nowrap}
.tbtn:hover{color:var(--text);border-color:var(--dim2)}
.tbtn[aria-pressed="true"]{background:var(--paper);color:var(--paperink);border-color:var(--paper);font-weight:600}
@media(max-width:880px){.compass b{display:none}.cbar{width:24px}.topin{padding:9px 15px;gap:11px}}
@media(max-width:560px){.brand span{display:none}.tbtn{padding:5px 10px;font-size:10px}.compass{display:none}
.brand{min-width:0;overflow:hidden;text-overflow:ellipsis}}
@media(max-width:400px){.topin{padding:9px 12px;gap:9px}.brand{font-size:13.5px}}
.shell{max-width:1180px;margin:0 auto;padding:0 22px;display:grid;grid-template-columns:210px 1fr;gap:44px;position:relative;z-index:1}
@media(max-width:960px){.shell{grid-template-columns:1fr;gap:0;padding:0 16px}}
#side{position:sticky;top:92px;align-self:start;max-height:calc(100vh - 120px);overflow-y:auto;padding:34px 0 60px;scrollbar-width:thin}
#side::-webkit-scrollbar{width:3px}
#side::-webkit-scrollbar-thumb{background:var(--line2);border-radius:3px}
.navgrp{font-family:var(--mono);font-size:9.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--dim2);margin:22px 0 9px;padding-left:11px}
.navgrp:first-child{margin-top:0}
#side a{display:block;font-size:13.2px;color:var(--dim);text-decoration:none;padding:5px 11px;border-left:2px solid var(--line);line-height:1.35;transition:.16s}
#side a:hover{color:var(--text);border-left-color:var(--dim2)}
#side a.act{color:var(--text);font-weight:600}
#side a.act.n1{border-left-color:var(--a1)}
#side a.act.n2{border-left-color:var(--a2)}
#side a.act.n3{border-left-color:var(--a3)}
@media(max-width:960px){#side{display:none}}
main{padding:0 0 100px;min-width:0;max-width:820px}
.hero{padding:76px 0 54px;border-bottom:1px solid var(--line)}
.eyebrow{font-family:var(--mono);font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--a2);margin:0 0 20px}
h1{font-family:var(--disp);font-weight:800;font-size:clamp(38px,7vw,66px);line-height:.98;letter-spacing:-.035em;margin:0 0 22px}
h1 em{font-style:normal;display:block;color:var(--dim2)}
.lede{font-size:clamp(17px,2.3vw,19.5px);color:var(--dim);max-width:60ch;margin:0 0 14px;line-height:1.55}
.lede strong{color:var(--text);font-weight:600}
section{padding:60px 0;border-bottom:1px solid var(--line);scroll-margin-top:calc(70px + env(safe-area-inset-top))}
.sechead{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;margin-bottom:8px}
.tag{font-family:var(--mono);font-size:10px;letter-spacing:.11em;text-transform:uppercase;padding:4px 9px;border-radius:4px;border:1px solid;white-space:nowrap}
.x1 .tag{color:var(--a1);border-color:color-mix(in srgb,var(--a1) 42%,transparent);background:color-mix(in srgb,var(--a1) 9%,transparent)}
.x2 .tag{color:var(--a2);border-color:color-mix(in srgb,var(--a2) 42%,transparent);background:color-mix(in srgb,var(--a2) 9%,transparent)}
.x3 .tag{color:var(--a3);border-color:color-mix(in srgb,var(--a3) 42%,transparent);background:color-mix(in srgb,var(--a3) 9%,transparent)}
.x0 .tag{color:var(--dim);border-color:var(--line2);background:var(--ink2)}
h2{font-family:var(--disp);font-weight:800;font-size:clamp(27px,4.4vw,38px);letter-spacing:-.03em;line-height:1.08;margin:0 0 6px}
.sub{color:var(--dim2);font-family:var(--mono);font-size:12.5px;margin:0 0 30px}
h3{font-family:var(--disp);font-weight:600;font-size:19px;letter-spacing:-.015em;margin:34px 0 10px}
h4{font-family:var(--disp);font-weight:600;font-size:15.5px;margin:22px 0 8px;letter-spacing:-.01em}
p{margin:0 0 15px;max-width:66ch}
strong{font-weight:600;color:#fff}
a{color:var(--a1)}
ul{max-width:66ch;padding-left:20px;margin:0 0 15px}
li{margin-bottom:7px}
.wwh{margin:26px 0}
.wwh-h{display:flex;align-items:center;gap:11px;margin:0 0 9px}
.wwh-n{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink);background:var(--dim);padding:3px 8px;border-radius:3px;font-weight:600}
.x1 .wwh-n{background:var(--a1)}.x2 .wwh-n{background:var(--a2)}.x3 .wwh-n{background:var(--a3)}
.wwh-h h3{margin:0;font-size:18px}
.eli{background:var(--paper);color:var(--paperink);border-radius:3px;margin:24px 0;box-shadow:0 12px 30px -14px rgba(0,0,0,.75);position:relative;overflow:hidden}
.eli::before{content:"";position:absolute;inset:0;pointer-events:none;background:repeating-linear-gradient(0deg,transparent,transparent 27px,rgba(43,36,24,.055) 27px,rgba(43,36,24,.055) 28px)}
.eli-t{width:100%;background:none;border:0;border-bottom:1px solid rgba(43,36,24,.18);font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;color:#6B5B3C;padding:11px 18px;display:flex;align-items:center;gap:9px;cursor:pointer;text-align:left;font-weight:600;position:relative;z-index:1}
.eli-t:hover{color:var(--paperink)}
.eli-t .chev{margin-left:auto;transition:transform .22s;font-size:13px}
.eli.open .eli-t .chev{transform:rotate(90deg)}
.eli-b{padding:16px 18px 19px;position:relative;z-index:1;display:none}
.eli.open .eli-b{display:block}
.eli-b p{margin:0 0 11px;font-size:16px;line-height:1.6;max-width:62ch}
.eli-b p:last-child{margin:0}
.eli-b strong{color:#4A2F14}
.eli-b em{font-style:italic;color:#6B5B3C}
pre{background:var(--ink2);border:1px solid var(--line);border-left:3px solid var(--dim2);border-radius:0 6px 6px 0;padding:15px 17px;overflow-x:auto;margin:15px 0;font-family:var(--mono);font-size:12.8px;line-height:1.72;color:#CBD3E0}
.x1 pre{border-left-color:var(--a1)}.x2 pre{border-left-color:var(--a2)}.x3 pre{border-left-color:var(--a3)}
pre b{color:#fff;font-weight:600}
pre i{color:var(--dim2);font-style:normal}
pre u{text-decoration:none;color:var(--a2)}
pre s{text-decoration:none;color:var(--warn)}
pre em{font-style:normal;color:var(--a3)}
code{font-family:var(--mono);font-size:.885em;background:var(--ink3);padding:1.5px 5px;border-radius:3px;color:#CBD3E0;border:1px solid var(--line)}
pre code{background:none;border:0;padding:0;font-size:inherit}
.lv{margin:28px 0;border:1px solid var(--line);border-radius:9px;background:var(--ink2);overflow:hidden}
.lv-bar{display:flex;border-bottom:1px solid var(--line);background:var(--ink);flex-wrap:wrap}
.lv-bar button{flex:1;min-width:104px;background:none;border:0;border-bottom:2px solid transparent;color:var(--dim2);font-family:var(--mono);font-size:11px;letter-spacing:.09em;text-transform:uppercase;padding:12px 8px;cursor:pointer;transition:.18s;font-weight:500}
.lv-bar button:hover{color:var(--text)}
.lv-bar button[aria-selected="true"]{color:var(--text);background:var(--ink2)}
.x1 .lv-bar button[aria-selected="true"]{border-bottom-color:var(--a1)}
.x2 .lv-bar button[aria-selected="true"]{border-bottom-color:var(--a2)}
.x3 .lv-bar button[aria-selected="true"]{border-bottom-color:var(--a3)}
.lv-pane{padding:20px 20px 24px;display:none}
.lv-pane.on{display:block}
.lv-pane>*:first-child{margin-top:0}
.lv-pane pre{background:var(--ink)}
.lvl-note{font-family:var(--mono);font-size:11px;color:var(--dim2);margin:0 0 14px;padding-bottom:12px;border-bottom:1px dashed var(--line)}
.jg{border-bottom:1.5px dotted var(--a2);cursor:help;color:inherit;background:color-mix(in srgb,var(--a2) 7%,transparent);padding:0 1px}
.jg:hover,.jg:focus{background:color-mix(in srgb,var(--a2) 17%,transparent);outline:none}
#tip{position:fixed;z-index:200;max-width:310px;background:var(--paper);color:var(--paperink);border-radius:5px;padding:11px 13px;font-size:13.6px;line-height:1.5;display:none;box-shadow:0 18px 44px -14px rgba(0,0,0,.85)}
#tip .tt{font-family:var(--mono);font-size:10px;letter-spacing:.11em;text-transform:uppercase;color:#6B5B3C;display:block;margin-bottom:5px;font-weight:600}
.recap{border:1px dashed var(--line2);border-radius:8px;padding:17px 19px;margin:28px 0;background:var(--ink2)}
.recap h4{margin:0 0 12px;font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--a2);font-weight:500}
.recap dl{margin:0;display:grid;gap:9px}
.recap dt{font-family:var(--mono);font-size:12.5px;color:var(--text);font-weight:600}
.recap dd{margin:2px 0 0;font-size:14.6px;color:var(--dim);line-height:1.52}
.note{border-left:3px solid var(--warn);background:color-mix(in srgb,var(--warn) 7%,var(--ink2));padding:14px 17px;border-radius:0 7px 7px 0;margin:22px 0}
.note p{margin:0;font-size:15.3px}
.note .lbl{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--warn);display:block;margin-bottom:6px;font-weight:600}
.key{border-left:3px solid var(--a3);background:color-mix(in srgb,var(--a3) 7%,var(--ink2));padding:14px 17px;border-radius:0 7px 7px 0;margin:22px 0}
.key p{margin:0;font-size:15.6px}
.key .lbl{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--a3);display:block;margin-bottom:6px;font-weight:600}
.tw{overflow-x:auto;margin:24px 0;border:1px solid var(--line);border-radius:8px}
table{border-collapse:collapse;width:100%;font-size:14.3px;min-width:560px}
th{background:var(--ink2);font-family:var(--mono);font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);text-align:left;padding:11px 14px;font-weight:500;border-bottom:1px solid var(--line)}
td{padding:12px 14px;border-bottom:1px solid var(--line);vertical-align:top;color:var(--dim);line-height:1.5}
tr:last-child td{border-bottom:0}
td strong{color:var(--text)}
.dg{margin:26px 0;padding:22px;background:var(--ink2);border:1px solid var(--line);border-radius:9px}
.dg svg{width:100%;height:auto;display:block}
.dg figcaption{font-family:var(--mono);font-size:11.2px;color:var(--dim2);margin-top:14px;padding-top:12px;border-top:1px dashed var(--line);line-height:1.55}
.sv-box{fill:var(--ink3);stroke:var(--line2);stroke-width:1}
.sv-t{fill:var(--text);font-family:var(--mono);font-size:12px}
.sv-s{fill:var(--dim2);font-family:var(--mono);font-size:10.5px}
.sv-l{stroke:var(--dim2);stroke-width:1.3;fill:none}
.wiz{background:var(--ink2);border:1px solid var(--line2);border-radius:12px;padding:24px;margin:28px 0}
.wq{margin-bottom:22px}
.wq>p{font-family:var(--disp);font-weight:600;font-size:16px;margin:0 0 11px;color:var(--text)}
.wopts{display:flex;flex-wrap:wrap;gap:8px}
.wopts button{background:var(--ink);border:1px solid var(--line2);color:var(--dim);border-radius:7px;padding:9px 14px;font-family:var(--body);font-size:14px;cursor:pointer;transition:.18s}
.wopts button:hover{border-color:var(--dim);color:var(--text)}
.wopts button[aria-pressed="true"]{background:var(--paper);color:var(--paperink);border-color:var(--paper);font-weight:600}
#wout{margin-top:8px;padding:19px;border-radius:9px;background:var(--ink);border:1px dashed var(--line2);font-size:15.4px;color:var(--dim);line-height:1.6}
#wout .verdict{font-family:var(--disp);font-weight:800;font-size:19px;color:var(--text);display:block;margin-bottom:10px;letter-spacing:-.02em;line-height:1.25}
.qz{border:1px solid var(--line);border-radius:9px;padding:19px 21px;margin:20px 0;background:var(--ink2)}
.qz>p{font-family:var(--disp);font-weight:600;font-size:16.5px;margin:0 0 13px}
.qopts{display:grid;gap:7px}
.qopts button{text-align:left;background:var(--ink);border:1px solid var(--line);color:var(--dim);border-radius:7px;padding:10px 14px;font-family:var(--body);font-size:14.6px;cursor:pointer;transition:.16s}
.qopts button:hover:not([disabled]){border-color:var(--dim2);color:var(--text)}
.qopts button.right{border-color:var(--a3);background:color-mix(in srgb,var(--a3) 12%,var(--ink));color:var(--text)}
.qopts button.wrong{border-color:var(--warn);background:color-mix(in srgb,var(--warn) 12%,var(--ink));color:var(--text)}
.qfb{margin-top:12px;font-size:14.6px;color:var(--dim);display:none;padding-top:12px;border-top:1px dashed var(--line);line-height:1.55}
.qfb.on{display:block}
#gsearch{width:100%;background:var(--ink2);border:1px solid var(--line2);border-radius:8px;padding:12px 15px;color:var(--text);font-family:var(--body);font-size:15px;margin-bottom:16px}
#gsearch:focus{outline:none;border-color:var(--a2)}
.gl{display:grid;gap:8px}
.gitem{border:1px solid var(--line);border-radius:8px;background:var(--ink2);padding:15px 18px}
.gitem dt{font-family:var(--mono);font-size:13.5px;color:var(--text);font-weight:600;margin-bottom:5px}
.gitem dd{margin:0;font-size:15px;color:var(--dim);line-height:1.55}
footer{padding:44px 0 20px;color:var(--dim2);font-family:var(--mono);font-size:11.5px;line-height:1.7}
:focus-visible{outline:2px solid var(--a2);outline-offset:3px;border-radius:3px}
.res{display:grid;gap:9px;margin:16px 0 30px}
.ritem{display:block;border:1px solid var(--line);border-left:3px solid var(--dim2);border-radius:0 8px 8px 0;background:var(--ink2);padding:13px 16px;text-decoration:none;transition:.16s}
.ritem:hover{border-color:var(--line2);background:var(--ink3)}
.ritem b{display:block;color:var(--text);font-family:var(--disp);font-weight:600;font-size:15.5px;margin-bottom:3px;letter-spacing:-.01em}
.ritem em{display:block;color:var(--dim);font-size:14.2px;line-height:1.5;font-style:normal}
.ritem cite{display:block;font-family:var(--mono);font-size:10.5px;color:var(--dim2);font-style:normal;margin-top:7px;letter-spacing:.04em;word-break:break-all}
.r1{border-left-color:var(--a1)}.r2{border-left-color:var(--a2)}.r3{border-left-color:var(--a3)}
.books{border:1px dashed var(--line2);border-radius:8px;padding:17px 20px;background:var(--ink2)}
.books h4{margin:0 0 11px;font-family:var(--mono);font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;color:var(--a2);font-weight:500}
.books ul{margin:0;padding-left:18px}
.books li{font-size:14.6px;color:var(--dim);margin-bottom:9px;line-height:1.5}
.books li:last-child{margin-bottom:0}
.books strong{color:var(--text)}
.demo{margin:28px 0;border:1px solid var(--line2);border-radius:11px;background:var(--ink2);overflow:hidden}
.demo-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:13px 17px;border-bottom:1px solid var(--line);background:var(--ink)}
.demo-lbl{font-family:var(--mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--a2);font-weight:500}
.demo-toggle{display:flex;margin-left:auto;background:var(--ink3);border-radius:100px;padding:3px;gap:2px;border:1px solid var(--line)}
.demo-toggle button{border:0;background:none;color:var(--dim2);font-family:var(--mono);font-size:11px;padding:6px 13px;border-radius:100px;cursor:pointer;transition:.18s;white-space:nowrap}
.demo-toggle button[aria-pressed="true"]{background:var(--paper);color:var(--paperink);font-weight:600}
.demo-stage{padding:18px 17px 6px}
.demo-stage svg{width:100%;height:auto;display:block}
.dnode rect{fill:var(--ink3);stroke:var(--line2);stroke-width:1;transition:fill .3s,stroke .3s}
.dnode text{fill:var(--dim);font-family:var(--mono);font-size:12.5px;transition:fill .3s}
.dnode.hot rect{fill:color-mix(in srgb,var(--warn) 22%,var(--ink3));stroke:var(--warn)}
.dnode.hot text{fill:#fff}
.dnode.src rect{fill:color-mix(in srgb,var(--a2) 22%,var(--ink3));stroke:var(--a2)}
.dnode.src text{fill:#fff}
.dedge{stroke:var(--line2);stroke-width:1.4;fill:none;transition:stroke .3s,opacity .3s}
.dedge.hot{stroke:var(--warn);opacity:1}
.demo-scen{display:flex;flex-wrap:wrap;gap:7px;padding:10px 17px 15px;border-bottom:1px solid var(--line)}
.demo-scen button{background:var(--ink);border:1px solid var(--line);color:var(--dim);border-radius:7px;padding:8px 13px;font-family:var(--body);font-size:13.5px;cursor:pointer;transition:.16s;text-align:left}
.demo-scen button:hover{border-color:var(--dim2);color:var(--text)}
.demo-scen button[aria-pressed="true"]{border-color:var(--a2);background:color-mix(in srgb,var(--a2) 11%,var(--ink));color:var(--text)}
.demo-out{display:grid;grid-template-columns:repeat(3,1fr);gap:1px;background:var(--line);border-bottom:1px solid var(--line)}
.dstat{background:var(--ink2);padding:14px 16px}
.dstat b{display:block;font-family:var(--disp);font-weight:800;font-size:25px;color:var(--warn);letter-spacing:-.02em;line-height:1.1;transition:color .3s}
.dstat.ok b{color:var(--a3)}
.dstat span{display:block;font-family:var(--mono);font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--dim2);margin-top:4px}
.demo-body{padding:16px 17px 18px}
.demo-body pre{margin:0 0 13px;background:var(--ink)}
.dverdict{margin:0;font-size:15px;line-height:1.55;color:var(--dim)}
.dverdict b{color:var(--text)}
@media(max-width:560px){.demo-out{grid-template-columns:1fr 1fr}.demo-scen button{font-size:12.5px;padding:7px 11px}}
footer{padding:46px 0 64px;color:var(--dim2);font-family:var(--mono);font-size:11.5px;line-height:1.7}
.fnote{max-width:62ch;margin:0 0 24px}
.fauth{display:flex;align-items:center;gap:13px;flex-wrap:wrap;padding-top:22px;border-top:1px solid var(--line)}
.fby{font-family:var(--mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--dim2)}
.fname{font-family:var(--disp);font-weight:600;font-size:17px;color:var(--text);letter-spacing:-.015em}
.flink{display:inline-flex;align-items:center;gap:7px;margin-left:auto;color:var(--dim);text-decoration:none;
border:1px solid var(--line2);border-radius:100px;padding:7px 15px;transition:.16s;
font-family:var(--mono);font-size:11.5px;letter-spacing:.03em}
.flink:hover{color:var(--text);border-color:var(--a1);background:color-mix(in srgb,var(--a1) 12%,transparent)}
.flink svg{width:12px;height:12px;stroke:currentColor;stroke-width:1.8;fill:none;stroke-linecap:round;stroke-linejoin:round}
@media(max-width:520px){.flink{margin-left:0}}
.byline{display:flex;align-items:center;gap:13px;flex-wrap:wrap;margin-top:34px;padding-top:24px;border-top:1px solid var(--line)}
@media(max-width:520px){.byline .flink{margin-left:0}}
.fbars{display:flex;gap:5px;margin-bottom:24px}
.fbars i{width:40px;height:5px;border-radius:3px;display:block}
.fbars i:nth-child(1){background:var(--a1)}
.fbars i:nth-child(2){background:var(--a2)}
.fbars i:nth-child(3){background:var(--a3)}
.slogan{font-family:var(--disp);font-weight:800;font-size:clamp(23px,3.8vw,33px);line-height:1.1;
letter-spacing:-.032em;color:var(--text);max-width:19ch;margin:0 0 18px}
#totop{position:fixed;right:max(22px,env(safe-area-inset-right));bottom:max(22px,env(safe-area-inset-bottom));z-index:70;width:44px;height:44px;border-radius:50%;
display:grid;place-items:center;cursor:pointer;background:var(--ink3);border:1px solid var(--line2);
color:var(--dim);opacity:0;visibility:hidden;transform:translateY(10px);
transition:opacity .25s,transform .25s,visibility .25s,color .16s,border-color .16s,background .16s}
#totop.on{opacity:1;visibility:visible;transform:none}
#totop:hover{color:var(--text);border-color:var(--a2);background:color-mix(in srgb,var(--a2) 14%,var(--ink3))}
#totop svg{width:17px;height:17px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
@media(max-width:560px){#totop{right:max(14px,env(safe-area-inset-right));bottom:max(14px,env(safe-area-inset-bottom))}}
/* reading time */
.rtime{font-family:var(--mono);font-size:10px;letter-spacing:.13em;text-transform:uppercase;color:var(--dim2);margin-left:auto}
@media(max-width:520px){.rtime{margin-left:0;width:100%}}
/* copy buttons */
.prewrap{position:relative}
.prewrap>pre{margin:15px 0}
.cpy{position:absolute;top:24px;right:9px;z-index:2;background:var(--ink3);border:1px solid var(--line2);
color:var(--dim2);font-family:var(--mono);font-size:10px;letter-spacing:.07em;text-transform:uppercase;
padding:5px 9px;border-radius:5px;cursor:pointer;opacity:0;transition:.16s}
.prewrap:hover .cpy,.cpy:focus{opacity:1}
.cpy:hover{color:var(--text);border-color:var(--dim2)}
.cpy.done{color:var(--a3);border-color:var(--a3);opacity:1}
@media(hover:none){.cpy{opacity:1}}
/* mobile navigation */
#navbtn{display:none;background:var(--ink3);border:1px solid var(--line2);color:var(--dim);
width:44px;height:44px;border-radius:8px;cursor:pointer;place-items:center;flex:0 0 auto}
#navbtn svg{width:16px;height:16px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round}
#navbtn:hover{color:var(--text)}
#scrim{position:fixed;inset:0;background:rgba(8,10,14,.72);z-index:90;opacity:0;visibility:hidden;
transition:opacity .22s,visibility .22s;backdrop-filter:blur(3px)}
#scrim.on{opacity:1;visibility:visible}
#sheet{position:fixed;top:0;left:0;bottom:0;width:min(310px,86vw);background:var(--ink2);z-index:91;
border-right:1px solid var(--line);transform:translateX(-100%);transition:transform .26s cubic-bezier(.4,0,.2,1);
overflow-y:auto;padding:18px 0 40px;padding-top:calc(18px + env(safe-area-inset-top));padding-bottom:calc(40px + env(safe-area-inset-bottom));padding-left:env(safe-area-inset-left)}
#sheet.on{transform:none}
#sheet .sh-h{display:flex;align-items:center;padding:0 18px 16px;border-bottom:1px solid var(--line);margin-bottom:14px}
#sheet .sh-h b{font-family:var(--disp);font-weight:800;font-size:15px;letter-spacing:-.02em;color:var(--text)}
#sheet .sh-x{margin-left:auto;background:none;border:0;color:var(--dim2);font-size:22px;cursor:pointer;line-height:1;width:44px;height:44px}
#sheet .sh-x:hover{color:var(--text)}
#sheet .navgrp{padding-left:18px}
#sheet a{display:block;font-size:14.5px;color:var(--dim);text-decoration:none;padding:8px 18px;
border-left:2px solid var(--line);line-height:1.35}
#sheet a:active{color:var(--text)}
@media(max-width:960px){#navbtn{display:grid}}
/* print */
@media print{
body{background:#fff;color:#111;font-size:11pt}
body::before,#top,#side,#totop,#scrim,#sheet,.cpy,.demo-toggle,.demo-scen,#prog,#gsearch{display:none!important}
.shell{display:block;max-width:100%;padding:0}
main{max-width:100%}
a{color:#111;text-decoration:underline}
.ritem cite,.flink svg{display:none}
h1,h2,h3,h4,.slogan,.fname{color:#000}
p,li,td,.sub,.dverdict{color:#222}
section{border-bottom:1px solid #ccc;padding:18pt 0;page-break-inside:auto}
h2,h3{page-break-after:avoid}
pre,figure,table,.eli,.qz{page-break-inside:avoid}
pre{background:#f5f5f5;border:1px solid #ddd;color:#111}
pre b,pre em,pre u{color:#000}
pre i,pre s{color:#555}
code{background:#f0f0f0;color:#111}
.lv-bar{display:none}
.lv-pane{display:block!important;border-top:1px solid #ddd}
.eli-b{display:block!important}
.eli{background:#fafafa;color:#111;box-shadow:none;border:1px solid #ddd}
.eli-b p{color:#111}
.jg{background:none;border-bottom:1px dotted #888}
.recap,.note,.key,.dg,.wiz{border-color:#ccc;background:#fafafa}
.dstat b{color:#111}
th{background:#f0f0f0;color:#111}
}
</style>
</head>
<body>
<div id="prog"></div>
<div id="tip" role="tooltip"></div>
<header id="top">
<div class="topin">
<button id="navbtn" aria-label="Open contents" aria-expanded="false" aria-controls="sheet"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 6h18M3 12h18M3 18h18"/></svg></button>
<div class="brand">The Architecture Decoder<span>3 axes / 10 words</span></div>
<button class="tbtn" id="eliAll" aria-pressed="false">Open all ELI5</button>
<div class="compass" aria-hidden="true">
<b>Axis</b>
<div class="cbar" id="cb1" title="Axis 1: repo"></div>
<div class="cbar" id="cb2" title="Axis 2: deployment"></div>
<div class="cbar" id="cb3" title="Axis 3: internal"></div>
</div>
</div>
</header>
<div id="scrim" hidden></div>
<nav id="sheet" aria-label="Contents" aria-hidden="true">
<div class="sh-h"><b>Contents</b><button class="sh-x" aria-label="Close contents">×</button></div>
<div id="sheetlinks"></div>
</nav>
<div class="shell">
<nav id="side" aria-label="Contents">
<div class="navgrp">Start</div>
<a href="#start" data-ax="0">The whole idea</a>
<div class="navgrp">Axis 1: Repo</div>
<a href="#repo" data-ax="1">Monorepo vs polyrepo</a>
<div class="navgrp">Axis 2: Deployment</div>
<a href="#monolith" data-ax="2">Modular monolith</a>
<a href="#distributed" data-ax="2">Distributed architecture</a>
<a href="#mfe-build" data-ax="2">Build-time micro-frontends</a>
<a href="#mfe-run" data-ax="2">Runtime micro-frontends</a>
<div class="navgrp">Axis 3: Inside</div>
<a href="#hex" data-ax="3">Hexagonal / Onion / Clean</a>
<a href="#layered" data-ax="3">Layered & GUI</a>
<a href="#events" data-ax="3">Event-driven</a>
<a href="#pipeline" data-ax="3">Pipeline</a>
<div class="navgrp">Decide & test</div>
<a href="#wizard" data-ax="0">Pick yours</a>
<a href="#quiz" data-ax="0">Test yourself</a>
<a href="#glossary" data-ax="0">Jargon glossary</a>
<a href="#reading" data-ax="0">Further reading</a>
</nav>
<main>
<div class="hero">
<p class="eyebrow">A field guide to software architecture</p>
<h1>Ten architecture words.<em>Three real questions.</em></h1>
<p class="lede">Monorepo. Modular monolith. Micro-frontends. Hexagonal, Clean and Onion Architecture. Event-driven, pipeline, layered, distributed. Every architecture discussion drags in these ten terms as though you have to pick a side.</p>
<p class="lede"><strong>You do not. They answer three unrelated questions,</strong> and most of them were never alternatives to each other. Learn which question each word belongs to, and the confusion stops for good.</p>
<p class="lede">Every concept below comes with a plain-language explanation, three depth levels from beginner to advanced, code examples, and an interactive demo of the problem it solves.</p>
<div class="byline">
<span class="fby">Written by</span>
<span class="fname">Rachit Thariani</span>
<span class="rtime" id="rtime"></span>
<a class="flink" href="https://www.linkedin.com/in/rachit-thariani/" target="_blank" rel="noopener">
LinkedIn
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 4h6v6"/><path d="M20 4L10 14"/><path d="M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5"/></svg>
</a>
</div>
</div>
<section id="start" class="x0">
<div class="sechead"><span class="tag">Orientation</span></div>
<h2>The three axes</h2>
<p class="sub">Read this once. Everything below hangs off it.</p>
<p>Picture building a business park. Three separate decisions, and answering one does not answer the others:</p>
<ul>
<li><strong>The filing cabinet</strong> where blueprints are stored: one cabinet or several? That is your <em>repo</em> question.</li>
<li><strong>The buildings you actually construct</strong>: one big building, or five connected by paths? That is your <em>deployment</em> question.</li>
<li><strong>How rooms are arranged inside</strong> any given building, where the plumbing runs, which room opens into which. That is your <em>internal structure</em> question.</li>
</ul>
<p>You can keep blueprints for five buildings in one cabinet. You can spread one building's blueprints across five cabinets. The cabinets have nothing to do with the buildings. That is why "monorepo" and "monolith" are unrelated despite sharing a prefix.</p>
<figure class="dg">
<svg viewBox="0 0 700 250" role="img" aria-label="Three axes, each with its own options">
<text class="sv-t" x="0" y="34">AXIS 1</text><text class="sv-s" x="0" y="50">where code lives</text>
<rect class="sv-box" style="stroke:var(--a1)" x="135" y="14" width="170" height="52" rx="7"/>
<text class="sv-t" x="220" y="44" text-anchor="middle">Monorepo</text>
<rect class="sv-box" style="stroke:var(--a1)" x="320" y="14" width="170" height="52" rx="7"/>
<text class="sv-t" x="405" y="44" text-anchor="middle">Polyrepo</text>
<text class="sv-t" x="0" y="114">AXIS 2</text><text class="sv-s" x="0" y="130">what you deploy</text>
<rect class="sv-box" style="stroke:var(--a2)" x="135" y="94" width="170" height="52" rx="7"/>
<text class="sv-t" x="220" y="118" text-anchor="middle">Modular monolith</text>
<text class="sv-s" x="220" y="134" text-anchor="middle">1 deploy unit</text>
<rect class="sv-box" style="stroke:var(--a2)" x="320" y="94" width="170" height="52" rx="7"/>
<text class="sv-t" x="405" y="118" text-anchor="middle">Build-time MFEs</text>
<text class="sv-s" x="405" y="134" text-anchor="middle">1 deploy unit</text>
<rect class="sv-box" style="stroke:var(--a2)" x="505" y="94" width="170" height="52" rx="7"/>
<text class="sv-t" x="590" y="118" text-anchor="middle">Runtime MFEs</text>
<text class="sv-s" x="590" y="134" text-anchor="middle">many deploy units</text>
<text class="sv-t" x="0" y="194">AXIS 3</text><text class="sv-s" x="0" y="210">code inside a unit</text>
<rect class="sv-box" style="stroke:var(--a3)" x="135" y="174" width="170" height="52" rx="7"/>
<text class="sv-t" x="220" y="204" text-anchor="middle">Hexagonal / Clean</text>
<rect class="sv-box" style="stroke:var(--a3)" x="320" y="174" width="170" height="52" rx="7"/>
<text class="sv-t" x="405" y="204" text-anchor="middle">Layered / Tiers</text>
<rect class="sv-box" style="stroke:var(--a3)" x="505" y="174" width="170" height="52" rx="7"/>
<text class="sv-t" x="590" y="204" text-anchor="middle">Events / Pipeline</text>
</svg>
<figcaption>You answer each row. "Modular monolith in a monorepo with hexagonal modules" is one coherent sentence, not three competing choices.</figcaption>
</figure>
<div class="eli"><button class="eli-t">Explain like I'm five <span class="chev">›</span></button><div class="eli-b">
<p>Imagine your toys.</p>
<p><strong>Question one:</strong> do you keep all your toys in one big box, or a separate box for each kind of toy? That is the <strong>repo</strong> question. It is only about storage.</p>
<p><strong>Question two:</strong> when you build a castle, is it one giant castle, or five small castles standing apart? That is the <strong>deployment</strong> question. It is about how many separate things exist when you actually play.</p>
<p><strong>Question three:</strong> inside one castle, where do you put the kitchen and the throne room? That is the <strong>internal structure</strong> question.</p>
<p>Here is the part grown-ups get wrong: <em>keeping all toys in one box does not mean you build one castle.</em> They are separate choices. Big box, five castles is perfectly fine.</p>
</div></div>
<div class="key"><span class="lbl">The one rule</span><p>Before you compare two architecture words, ask which axis each belongs to. If they are on different axes, they are not alternatives and comparing them is a category error.</p></div>
<h3>One more distinction: style, pattern, tool</h3>
<p>The three axes tell you <em>which question</em> a word answers. This tells you <em>what kind of thing</em> the word is, and it prevents a second, very common category error.</p>
<div class="tw"><table>
<thead><tr><th>Level</th><th>What it is</th><th>Examples on this page</th></tr></thead>
<tbody>
<tr><td><strong>Style</strong></td><td>The overall shape of a system or of a unit inside it</td><td>modular monolith, microservices, runtime micro-frontends, layered, event-driven, pipeline, Hexagonal Architecture</td></tr>
<tr><td><strong>Pattern</strong></td><td>A reusable solution to a recurring problem within a style</td><td>ports and adapters, saga, transactional outbox, circuit breaker, BFF, reducer, dead-letter queue</td></tr>
<tr><td><strong>Tool</strong></td><td>A concrete product that implements a style or a pattern</td><td>Nx, Turborepo, Module Federation, Kafka, BullMQ, Prisma, Redux</td></tr>
</tbody>
</table></div>
<p>Arguments get stuck when people compare across levels without noticing. <em>"Should we use microservices or Kafka?"</em> is not a question with an answer, because one is a style and the other is a tool you could use in either style. Neither is <em>"hexagonal or Prisma"</em>.</p>
<p>The trap runs the other way too. Tools get spoken about as though they were architectures, usually because the tool arrived first and the vocabulary followed. Module Federation is the clearest case: it is a bundler feature, not a way of designing software. The style is runtime micro-frontends, and you could implement that style with import maps, single-spa, web components or plain iframes instead.</p>
<h3>What this model is not</h3>
<p>Three axes is a teaching frame, not a complete taxonomy. Some concepts genuinely straddle it. Event-driven is the clearest case: as an in-process bus it is purely an axis 3 choice about how modules talk, but swap in Kafka and it becomes an axis 2 choice too, because you have just created deploy units. Pipeline does the same thing when its stages become separate workers.</p>
<p>Treat the axes as dimensions you can vary independently, not as boxes every word drops neatly into. The frame earns its keep by stopping the most common category error, and it does not have to be exhaustive to do that.</p>
<div class="key"><span class="lbl">Two questions, not one</span><p>Before comparing two terms, ask which axis each belongs to, then ask which level each sits at. If either answer differs, they were never competing.</p></div>
</section>
<section id="repo" class="x1">
<div class="sechead"><span class="tag">Axis 1 · where code lives</span></div>
<h2>Monorepo vs polyrepo</h2>
<p class="sub">The cheapest decision on the page. Answer it in ten seconds.</p>
<div class="wwh"><div class="wwh-h"><span class="wwh-n">Why</span><h3>Why this exists at all</h3></div>
<p>A product is rarely one program. You have an API, a website, an admin console, some shared code, some infrastructure config. Those files have to live somewhere in version control, and someone has to decide whether they share a repository or get their own.</p>
<p>Get it wrong toward polyrepo and a one-line change to a shared type becomes a four-repository, four-pull-request afternoon. Get it wrong toward monorepo without tooling and your CI runs the whole test suite for a typo in the README.</p></div>
<div class="wwh"><div class="wwh-h"><span class="wwh-n">What it does</span><h3>What each choice actually gives you</h3></div>
<p>A <strong>monorepo</strong> is one repository holding many projects. One clone gets you everything. Changes that touch several projects land in a single atomic commit, so the codebase is never in a half-updated state.</p>
<p>A <strong>polyrepo</strong> (also called multi-repo) gives each project its own repository. Shared code becomes a published package that others install by version number.</p>
<p>Neither one says anything about how many servers you run. Google uses a monorepo and runs thousands of separate services. Netflix uses polyrepo. All four combinations exist in production somewhere.</p></div>
<div class="wwh"><div class="wwh-h"><span class="wwh-n">How</span><h3>How you actually set it up</h3></div>
<p>For a monorepo you need a workspace tool so that builds and tests only run for what changed. Pick one: Nx, Turborepo, Bazel, or plain pnpm workspaces for small setups. Then add a CODEOWNERS file so the right people review the right folders.</p>
<p>For polyrepo you need a package registry (npm, Artifactory, GitHub Packages) and a versioning discipline, because now every shared change is a version bump followed by upgrades in each consumer.</p></div>
<div class="eli"><button class="eli-t">Explain like I'm five <span class="chev">›</span></button><div class="eli-b">
<p>You and three friends are drawing a comic book together.</p>
<p><strong>Monorepo</strong> is one shared notebook. Everyone draws in the same notebook. If you change a character's hat on page 2, you can fix it on page 9 at the same time, in one go. But everyone has to be careful not to scribble on each other's pages.</p>
<p><strong>Polyrepo</strong> is a notebook each. Very tidy, nobody scribbles on anyone. But if the hat changes, you have to walk to each friend and ask them to update their notebook, and some of them will forget.</p>
<p>Most small teams are happier with one shared notebook.</p>
</div></div>
<div class="lv" data-lv>
<div class="lv-bar" role="tablist">
<button role="tab" aria-selected="true">Basic</button>
<button role="tab" aria-selected="false">Intermediate</button>
<button role="tab" aria-selected="false">Advanced</button>
</div>
<div class="lv-pane on" role="tabpanel">
<p class="lvl-note">Level 1: just look at what is on disk.</p>
<p>Forget theory. Run <code>git clone</code> and look at the folder. That tells you which one you have.</p>
<p><strong>Monorepo</strong>: one clone, everything inside:</p>
<pre><b>opsvo/</b> <i>← a single git repository</i>
apps/
api/
storefront/
admin-console/
packages/
ui/
shared-types/</pre>
<p><strong>Polyrepo</strong>: four separate clones:</p>
<pre><b>opsvo-api/</b> <i>← repo 1</i>
<b>opsvo-storefront/</b> <i>← repo 2</i>
<b>opsvo-admin/</b> <i>← repo 3</i>
<b>opsvo-ui/</b> <i>← repo 4, published to npm</i></pre>
<p>That is the whole distinction. Notice you cannot tell from either picture how many servers run in production, because the repo layout does not decide that.</p>
</div>
<div class="lv-pane" role="tabpanel">
<p class="lvl-note">Level 2: the workspace wiring that makes a monorepo bearable.</p>
<p>In a monorepo, apps reference shared packages by name, and the workspace tool links them on disk instead of downloading from a registry. No version numbers, no publishing.</p>
<pre><i>// pnpm-workspace.yaml</i>
packages:
- 'apps/*'
- 'packages/*'
<i>// apps/api/package.json</i>
{
"dependencies": {
<u>"@opsvo/shared-types": "workspace:*"</u> <i>← linked locally, not downloaded</i>
}
}</pre>
<p>The payoff is the atomic commit. Change a type and its consumer together, and CI verifies the whole thing at once:</p>
<pre>$ git commit -m "add currency to Order"
<em>modified: packages/shared-types/order.ts</em>
<em>modified: apps/api/src/orders/create.ts</em>
<em>modified: apps/storefront/src/cart.tsx</em>
<i># one commit. one review. never half-migrated.</i></pre>
<p>In polyrepo that same change is three pull requests plus a version bump, and there is a window where the published package and its consumers disagree.</p>
</div>
<div class="lv-pane" role="tabpanel">
<p class="lvl-note">Level 3: what breaks at scale, and how teams fix it.</p>
<p>Monorepos fail on CI time. The fix is an affected graph: the tool computes which projects depend on the changed files and only builds those.</p>
<pre>$ npx nx affected --target=test --base=origin/main
<em>✓ shared-types (changed)</em>
<em>✓ api (depends on shared-types)</em>
<i>- storefront skipped, not affected</i>
<i>- admin-console skipped, not affected</i>
<i># 2 of 4 projects built. this is why the tooling exists.</i></pre>
<p>Three more things serious monorepos add:</p>
<ul>
<li><strong>Remote build cache</strong>: a task with identical inputs is never rebuilt, on any machine. CI restores the previous output instead.</li>
<li><strong>Module boundary rules</strong>: a lint rule that fails the build when a project imports something it should not. This is what stops a monorepo becoming a mud ball.</li>
<li><strong>Sparse checkout</strong>: at Google scale nobody clones everything; you materialise only the paths you touch.</li>
</ul>
<p>Here is a boundary rule, which matters far more than it looks. It is the same idea that will make modular monoliths work later:</p>
<pre><i>// .eslintrc: nx enforce-module-boundaries</i>
{
"sourceTag": <b>"scope:storefront"</b>,
"onlyDependOnLibsWithTags": [<b>"scope:shared"</b>]
}
<i>// storefront importing from admin now fails CI:</i>
<s>import { AuditLog } from '@opsvo/admin/internals'</s>
<s>✗ scope:storefront cannot depend on scope:admin</s></pre>
<p>The polyrepo failure mode is the mirror image: no CI problem, but a "diamond dependency" mess where two services need incompatible versions of the same shared library, and nobody can upgrade without coordinating a release train.</p>
</div>
</div>
<div class="recap"><h4>Jargon used in this section</h4><dl>
<dt>monorepo</dt><dd>One version-control repository holding many projects. Storage only: it says nothing about deployment.</dd>
<dt>polyrepo / multi-repo</dt><dd>One repository per project. Shared code becomes a published, versioned package.</dd>
<dt>atomic commit</dt><dd>A single commit that changes several projects together, so the codebase is never half-updated.</dd>
<dt>affected graph</dt><dd>The tool's calculation of which projects depend on your changed files, so CI builds only those.</dd>
<dt>diamond dependency</dt><dd>Two things you depend on need conflicting versions of the same third thing. The classic polyrepo pain.</dd>
</dl></div>
<div class="note"><span class="lbl">Common mix-up</span><p>Monorepo does not mean monolith. Monorepo is a storage choice; monolith is a deployment choice. If you remember nothing else from this section, remember that they share a prefix and nothing else.</p></div>
</section>
<section id="monolith" class="x2">
<div class="sechead"><span class="tag">Axis 2 · what you deploy</span></div>
<h2>Modular monolith</h2>
<p class="sub">One deployable thing. Many enforced compartments inside it.</p>
<div class="wwh"><div class="wwh-h"><span class="wwh-n">Why</span><h3>Why not just a plain monolith</h3></div>
<p>A plain monolith works fine until it does not. What kills it is not size, it is entanglement: the orders code reads the inventory tables directly, the billing code imports a helper from shipping, and after two years every change touches everything. Nobody can reason about one part in isolation, and nobody can extract one part later.</p>
<p>A modular monolith keeps the operational simplicity of one deployment while refusing that entanglement. It buys you the ability to split later without committing to distribution now.</p></div>
<figure class="demo" id="tangle">
<div class="demo-head">
<span class="demo-lbl">Interactive: change one thing</span>
<div class="demo-toggle" role="group" aria-label="Architecture mode">
<button data-mode="tangled" aria-pressed="true">Tangled monolith</button>
<button data-mode="modular" aria-pressed="false">Modular monolith</button>
</div>
</div>
<div class="demo-stage">
<svg viewBox="0 0 640 250" role="img" aria-label="Five modules of an online store and the dependencies between them">
<g id="dEdges"></g><g id="dNodes"></g>
</svg>
</div>
<div class="demo-scen" role="group" aria-label="Pick a change">
<button data-s="0" aria-pressed="true">Rename a column in inventory</button>
<button data-s="1">Swap the payment provider</button>
<button data-s="2">Move inventory to its own service</button>
</div>
<div class="demo-out">
<div class="dstat"><b id="dA">4</b><span>modules broken</span></div>
<div class="dstat"><b id="dB">11</b><span>files to change</span></div>
<div class="dstat"><b id="dC">Days</b><span>realistic effort</span></div>
</div>
<div class="demo-body">
<pre id="dCode"></pre>
<p class="dverdict" id="dVerdict"></p>
</div>
<figcaption style="padding:0 17px 15px;font-family:var(--mono);font-size:11px;color:var(--dim2);line-height:1.55">An online store with five modules. Flip the toggle and pick a change to see how far it travels. File counts are illustrative, the failure shape is not.</figcaption>
</figure>
<div class="wwh"><div class="wwh-h"><span class="wwh-n">What it does</span><h3>What it actually is</h3></div>
<p>One process, one deployable artifact. Inside it, modules that each own their data and expose an explicit, narrow interface. Modules talk through those interfaces or through in-process events, never by reaching into each other's tables or internal files.</p>
<p>Everything ships together. Fix a checkout bug, rebuild the one image, redeploy. Checkout, orders and catalog all restart, because they are the same process.</p></div>
<div class="wwh"><div class="wwh-h"><span class="wwh-n">How</span><h3>How you enforce the boundaries</h3></div>
<p>This is the whole discipline, and it is mechanical. Folder structure alone always decays, so you need three things:</p>
<ul>
<li>A single public entry file per module. Everything else in that folder is private.</li>
<li>A lint rule in CI that fails on deep imports past that entry file.</li>
<li>Data ownership: each module's tables are touched only by that module. No cross-module joins.</li>
</ul></div>
<div class="eli"><button class="eli-t">Explain like I'm five <span class="chev">›</span></button><div class="eli-b">
<p>Think of a big house with rooms. Everyone lives in the same house: one front door, one address.</p>
<p>A <strong>messy monolith</strong> is a house with no walls. Someone's socks end up in the kitchen. Change one thing and you trip over something in another room.</p>
<p>A <strong>modular monolith</strong> is the same house, but each room has a door, and there is a rule: you knock, you do not climb through the window. You also do not take things out of someone's cupboard: you ask them and they hand it to you.</p>
<p>It is still one house. But because everyone knocks, you could one day move a room into its own little cottage without the whole house falling down.</p>
</div></div>
<div class="lv" data-lv>
<div class="lv-bar" role="tablist">
<button role="tab" aria-selected="true">Basic</button>
<button role="tab" aria-selected="false">Intermediate</button>
<button role="tab" aria-selected="false">Advanced</button>
</div>
<div class="lv-pane on" role="tabpanel">
<p class="lvl-note">Level 1: the folder shape and the one rule.</p>
<pre>src/
modules/
<b>orders/</b>
index.ts <i>← the ONLY file others may import</i>
service.ts <i>← private</i>
repository.ts <i>← private</i>
<b>inventory/</b>
index.ts
service.ts</pre>
<p>Legal:</p>
<pre><em>import { placeOrder } from '../orders'</em> <i>✓ goes through index.ts</i></pre>
<p>Illegal:</p>
<pre><s>import { OrderRepo } from '../orders/repository'</s> <i>✗ reaches inside</i></pre>
<p>That is it. One public door per module, and everything else is behind it.</p>
</div>
<div class="lv-pane" role="tabpanel">
<p class="lvl-note">Level 2: making the rule real instead of aspirational.</p>
<p>A rule nobody checks is a suggestion. Put it in CI with dependency-cruiser or an ESLint boundaries plugin:</p>
<pre><i>// .dependency-cruiser.js</i>
{
name: <b>'no-deep-module-imports'</b>,
severity: 'error',
from: { path: '^src/modules/([^/]+)/' },
to: {
path: '^src/modules/([^/]+)/.+',
pathNot: '^src/modules/$1/', <i>own folder is fine</i>
<u>pathNot: '^src/modules/[^/]+/index.ts$'</u> <i>public door is fine</i>
}
}</pre>
<p>Then the data half, which people skip and later regret. Each module gets its own schema, so a cross-module join is not merely discouraged: it is visible in the SQL:</p>
<pre><i>// prisma/schema.prisma</i>
model Order {
id String @id
<em>@@schema("orders")</em>
}
model StockItem {
id String @id
<em>@@schema("inventory")</em>
}
<i>-- this now looks obviously wrong in a code review:</i>
<s>SELECT * FROM orders.order o JOIN inventory.stock_item s ...</s></pre>
<p>Modules ask each other in code, not in SQL:</p>
<pre><i>// orders/service.ts</i>
<em>const ok = await inventory.reserve(items)</em> <i>← a normal function call</i></pre>
</div>
<div class="lv-pane" role="tabpanel">
<p class="lvl-note">Level 3: designing for the extraction you may never do.</p>
<p>The advanced move is to make every cross-module call look exactly like a network call would, without being one. Define the contract as an interface, inject the implementation:</p>
<pre><i>// orders/ports.ts: orders declares what it NEEDS</i>
export interface InventoryPort {
reserve(items: LineItem[]): Promise<Reservation>
}
<i>// wiring/container.ts, today it is in-process</i>
const inventoryPort: InventoryPort = <em>new LocalInventoryAdapter(inventoryService)</em>
<i>// tomorrow, if you ever extract it: one line changes</i>
const inventoryPort: InventoryPort = <u>new HttpInventoryAdapter('https://inventory.internal')</u></pre>
<p>Because the call was always async and always went through an interface, extraction becomes configuration rather than a rewrite. But note carefully what that one line silently introduces:</p>
<ul>
<li>The call can now time out, and you will not know whether the reservation happened.</li>
<li>You need a retry policy, and therefore an <strong>idempotency key</strong> so a retry does not double-reserve.</li>
<li>You lose the shared database transaction that used to make order-plus-reservation atomic.</li>
</ul>
<p>Which is why the honest advice is: build the seam, then do not use it until something forces you. Keep a checklist of what a module must satisfy before it earns extraction: owns its tables, no shared transactions with others, communicates only through its port, has its own metrics.</p>
<p>The transactional point is the one that bites hardest. In-process you get this for free:</p>
<pre>await db.$transaction(async (tx) => {
await orders.create(tx, order)
await inventory.reserve(tx, items) <i>both commit or neither does</i>
})</pre>
<p>Split them across services and that guarantee is gone. You replace it with a saga: reserve first, create the order, and if the order fails, issue a compensating action that releases the reservation. That is real work, and it is the true price of the split.</p>
</div>
</div>
<div class="recap"><h4>Jargon used in this section</h4><dl>
<dt>modular monolith</dt><dd>One deployable process containing modules with enforced boundaries and their own data.</dd>
<dt>deployable artifact</dt><dd>The single built thing you ship: a container image, a jar, a zip. Counting these is how you count deploy units.</dd>
<dt>module boundary</dt><dd>The rule that code outside a module may only use its public entry point, checked by CI rather than trusted.</dd>
<dt>idempotency key</dt><dd>A unique ID sent with a request so that retrying it does not perform the action twice.</dd>
<dt>saga</dt><dd>A sequence of local steps with compensating undo actions, used to replace a transaction once you no longer have one.</dd>
<dt>compensating action</dt><dd>The undo step in a saga: release the reservation, refund the charge.</dd>
</dl></div>
</section>
<section id="distributed" class="x2">
<div class="sechead"><span class="tag">Axis 2 · what you deploy</span></div>
<h2>Distributed architecture</h2>
<p class="sub">Not a separate option: the umbrella name for the whole right side of axis 2.</p>
<div class="wwh"><div class="wwh-h"><span class="wwh-n">Why</span><h3>Why anyone accepts the pain</h3></div>
<p>Distributed architecture is not a maturity level you graduate to. It is a trade: you accept an entire category of new failures in exchange for something specific. If you cannot name the something, do not trade.</p>
<p>The reasons that come up most often:</p>
<ul>
<li><strong>Divergent scaling</strong>: image processing needs 16 GB and a GPU, your API needs 512 MB. Scaling them together burns money.</li>
<li><strong>Divergent runtime</strong>: the ML piece is Python, the rest is Node.</li>
<li><strong>Team release independence</strong>: two teams whose deploy cadences genuinely conflict, after you already tried one pipeline and it failed.</li>
<li><strong>Isolation</strong>: a regulated or untrusted workload that must not share a process.</li>
<li><strong>Availability asymmetry</strong>: checkout must stay up while reporting is being redeployed.</li>
</ul>
<p>That list is the common ground, not a closed set. Others are real but rarer: data residency rules forcing EU data onto EU infrastructure, systems you acquired rather than built, a boundary around code you do not control, and strangler-fig migrations where the split is a transitional state rather than the target.</p>
<p>Notice what is missing from all of them: "it scales better", "it is cleaner", "it is what real companies do". A modular monolith scales horizontally perfectly well: you run more copies of the same container. Scaling and splitting are different things.</p></div>
<div class="wwh"><div class="wwh-h"><span class="wwh-n">What it does</span><h3>What the word actually covers</h3></div>
<p>A distributed architecture is any system whose parts run in separate processes and communicate over a network. Microservices are distributed. Runtime micro-frontends are distributed. A Kafka-based event system is distributed. A modular monolith is not, no matter how many modules it has.</p>
<p>The moment a call crosses a process boundary, it stops being a function call and becomes a message that may vanish.</p></div>
<figure class="dg">
<svg viewBox="0 0 700 300" role="img" aria-label="In-process call compared with a network call">
<defs><marker id="ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M1 1L9 5L1 9" fill="none" stroke="var(--dim2)" stroke-width="1.6"/></marker></defs>
<text class="sv-s" x="20" y="14">IN ONE PROCESS</text>
<rect class="sv-box" x="20" y="30" width="280" height="42" rx="7"/><text class="sv-t" x="160" y="56" text-anchor="middle">Order module</text>
<line class="sv-l" x1="160" y1="72" x2="160" y2="94" marker-end="url(#ah)"/>
<rect class="sv-box" style="stroke:var(--a3)" x="20" y="98" width="280" height="42" rx="7"/><text class="sv-t" x="160" y="124" text-anchor="middle">direct function call</text>
<line class="sv-l" x1="160" y1="140" x2="160" y2="162" marker-end="url(#ah)"/>
<rect class="sv-box" x="20" y="166" width="280" height="42" rx="7"/><text class="sv-t" x="160" y="192" text-anchor="middle">Payment module</text>
<text class="sv-s" x="160" y="232" text-anchor="middle">one failure mode: it throws.</text>
<text class="sv-s" x="160" y="250" text-anchor="middle">same memory, same clock.</text>
<text class="sv-s" x="400" y="14">ACROSS THE NETWORK</text>
<rect class="sv-box" x="400" y="30" width="280" height="42" rx="7"/><text class="sv-t" x="540" y="56" text-anchor="middle">Order service</text>
<line class="sv-l" x1="540" y1="72" x2="540" y2="94" marker-end="url(#ah)"/>
<rect class="sv-box" style="stroke:var(--warn)" x="400" y="98" width="280" height="42" rx="7"/><text class="sv-t" x="540" y="124" text-anchor="middle">call over a network</text>
<line class="sv-l" x1="540" y1="140" x2="540" y2="162" marker-end="url(#ah)"/>
<rect class="sv-box" x="400" y="166" width="280" height="42" rx="7"/><text class="sv-t" x="540" y="192" text-anchor="middle">Payment service</text>
<text class="sv-s" x="540" y="232" text-anchor="middle">timeouts, retries, partial writes,</text>
<text class="sv-s" x="540" y="250" text-anchor="middle">version skew, network splits.</text>
</svg>
<figcaption>Only the middle box changed. That is the entire difference between a monolith and a distributed system.</figcaption>
</figure>
<div class="eli"><button class="eli-t">Explain like I'm five <span class="chev">›</span></button><div class="eli-b">
<p>You want a cookie. Your sister is in the same room. You ask her, she hands you a cookie, or she says no. Two outcomes. Easy.</p>
<p>Now your sister is at her friend's house and you send her a letter asking for a cookie. You wait. Nothing comes back.</p>
<p>Did the letter get lost? Did she get it and the cookie is still in the post? Did she never read it? <strong>You cannot tell.</strong> And if you send a second letter, you might end up with two cookies, which sounds good until it is two charges on someone's credit card.</p>
<p>That "I do not know what happened" feeling is the whole difficulty of distributed systems. Everything hard about them (retries, timeouts, undo steps) exists to deal with that one feeling.</p>
</div></div>
<div class="lv" data-lv>
<div class="lv-bar" role="tablist">
<button role="tab" aria-selected="true">Basic</button>
<button role="tab" aria-selected="false">Intermediate</button>
<button role="tab" aria-selected="false">Advanced</button>
</div>
<div class="lv-pane on" role="tabpanel">
<p class="lvl-note">Level 1: count your deploy units.</p>
<p>Ask one question: <em>I fixed a bug in checkout. What must I redeploy, and what keeps running untouched?</em></p>
<pre><b>Modular monolith</b>
rebuild 1 image → redeploy → <i>everything restarts together</i>
deploy units: <em>1</em> → not distributed
<b>Microservices</b>
rebuild checkout-service → redeploy it alone
orders-service <i>never restarts and does not notice</i>
deploy units: <em>many</em> → distributed</pre>
<p>Same code split, wildly different operational reality. The word "distributed" just means the second picture.</p>
</div>
<div class="lv-pane" role="tabpanel">
<p class="lvl-note">Level 2: the timeout that has no answer.</p>
<p>Here is the failure that does not exist in a monolith:</p>
<pre>try {
await paymentService.charge(orderId, amount) <i>HTTP call</i>
} catch (e) {
<s>// e.code === 'ETIMEDOUT'</s>
<s>// Did the customer get charged? UNKNOWABLE from here.</s>
}</pre>
<p>You cannot fix this by retrying blindly, because a retry might charge twice. You fix it by making the operation idempotent: the caller sends a key, the callee remembers it:</p>
<pre><i>// caller: same key on every retry of the same logical charge</i>
await payments.charge({
<u>idempotencyKey: `order-${orderId}-charge`</u>,
amount
})
<i>// callee: seen this key? return the old result, do nothing new</i>
const prior = await db.charges.findUnique({ where: { key } })
<em>if (prior) return prior.result</em></pre>
<p>Every distributed system ends up building this. It is not optional, and it is pure overhead you did not need in-process.</p>
</div>
<div class="lv-pane" role="tabpanel">
<p class="lvl-note">Level 3: the tax bill in full.</p>
<p>Once you are distributed, these stop being nice-to-haves:</p>
<ul>
<li><strong>Correlation IDs and distributed tracing</strong>: a stack trace no longer spans the request. You need OpenTelemetry to reassemble one user action from five services' logs.</li>
<li><strong>Circuit breakers and timeouts everywhere</strong>: a slow dependency will otherwise exhaust your connection pool and take you down with it.</li>
<li><strong>Backpressure</strong>: a fast producer will drown a slow consumer unless something pushes back.</li>
<li><strong>Contract versioning</strong>: you cannot deploy both sides at once, so every change must be backward compatible for at least one release. Expand, migrate, contract.</li>
<li><strong>Eventual consistency</strong>: different services briefly disagree about the truth, and the UI has to be honest about that.</li>
</ul>
<p>The backward-compatible rollout is the one people underestimate. You cannot rename a field:</p>
<pre><i>// ✗ deploys are not simultaneous: this breaks in the gap</i>
<s>- { total: number }</s>
<s>+ { totalAmount: number }</s>
<i>// ✓ expand → migrate → contract, over three releases</i>
release 1: <em>{ total: number, totalAmount: number }</em> <i>write both</i>
release 2: <i>all consumers read totalAmount</i>
release 3: <em>{ totalAmount: number }</em> <i>drop the old field</i></pre>
<p>And the deepest one: you cannot have consistency, availability and partition tolerance all at once. Partitions happen whether you like it or not, so every distributed design is really choosing between staying consistent and staying available when the network splits. In a monolith that question never arises.</p>
</div>
</div>
<div class="recap"><h4>Jargon used in this section</h4><dl>
<dt>distributed architecture</dt><dd>Any system whose parts run in separate processes talking over a network. The umbrella term.</dd>
<dt>microservices</dt><dd>The backend form of distributed architecture: many independently deployed services. Independent deployability is the defining property, not size.</dd>
<dt>eventual consistency</dt><dd>Parts of the system are briefly out of sync and converge later, rather than all agreeing instantly.</dd>
<dt>circuit breaker</dt><dd>A wrapper that stops calling a failing dependency for a while, so its failure does not exhaust your resources.</dd>
<dt>backpressure</dt><dd>A signal from a slow consumer telling the producer to slow down instead of dropping work.</dd>
<dt>distributed tracing</dt><dd>Tagging a request with an ID that travels across services so you can reconstruct one user action from many logs.</dd>
<dt>blast radius</dt><dd>How much of the system a single failure or deploy can damage.</dd>
</dl></div>
<div class="note"><span class="lbl">Common mix-up</span><p>Distributed is about process boundaries, not about code organisation. You can have a beautifully modular monolith that is not distributed, and a hideous ball of mud spread across forty services that is. Splitting does not clean anything up: it just adds a network between your existing mess.</p></div>
</section>
<section id="mfe-build" class="x2">
<div class="sechead"><span class="tag">Axis 2 · what you deploy</span></div>
<h2>Build-time micro-frontends</h2>
<p class="sub">Code ownership without deploy independence. Usually the right answer.</p>
<div class="wwh"><div class="wwh-h"><span class="wwh-n">Why</span><h3>Why split a frontend at all</h3></div>
<p>Because several teams working in one React app step on each other: shared routing, shared state, merge conflicts in the same files, and a review queue where nobody knows who owns what. Splitting the frontend into owned pieces fixes the ownership problem.</p>
<p>The question is whether you also need each piece to <em>deploy</em> on its own. Build-time integration says no, and that "no" saves you an enormous amount of grief.</p></div>
<div class="wwh"><div class="wwh-h"><span class="wwh-n">What it does</span><h3>What actually happens</h3></div>
<p>Each so-called micro-frontend is a package in your workspace. The shell app imports them, and the bundler compiles everything into one output. The browser downloads a single application.</p>
<p>So the cart team owns their code, gets their own folder, their own reviews, their own tests. But when they fix a bug, someone must rebuild and redeploy the shell. <strong>They cannot ship without the shell shipping.</strong></p>
<p>Which is the honest description: this is a modular monolith written in React. That is not an insult: it is the correct default.</p></div>
<div class="wwh"><div class="wwh-h"><span class="wwh-n">How</span><h3>How it is wired</h3></div>
<p>Workspace packages, ordinary imports, one build. There is no special runtime machinery at all: that is precisely the point.</p></div>
<div class="eli"><button class="eli-t">Explain like I'm five <span class="chev">›</span></button><div class="eli-b">
<p>Your class is making one big poster for the science fair.</p>
<p>Each group draws their own piece on their own sheet of paper: the volcano group, the planets group, the dinosaur group. Everyone owns their sheet and nobody scribbles on anyone else's.</p>
<p>But at the end, the teacher <strong>glues all the sheets onto one big board</strong>, and that board goes to the fair.</p>
<p>So if the dinosaur group wants to fix a wonky tail after the board is already glued, the whole board has to come back and be re-glued. Everyone's work goes back on the wall together, every time.</p>
<p>That is build-time. Separate sheets, one board.</p>
</div></div>
<div class="lv" data-lv>
<div class="lv-bar" role="tablist">
<button role="tab" aria-selected="true">Basic</button>
<button role="tab" aria-selected="false">Intermediate</button>
<button role="tab" aria-selected="false">Advanced</button>
</div>
<div class="lv-pane on" role="tabpanel">
<p class="lvl-note">Level 1: what the browser downloads.</p>
<p>This is the fastest way to tell build-time from runtime. Open the network tab.</p>
<pre>GET /assets/<b>main-a3f2b1.js</b>
<i>↑ shell + cart + search + checkout, all in one file</i>
<i># one request. one file. one deploy produced it.</i></pre>
<p>And the code is completely ordinary:</p>
<pre><i>// apps/shell/src/App.tsx</i>
import { CartWidget } from <em>'@opsvo/cart'</em>
import { SearchBar } from <em>'@opsvo/search'</em>
<i>// just imports. nothing clever happening.</i></pre>
</div>
<div class="lv-pane" role="tabpanel">
<p class="lvl-note">Level 2: ownership without a network, plus lazy loading.</p>
<p>You get real boundaries from CODEOWNERS and lint rules, exactly like the modular monolith on the backend:</p>
<pre><i># .github/CODEOWNERS</i>
/packages/cart/ @opsvo/team-cart
/packages/search/ @opsvo/team-search
/apps/shell/ @opsvo/team-platform</pre>
<p>People often confuse code splitting with micro-frontends. It is not the same thing. Lazy loading splits the <em>bundle</em>, but it is still one deploy:</p>
<pre>const Checkout = React.lazy(() => import(<em>'@opsvo/checkout'</em>))
<i>// browser now fetches:</i>
GET /assets/main-a3f2b1.js
GET /assets/<b>checkout-7d21c9.js</b> <i>← separate FILE...</i>
<i>// ...but produced by the SAME build, from the SAME deploy.</i>
<i>// still one deploy unit. still not distributed.</i></pre>
<p>Deploy units are counted by "how many things can I ship separately", not by "how many files does the browser fetch".</p>
</div>
<div class="lv-pane" role="tabpanel">
<p class="lvl-note">Level 3: scaling this to many teams before reaching for runtime.</p>
<p>Most teams that think they need runtime micro-frontends actually need a better build-time setup. Three techniques buy you a long runway:</p>
<ul>
<li><strong>Affected builds</strong>: CI only rebuilds and retests the packages a change touched, so a shared deploy is still fast.</li>
<li><strong>Contract-tested boundaries</strong>: each package exports a typed public API, and a type test fails CI if it changes incompatibly.</li>
<li><strong>Trunk-based deploys with feature flags</strong>: teams merge continuously and control their own release timing by flag, not by deploy. This is the key move: it gives you release independence without deploy independence.</li>
</ul>
<p>That last one deserves the code, because it solves the actual problem people buy Module Federation for:</p>
<pre><i>// the cart team ships code every day, dark, behind a flag</i>
export function CartWidget() {
const flags = useFlags()
return flags.<u>cartRedesign</u>
? <em><CartV2 /></em>
: <CartV1 />
}
<i>// release = flip a flag in the dashboard, no deploy at all.</i>
<i>// rollback = flip it back, in seconds, not a revert + rebuild.</i></pre>
<p>Compare the rollback stories honestly. Flag flip: seconds, no build. Runtime micro-frontend rollback: republish the previous remote bundle and hope no other remote depended on the new one. Feature flags are strictly better for most teams, and cost a fraction as much.</p>
</div>
</div>
<div class="recap"><h4>Jargon used in this section</h4><dl>
<dt>build-time micro-frontends</dt><dd>Frontend packages composed into one application by the bundler. One deploy unit.</dd>
<dt>shell / host app</dt><dd>The application that owns routing and layout and hosts the other pieces.</dd>
<dt>bundler</dt><dd>The tool (Vite, webpack, esbuild) that compiles your source and dependencies into files the browser can load.</dd>
<dt>code splitting</dt><dd>Producing several bundle files from one build so the browser loads them on demand. Still one deploy unit.</dd>
<dt>feature flag</dt><dd>A runtime switch that turns a feature on or off without deploying, giving release independence cheaply.</dd>
<dt>trunk-based development</dt><dd>Everyone merges to main continuously, keeping unfinished work behind flags rather than in long branches.</dd>
</dl></div>
</section>
<section id="mfe-run" class="x2">