A place to cache linked articles (think custom and personal wayback machine)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

преди 4 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859
  1. <!doctype html><!-- This is a valid HTML5 document. -->
  2. <!-- Screen readers, SEO, extensions and so on. -->
  3. <html lang=fr>
  4. <!-- Has to be within the first 1024 bytes, hence before the <title>
  5. See: https://www.w3.org/TR/2012/CR-html5-20121217/document-metadata.html#charset -->
  6. <meta charset=utf-8>
  7. <!-- Why no `X-UA-Compatible` meta: https://stackoverflow.com/a/6771584 -->
  8. <!-- The viewport meta is quite crowded and we are responsible for that.
  9. See: https://codepen.io/tigt/post/meta-viewport-for-2015 -->
  10. <meta name=viewport content="width=device-width,minimum-scale=1,initial-scale=1,shrink-to-fit=no">
  11. <!-- Required to make a valid HTML5 document. -->
  12. <title>One Concern, One File (archive) — David Larlet</title>
  13. <!-- Generated from https://realfavicongenerator.net/ such a mess. -->
  14. <link rel="apple-touch-icon" sizes="180x180" href="/static/david/icons/apple-touch-icon.png">
  15. <link rel="icon" type="image/png" sizes="32x32" href="/static/david/icons/favicon-32x32.png">
  16. <link rel="icon" type="image/png" sizes="16x16" href="/static/david/icons/favicon-16x16.png">
  17. <link rel="manifest" href="/manifest.json">
  18. <link rel="mask-icon" href="/static/david/icons/safari-pinned-tab.svg" color="#5bbad5">
  19. <link rel="shortcut icon" href="/static/david/icons/favicon.ico">
  20. <meta name="apple-mobile-web-app-title" content="David Larlet">
  21. <meta name="application-name" content="David Larlet">
  22. <meta name="msapplication-TileColor" content="#da532c">
  23. <meta name="msapplication-config" content="/static/david/icons/browserconfig.xml">
  24. <meta name="theme-color" content="#f0f0ea">
  25. <!-- That good ol' feed, subscribe :p. -->
  26. <link rel=alternate type="application/atom+xml" title=Feed href="/david/log/">
  27. <meta name="robots" content="noindex, nofollow">
  28. <meta content="origin-when-cross-origin" name="referrer">
  29. <!-- Canonical URL for SEO purposes -->
  30. <link rel="canonical" href="http://tech.pro/blog/6968/one-concern-one-file">
  31. <style>
  32. /* http://meyerweb.com/eric/tools/css/reset/ */
  33. html, body, div, span,
  34. h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  35. a, abbr, address, big, cite, code,
  36. del, dfn, em, img, ins,
  37. small, strike, strong, tt, var,
  38. dl, dt, dd, ol, ul, li,
  39. fieldset, form, label, legend,
  40. table, caption, tbody, tfoot, thead, tr, th, td,
  41. article, aside, canvas, details, embed,
  42. figure, figcaption, footer, header, hgroup,
  43. menu, nav, output, ruby, section, summary,
  44. time, mark, audio, video {
  45. margin: 0;
  46. padding: 0;
  47. border: 0;
  48. font-size: 100%;
  49. font: inherit;
  50. vertical-align: baseline;
  51. }
  52. /* HTML5 display-role reset for older browsers */
  53. article, aside, details, figcaption, figure,
  54. footer, header, hgroup, menu, nav, section { display: block; }
  55. body { line-height: 1; }
  56. blockquote, q { quotes: none; }
  57. blockquote:before, blockquote:after,
  58. q:before, q:after {
  59. content: '';
  60. content: none;
  61. }
  62. table {
  63. border-collapse: collapse;
  64. border-spacing: 0;
  65. }
  66. /* http://practicaltypography.com/equity.html */
  67. /* https://calendar.perfplanet.com/2016/no-font-face-bulletproof-syntax/ */
  68. /* https://www.filamentgroup.com/lab/js-web-fonts.html */
  69. @font-face {
  70. font-family: 'EquityTextB';
  71. src: url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff2') format('woff2'),
  72. url('/static/david/css/fonts/Equity-Text-B-Regular-webfont.woff') format('woff');
  73. font-weight: 300;
  74. font-style: normal;
  75. font-display: swap;
  76. }
  77. @font-face {
  78. font-family: 'EquityTextB';
  79. src: url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff2') format('woff2'),
  80. url('/static/david/css/fonts/Equity-Text-B-Italic-webfont.woff') format('woff');
  81. font-weight: 300;
  82. font-style: italic;
  83. font-display: swap;
  84. }
  85. @font-face {
  86. font-family: 'EquityTextB';
  87. src: url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff2') format('woff2'),
  88. url('/static/david/css/fonts/Equity-Text-B-Bold-webfont.woff') format('woff');
  89. font-weight: 700;
  90. font-style: normal;
  91. font-display: swap;
  92. }
  93. @font-face {
  94. font-family: 'ConcourseT3';
  95. src: url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff2') format('woff2'),
  96. url('/static/david/css/fonts/concourse_t3_regular-webfont-20190806.woff') format('woff');
  97. font-weight: 300;
  98. font-style: normal;
  99. font-display: swap;
  100. }
  101. /* http://practice.typekit.com/lesson/caring-about-opentype-features/ */
  102. body {
  103. /* http://www.cssfontstack.com/ Palatino 99% Win 86% Mac */
  104. font-family: "EquityTextB", Palatino, serif;
  105. background-color: #f0f0ea;
  106. color: #07486c;
  107. font-kerning: normal;
  108. -moz-osx-font-smoothing: grayscale;
  109. -webkit-font-smoothing: subpixel-antialiased;
  110. text-rendering: optimizeLegibility;
  111. font-variant-ligatures: common-ligatures contextual;
  112. font-feature-settings: "kern", "liga", "clig", "calt";
  113. }
  114. pre, code, kbd, samp, var, tt {
  115. font-family: 'TriplicateT4c', monospace;
  116. }
  117. em {
  118. font-style: italic;
  119. color: #323a45;
  120. }
  121. strong {
  122. font-weight: bold;
  123. color: black;
  124. }
  125. nav {
  126. background-color: #323a45;
  127. color: #f0f0ea;
  128. display: flex;
  129. justify-content: space-around;
  130. padding: 1rem .5rem;
  131. }
  132. nav:last-child {
  133. border-bottom: 1vh solid #2d7474;
  134. }
  135. nav a {
  136. color: #f0f0ea;
  137. }
  138. nav abbr {
  139. border-bottom: 1px dotted white;
  140. }
  141. h1 {
  142. border-top: 1vh solid #2d7474;
  143. border-bottom: .2vh dotted #2d7474;
  144. background-color: #e3e1e1;
  145. color: #323a45;
  146. text-align: center;
  147. padding: 5rem 0 4rem 0;
  148. width: 100%;
  149. font-family: 'ConcourseT3';
  150. display: flex;
  151. flex-direction: column;
  152. }
  153. h1.single {
  154. padding-bottom: 10rem;
  155. }
  156. h1 span {
  157. position: absolute;
  158. top: 1vh;
  159. left: 20%;
  160. line-height: 0;
  161. }
  162. h1 span a {
  163. line-height: 1.7;
  164. padding: 1rem 1.2rem .6rem 1.2rem;
  165. border-radius: 0 0 6% 6%;
  166. background: #2d7474;
  167. font-size: 1.3rem;
  168. color: white;
  169. text-decoration: none;
  170. }
  171. h2 {
  172. margin: 4rem 0 1rem;
  173. border-top: .2vh solid #2d7474;
  174. padding-top: 1vh;
  175. }
  176. h3 {
  177. text-align: center;
  178. margin: 3rem 0 .75em;
  179. }
  180. hr {
  181. height: .4rem;
  182. width: .4rem;
  183. border-radius: .4rem;
  184. background: #07486c;
  185. margin: 2.5rem auto;
  186. }
  187. time {
  188. display: bloc;
  189. margin-left: 0 !important;
  190. }
  191. ul, ol {
  192. margin: 2rem;
  193. }
  194. ul {
  195. list-style-type: square;
  196. }
  197. a {
  198. text-decoration-skip-ink: auto;
  199. text-decoration-thickness: 0.05em;
  200. text-underline-offset: 0.09em;
  201. }
  202. article {
  203. max-width: 50rem;
  204. display: flex;
  205. flex-direction: column;
  206. margin: 2rem auto;
  207. }
  208. article.single {
  209. border-top: .2vh dotted #2d7474;
  210. margin: -6rem auto 1rem auto;
  211. background: #f0f0ea;
  212. padding: 2rem;
  213. }
  214. article p:last-child {
  215. margin-bottom: 1rem;
  216. }
  217. p {
  218. padding: 0 .5rem;
  219. margin-left: 3rem;
  220. }
  221. p + p,
  222. figure + p {
  223. margin-top: 2rem;
  224. }
  225. blockquote {
  226. background-color: #e3e1e1;
  227. border-left: .5vw solid #2d7474;
  228. display: flex;
  229. flex-direction: column;
  230. align-items: center;
  231. padding: 1rem;
  232. margin: 1.5rem;
  233. }
  234. blockquote cite {
  235. font-style: italic;
  236. }
  237. blockquote p {
  238. margin-left: 0;
  239. }
  240. figure {
  241. border-top: .2vh solid #2d7474;
  242. background-color: #e3e1e1;
  243. text-align: center;
  244. padding: 1.5rem 0;
  245. margin: 1rem 0 0;
  246. font-size: 1.5rem;
  247. width: 100%;
  248. }
  249. figure img {
  250. max-width: 250px;
  251. max-height: 250px;
  252. border: .5vw solid #323a45;
  253. padding: 1px;
  254. }
  255. figcaption {
  256. padding: 1rem;
  257. line-height: 1.4;
  258. }
  259. aside {
  260. display: flex;
  261. flex-direction: column;
  262. background-color: #e3e1e1;
  263. padding: 1rem 0;
  264. border-bottom: .2vh solid #07486c;
  265. }
  266. aside p {
  267. max-width: 50rem;
  268. margin: 0 auto;
  269. }
  270. /* https://fvsch.com/code/css-locks/ */
  271. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  272. font-size: 1rem;
  273. line-height: calc( 1.5em + 0.2 * 1rem );
  274. }
  275. h1 {
  276. font-size: 1.9rem;
  277. line-height: calc( 1.2em + 0.2 * 1rem );
  278. }
  279. h2 {
  280. font-size: 1.6rem;
  281. line-height: calc( 1.3em + 0.2 * 1rem );
  282. }
  283. h3 {
  284. font-size: 1.35rem;
  285. line-height: calc( 1.4em + 0.2 * 1rem );
  286. }
  287. @media (min-width: 20em) {
  288. /* The (100vw - 20rem) / (50 - 20) part
  289. resolves to 0-1rem, depending on the
  290. viewport width (between 20em and 50em). */
  291. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  292. font-size: calc( 1rem + .6 * (100vw - 20rem) / (50 - 20) );
  293. line-height: calc( 1.5em + 0.2 * (100vw - 50rem) / (20 - 50) );
  294. margin-left: 0;
  295. }
  296. h1 {
  297. font-size: calc( 1.9rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  298. line-height: calc( 1.2em + 0.2 * (100vw - 50rem) / (20 - 50) );
  299. }
  300. h2 {
  301. font-size: calc( 1.5rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  302. line-height: calc( 1.3em + 0.2 * (100vw - 50rem) / (20 - 50) );
  303. }
  304. h3 {
  305. font-size: calc( 1.35rem + 1.5 * (100vw - 20rem) / (50 - 20) );
  306. line-height: calc( 1.4em + 0.2 * (100vw - 50rem) / (20 - 50) );
  307. }
  308. }
  309. @media (min-width: 50em) {
  310. /* The right part of the addition *must* be a
  311. rem value. In this example we *could* change
  312. the whole declaration to font-size:2.5rem,
  313. but if our baseline value was not expressed
  314. in rem we would have to use calc. */
  315. p, li, pre, code, kbd, samp, var, tt, time, details, figcaption {
  316. font-size: calc( 1rem + .6 * 1rem );
  317. line-height: 1.5em;
  318. }
  319. p, li, pre, details {
  320. margin-left: 3rem;
  321. }
  322. h1 {
  323. font-size: calc( 1.9rem + 1.5 * 1rem );
  324. line-height: 1.2em;
  325. }
  326. h2 {
  327. font-size: calc( 1.5rem + 1.5 * 1rem );
  328. line-height: 1.3em;
  329. }
  330. h3 {
  331. font-size: calc( 1.35rem + 1.5 * 1rem );
  332. line-height: 1.4em;
  333. }
  334. figure img {
  335. max-width: 500px;
  336. max-height: 500px;
  337. }
  338. }
  339. figure.unsquared {
  340. margin-bottom: 1.5rem;
  341. }
  342. figure.unsquared img {
  343. height: inherit;
  344. }
  345. @media print {
  346. body { font-size: 100%; }
  347. a:after { content: " (" attr(href) ")"; }
  348. a, a:link, a:visited, a:after {
  349. text-decoration: underline;
  350. text-shadow: none !important;
  351. background-image: none !important;
  352. background: white;
  353. color: black;
  354. }
  355. abbr[title] { border-bottom: 0; }
  356. abbr[title]:after { content: " (" attr(title) ")"; }
  357. img { page-break-inside: avoid; }
  358. @page { margin: 2cm .5cm; }
  359. h1, h2, h3 { page-break-after: avoid; }
  360. p3 { orphans: 3; widows: 3; }
  361. img {
  362. max-width: 250px !important;
  363. max-height: 250px !important;
  364. }
  365. nav, aside { display: none; }
  366. }
  367. ul.with_columns {
  368. column-count: 1;
  369. }
  370. @media (min-width: 20em) {
  371. ul.with_columns {
  372. column-count: 2;
  373. }
  374. }
  375. @media (min-width: 50em) {
  376. ul.with_columns {
  377. column-count: 3;
  378. }
  379. }
  380. ul.with_two_columns {
  381. column-count: 1;
  382. }
  383. @media (min-width: 20em) {
  384. ul.with_two_columns {
  385. column-count: 1;
  386. }
  387. }
  388. @media (min-width: 50em) {
  389. ul.with_two_columns {
  390. column-count: 2;
  391. }
  392. }
  393. .gallery {
  394. display: flex;
  395. flex-wrap: wrap;
  396. justify-content: space-around;
  397. }
  398. .gallery figure img {
  399. margin-left: 1rem;
  400. margin-right: 1rem;
  401. }
  402. .gallery figure figcaption {
  403. font-family: 'ConcourseT3'
  404. }
  405. footer {
  406. font-family: 'ConcourseT3';
  407. display: flex;
  408. flex-direction: column;
  409. border-top: 3px solid white;
  410. padding: 4rem 0;
  411. background-color: #07486c;
  412. color: white;
  413. }
  414. footer > * {
  415. max-width: 50rem;
  416. margin: 0 auto;
  417. }
  418. footer a {
  419. color: #f1c40f;
  420. }
  421. footer .avatar {
  422. width: 200px;
  423. height: 200px;
  424. border-radius: 50%;
  425. float: left;
  426. -webkit-shape-outside: circle();
  427. shape-outside: circle();
  428. margin-right: 2rem;
  429. padding: 2px 5px 5px 2px;
  430. background: white;
  431. border-left: 1px solid #f1c40f;
  432. border-top: 1px solid #f1c40f;
  433. border-right: 5px solid #f1c40f;
  434. border-bottom: 5px solid #f1c40f;
  435. }
  436. </style>
  437. <h1>
  438. <span><a id="jumper" href="#jumpto" title="Un peu perdu ?">?</a></span>
  439. One Concern, One File (archive)
  440. <time>Pour la pérennité des contenus liés. Non-indexé, retrait sur simple email.</time>
  441. </h1>
  442. <section>
  443. <article>
  444. <h3><a href="http://tech.pro/blog/6968/one-concern-one-file">Source originale du contenu</a></h3>
  445. <p>Most of us are aware of the notion of having a "Separation of Concerns". It's one of the holy truths of computer science. An unbreakable law: something we mustn't ever question, only follow.</p>
  446. <p>It's a good rule.</p>
  447. <p>There <em>are</em> some things about this rule that I think are up to interpretation though. It's not that I want to break the rule. I just want to explore it a little bit. That's what this post is about.</p>
  448. <h2 id="lets_talk_about_mvc">Let's talk about MVC</h2>
  449. <p>The MVC (Model View Controller) pattern is everywhere these days. I mean <em>everywhere</em>. Even if something isn't following a pattern even remotely close to MVC, library authors will try and somehow map elements of their library on to those three letters in hopes that someone might be fooled into thinking it's as well thought out as the one design pattern to rule them all: MVC.</p>
  450. <p>When people talk about "separation of concerns", the MVC pattern is often brought up. In this context, it often means separating things such as "business logic" from other things like "presentation logic". Hence, the "Model" stays separate from the "View". The "Controller", for the most part, determines where the other two come from, and how they interact.</p>
  451. <p>There is something that is particularly frustrating to me that comes about when using the MVC design pattern: the number of files I have to create to do <em>one</em> thing.</p>
  452. <p>Let's assume for a moment that I am building a web site and need to create a new entity called a "post". I crack open my editor and create a couple of new files. After a minute or so, my folder structure might look something like this:</p>
  453. <pre><code>- Server
  454. - Models
  455. Post.js
  456. - Views
  457. Post.ejs
  458. - Controllers
  459. Post.js
  460. </code></pre>
  461. <p><em>Note: For the sake of example, I'm assuming this is something like a node app with js files on the server, but it doesn't make any real difference for this post. Most environments will map to the same files, just different extensions.</em></p>
  462. <p>So we just created <strong>three</strong> files for our <strong>one</strong> new thing: the Post entity. </p>
  463. <p>Just to be clear, I don't have a problem with having a bunch of files in a project... Small, clean, concise, clear files are always a plus. What seems problematic though is that all of these files are <em>deeply</em> coupled together. If I want to make a change in one, I will likely have to make a change in the others. That leaves a bit of a bad taste in my mouth.</p>
  464. <p>According to MVC we are doing everything right... but we aren't done implementing our "post" yet...</p>
  465. <p>We will be generating HTML here, and we don't want to have a bunch of inline styles so we go ahead and create a <code>Styles/Post.css</code> file.</p>
  466. <p>We have to keep up with the trends and make our "post" fancy and interactive, so of course at some point we are going to plop some JS onto the page; let's go ahead and create a <code>Scripts/Post.js</code> file.</p>
  467. <p>We know that our controller code is supposed to be minimal, so we make sure and move that code into a repository and create a <code>Repositories/Post.js</code> file.</p>
  468. <p>So just to recap, our project may look a little closer to this now:</p>
  469. <pre><code>- Server
  470. - Repositories
  471. Post.js
  472. - Models
  473. Post.js
  474. - Views
  475. Post.ejs
  476. - Controllers
  477. Post.js
  478. - Client
  479. - Scripts
  480. Post.js
  481. - Styles
  482. Post.css
  483. </code></pre>
  484. <p>This is getting a little crazy, right? </p>
  485. <p>Now of course, this is just one entity/piece (one might say... "concern") of our project. We will have many more, and chances are each one might have <em>at least</em> this many files associated with it.</p>
  486. <h3 id="lets_make_a_quick_change">Let's make a quick change</h3>
  487. <p>Consider an example where we added on a field to our "post" entity, and need to update our application to display it. What kind of change would we need to make?</p>
  488. <ol>
  489. <li>Update the <code>Repositories/Post.js</code> file so that it included the <code>image</code> property in the data query.</li>
  490. <li>Update the <code>Models/Post.js</code> file so that the model included the <code>image</code> property.</li>
  491. <li>Update the <code>Views/Post.ejs</code> file so that it actually renders the included image now.</li>
  492. <li>Update the <code>Scripts/Post.js</code> file to have some cool hover effect over the image, because why not?</li>
  493. <li>Update the <code>Styles/Post.css</code> file to include some new basic styling for the image.</li>
  494. </ol>
  495. <p>Whoa. We just had to update almost every file in our project! The controller, assuming it was properly designed, was the one thing we didn't really have to change. </p>
  496. <p>That's kind of excessive, isn't it? I mean, what happened to "separation of concerns"? Was adding our "image" property really such a huge refactor that it required us to change every file in our project?!</p>
  497. <h2 id="are_we_separating_by_the_wrong_concerns">Are we separating by the wrong concerns?</h2>
  498. <p>I have often heard some people remark about how brilliantly the MVC pattern worked for them, and that they had such good separation of concerns that they once had to completely change their underlying data store technology and only had to change one file (or just a few files, or something like that).</p>
  499. <p>That's pretty cool, and no one is arguing that that wouldn't be a big win for that scenario...</p>
  500. <p>However, which of the following scenarios happens more often:</p>
  501. <p><img src="http://tpstatic.com/img/usermedia/-2YBoUduq0aLXbRU_I5O7g/w645.png" alt="enter image description here"/></p>
  502. <p>If your answer is the latter, you should run far, far away from that job and never look back.</p>
  503. <p>The former, on the other hand, probably happens to you like... <em>many</em> times a day, right? Maybe not in true Bill Lumbergh style, but bottom line: tweaking things is the norm. Drastic, sweeping refactors are not.</p>
  504. <p>We should optimize the ease with which we can make common everyday feature changes, since that's what we as developers will be doing every day. It's not a perfect metric, but the lower we can get to the average number of files needing to be changed by implementing a new feature or tweak, the better off we probably are.</p>
  505. <h2 id="what_about_flux">What about Flux?</h2>
  506. <p>This problem isn't unique to MVC. For instance, the Flux design pattern that is being pushed a lot in the React community has similar symptoms. In a flux application, I might have a file structures like the following:</p>
  507. <pre><code>- Server
  508. - API
  509. Post.js
  510. - Client
  511. - Actions
  512. PostActions.js
  513. - Stores
  514. PostStore.js
  515. - Components
  516. Post.js
  517. - Styles
  518. Post.css
  519. </code></pre>
  520. <p>In this scenario, I still have many files across a single entity. Implementing a single feature will often lead to me needing 4-5 different files open... I might add an action, subscribe to it in my store, update my component's render, add some CSS, and finish it all off with an API call... each in different files. I think on average it may be better than our MVC example, but it's still not perfect.</p>
  521. <h2 id="how_can_we_make_it_better">How can we make it better?</h2>
  522. <p>Let's pretend for a moment that you buy into this whole "just one file" idea, or at least you are interested enough that you've read this far. Well, what's the next step then?</p>
  523. <h2 id="step_1:_components">Step 1: Components</h2>
  524. <p>First, we stop writing HTML. Let's write JSX instead. It looks almost identical, but has magical powers that HTML doesn't have (hint: JavaScript). Components are easily separable into composable containers that are smart instead of dumb (like partial string-based templates are).</p>
  525. <p>Most importantly, this rids us entirely of the need of models and view models. Our models are now all of a sudden just our actual data, and our view models are just our view. There's no implicit coupling. Logic is just logic, and it's right there in plain sight. And we have all of the composable powers of JavaScript at our fingertips.</p>
  526. <p>Result: Our <code>Scripts/Post.js</code> file and our <code>Views/Post.ejs</code> file have now become one.</p>
  527. <h2 id="step_2:_css_in_your_js">Step 2: CSS in your JS</h2>
  528. <p>If you haven't looked at the <a href="https://speakerdeck.com/vjeux/react-css-in-js">CSS in JS presentation by Christopher Chedeau</a> (@vjeaux) at Facebook, you should take some time to look at it. There is a whole set of problems that CSS has that are difficult to scale, and using JavaScript with inline styles solves a lot of them. To get around these failures, we have some conventions like the BEM syntax, but in the end it's just a bandaid over a huge gaping flesh wound causing severe internal bleeding.</p>
  529. <p>Bringing our styling into JS actually fixes a lot (if not all) of these issues. And we don't actually have to change our habits that much.</p>
  530. <p>For instance, the way we do things like now might look kinda like this:</p>
  531. <pre><code>// FooComponent.css
  532. .foo-component {
  533. padding: 10px;
  534. background-color: red;
  535. }
  536. .foo-component-inner {
  537. width: 80px;
  538. border: 1px solid #000;
  539. }
  540. // FooComponent.js
  541. module.exports = React.createClass({
  542. render() {
  543. return (
  544. &lt;div class="foo-component"&gt;
  545. Foo Component
  546. &lt;div class="foo-component-inner"&gt;
  547. Inner
  548. &lt;/div&gt;
  549. &lt;/div&gt;
  550. );
  551. }
  552. });
  553. </code></pre>
  554. <p>Embracing component styling from within JS, we might instead have something that looks like this:</p>
  555. <pre><code>// FooComponent.js
  556. var styles = {
  557. outer: {
  558. padding: 10,
  559. backgroundColor: 'red'
  560. },
  561. inner: {
  562. width: 80,
  563. border: '1px solid #000'
  564. }
  565. };
  566. module.exports = React.createClass({
  567. render() {
  568. return (
  569. &lt;div style={styles.outer}&gt;
  570. Foo Component
  571. &lt;div style={styles.inner}&gt;
  572. Inner
  573. &lt;/div&gt;
  574. &lt;/div&gt;
  575. );
  576. }
  577. });
  578. </code></pre>
  579. <p>If you've been a web developer for a long time, you might first see this and cringe—but try to avoid your gut reaction for a minute and look at this objectively. Although there are still valid concerns for bringing all styles into JS-land, this new approach gives us several advantages.</p>
  580. <ol>
  581. <li>Scope: We know, with absolute certainty, that these styles aren't used elsewhere</li>
  582. <li>Semantically speaking, this actually reads more clearly (ie, it makes more sense for styles to go into a <code>style</code> property, rather than a <code>class</code> property).</li>
  583. <li>Now that we are in JS-land, we can create/mix/compute styles with the full power of javascript behind us!</li>
  584. <li>Removed global </li>
  585. <li>We've eliminated a file! No mare needing to switch between these two coupled files</li>
  586. </ol>
  587. <h2 id="step_3:_data_retrieval_in_your_view">Step 3: Data Retrieval in your View</h2>
  588. <p>Somewhere along the line, the world decided that data retrieval needed to stay as far away from the UI as possible. Let's play devil's advocate for a minute and question this.</p>
  589. <p>Let's continue our post example from above. A dumb, simple <code>Post</code> component in React might look like the following:</p>
  590. <pre><code>// PostComponent.js
  591. var Post = React.createClass({
  592. render() {
  593. var post = this.props.post;
  594. return (
  595. &lt;div&gt;
  596. &lt;h1&gt;{post.title}&lt;/h1&gt;
  597. &lt;h4&gt;by {post.author.name}&lt;/h4&gt;
  598. &lt;div&gt;{post.body}&lt;/div&gt;
  599. &lt;/div&gt;
  600. );
  601. }
  602. });
  603. </code></pre>
  604. <p>Great. Nice and simple. </p>
  605. <p>This component has some pretty dramatic coupling to our server though. In order for this component to work, we are assuming that a "post" object handed to us from the server is going to have a <code>title</code> and <code>body</code> property, as well as an <code>author</code> property which itself will have a <code>name</code> property.</p>
  606. <p>Those requirements are not explicitly passed on to the server anywhere, so instead the developer must keep these requirements in their head as they are implementing things. </p>
  607. <p>Because "data retrieval" has historically been considered a concern that should be pretty much as far away from the UI as possible, these data contracts are things that we usually and up dealing with <em>all the time</em> as developers. As a result, whenever we decide that we want to add something to the UI that requires more data, we have to walk all the way down the stack to make sure that every layer along the way has that field properly included.</p>
  608. <p>Consider this: let your component <em>declare</em> it's data requirements, and do the data retrieval itself.</p>
  609. <p>This is precisely what the "Relay" framework that Facebook is planning on open sourcing seems to be addressing. We can actually declare where the data is going to come from in our post component with syntax like the following:</p>
  610. <pre><code>// PostComponent.js
  611. var Post = React.createClass({
  612. render() {
  613. var post = this.props.post;
  614. return (
  615. &lt;div&gt;
  616. &lt;h1&gt;{post.title}&lt;/h1&gt;
  617. &lt;h4&gt;by {post.author.name}&lt;/h4&gt;
  618. &lt;div&gt;{post.body}&lt;/div&gt;
  619. &lt;/div&gt;
  620. );
  621. }
  622. });
  623. module.exports = Relay.createContainer(Post, {
  624. queries: {
  625. post: graphql`
  626. Post {
  627. title,
  628. body,
  629. author {
  630. name
  631. }
  632. }
  633. `
  634. }
  635. });
  636. </code></pre>
  637. <p>The exact syntax here isn't really as important as the general idea. This allows us to effectively get rid of our "Stores" in Flux, and our Repository + Model in our MVC example, and instead just have our component declare its data needs directly.</p>
  638. <p>A powerful side effect of this strategy is that our data requirements are now as composable as our components.</p>
  639. <p>Let's say our Post component's render method instead looked something like this:</p>
  640. <pre><code>render() {
  641. var post = this.props.post;
  642. return (
  643. &lt;div&gt;
  644. &lt;h1&gt;{post.title}&lt;/h1&gt;
  645. &lt;UserInfo user={post.author} /&gt;
  646. &lt;div&gt;{post.body}&lt;/div&gt;
  647. &lt;/div&gt;
  648. );
  649. }
  650. </code></pre>
  651. <p>We now don't know from looking at this file exactly what data is needed in order to display a post, since we are now posting <code>post.author</code> into a child <code>&lt;UserInfo /&gt;</code> component. If the <code>UserInfo</code> component decides that it needs more than just the author's name, how will we know? We can actually change the above example to be something like the following:</p>
  652. <pre><code>module.exports = Relay.createContainer(Post, {
  653. queries: {
  654. post: graphql`
  655. Post {
  656. title,
  657. body,
  658. author {
  659. ${UserInfo.getQuery('user')}
  660. }
  661. }
  662. `
  663. }
  664. });
  665. </code></pre>
  666. <p>Perfect! Now our component's data requirements are a function of its own explicit needs, and any of the needs that its children components required themselves. No error-prone implicit coupling! If the <code>UserInfo</code> component is updated to require more data, all of the higher order components that depend on it will automatically update their requirements.</p>
  667. <p>Similar to the CSS example, we see that this approach can have several immediate benefits:</p>
  668. <ol>
  669. <li>We can change the data "contract" for this component and know for sure that it's only affecting this file, and won't break any of our other code.</li>
  670. <li>Since Components are composable, we can also have component's data contracts simply reference the data contracts of other</li>
  671. <li>We can now minimize the amount of data that our server needs to send us on each request, since we know precisely what data the UI needs to display.</li>
  672. <li>We brought more files together!</li>
  673. </ol>
  674. <p><em>Note: I'd like to mention that bringing in these declarations to client-facing JavaScript files does have some potentially troubling security implications, and those issues will still need to be handled purely server-side... which may not be a trivial task</em></p>
  675. <h2 id="recap">Recap</h2>
  676. <h3 id="what_i_am_not_saying:">What I am NOT saying:</h3>
  677. <p>I want to be clear that if our project is made up of 4 entities such as <code>Foo</code>, <code>Bar</code>, <code>Baz</code>, and <code>Biz</code>, that <strong>I am NOT saying that the resulting file structure should look like this</strong>:</p>
  678. <pre><code>Foo.js
  679. Bar.js
  680. Baz.js
  681. Biz.js
  682. </code></pre>
  683. <h3 id="what_i_am_saying:">What I AM saying:</h3>
  684. <p>By moving to a component model, we should strive to have our components be completely self-contained and, ideally, made up of a single file. But thinking in terms of components might actually make our project end up with a larger number of files than a project which did not. The goal is to make it such that those files are not implicitly coupled with one another, and implementing a change in one will not usually necessitate updating a chain of other files.</p>
  685. <p>By moving in data retrieval and styling into the component itself, we actually start to shed a lot of the weight off of the other parts of the app that were a little bit heavier before. The need for the "M" and the "C" in "MVC" might go away entirely... </p>
  686. <h2 id="whats_next">What's next?</h2>
  687. <p>I really like where I think the development world is heading by embracing UI in terms of components, and I would like to encourage you (the reader) to continue thinking along these lines about how we can make developing applications even nicer by minimizing the coupling between files, even if it goes against established "best practices".</p>
  688. </article>
  689. </section>
  690. <nav id="jumpto">
  691. <p>
  692. <a href="/david/blog/">Accueil du blog</a> |
  693. <a href="http://tech.pro/blog/6968/one-concern-one-file">Source originale</a> |
  694. <a href="/david/stream/2019/">Accueil du flux</a>
  695. </p>
  696. </nav>
  697. <footer>
  698. <div>
  699. <img src="/static/david/david-larlet-avatar.jpg" loading="lazy" class="avatar" width="200" height="200">
  700. <p>
  701. Bonjour/Hi!
  702. Je suis <a href="/david/" title="Profil public">David&nbsp;Larlet</a>, je vis actuellement à Montréal et j’alimente cet espace depuis 15 ans. <br>
  703. Si tu as apprécié cette lecture, n’hésite pas à poursuivre ton exploration. Par exemple via les <a href="/david/blog/" title="Expériences bienveillantes">réflexions bimestrielles</a>, la <a href="/david/stream/2019/" title="Pensées (dés)articulées">veille hebdomadaire</a> ou en t’abonnant au <a href="/david/log/" title="S’abonner aux publications via RSS">flux RSS</a> (<a href="/david/blog/2019/flux-rss/" title="Tiens c’est quoi un flux RSS ?">so 2005</a>).
  704. </p>
  705. <p>
  706. Je m’intéresse à la place que je peux avoir dans ce monde. En tant qu’humain, en tant que membre d’une famille et en tant qu’associé d’une coopérative. De temps en temps, je fais aussi des <a href="https://github.com/davidbgk" title="Principalement sur Github mais aussi ailleurs">trucs techniques</a>. Et encore plus rarement, <a href="/david/talks/" title="En ce moment je laisse plutôt la place aux autres">j’en parle</a>.
  707. </p>
  708. <p>
  709. Voici quelques articles choisis :
  710. <a href="/david/blog/2019/faire-equipe/" title="Accéder à l’article complet">Faire équipe</a>,
  711. <a href="/david/blog/2018/bivouac-automnal/" title="Accéder à l’article complet">Bivouac automnal</a>,
  712. <a href="/david/blog/2018/commodite-effondrement/" title="Accéder à l’article complet">Commodité et effondrement</a>,
  713. <a href="/david/blog/2017/donnees-communs/" title="Accéder à l’article complet">Des données aux communs</a>,
  714. <a href="/david/blog/2016/accompagner-enfant/" title="Accéder à l’article complet">Accompagner un enfant</a>,
  715. <a href="/david/blog/2016/senior-developer/" title="Accéder à l’article complet">Senior developer</a>,
  716. <a href="/david/blog/2016/illusion-sociale/" title="Accéder à l’article complet">L’illusion sociale</a>,
  717. <a href="/david/blog/2016/instantane-scopyleft/" title="Accéder à l’article complet">Instantané Scopyleft</a>,
  718. <a href="/david/blog/2016/enseigner-web/" title="Accéder à l’article complet">Enseigner le Web</a>,
  719. <a href="/david/blog/2016/simplicite-defaut/" title="Accéder à l’article complet">Simplicité par défaut</a>,
  720. <a href="/david/blog/2016/minimalisme-esthetique/" title="Accéder à l’article complet">Minimalisme et esthétique</a>,
  721. <a href="/david/blog/2014/un-web-omni-present/" title="Accéder à l’article complet">Un web omni-présent</a>,
  722. <a href="/david/blog/2014/manifeste-developpeur/" title="Accéder à l’article complet">Manifeste de développeur</a>,
  723. <a href="/david/blog/2013/confort-convivialite/" title="Accéder à l’article complet">Confort et convivialité</a>,
  724. <a href="/david/blog/2013/testament-numerique/" title="Accéder à l’article complet">Testament numérique</a>,
  725. et <a href="/david/blog/" title="Accéder aux archives">bien d’autres…</a>
  726. </p>
  727. <p>
  728. On peut <a href="mailto:david%40larlet.fr" title="Envoyer un courriel">échanger par courriel</a>. Si éventuellement tu souhaites que l’on travaille ensemble, tu devrais commencer par consulter le <a href="http://larlet.com">profil dédié à mon activité professionnelle</a> et/ou contacter directement <a href="http://scopyleft.fr/">scopyleft</a>, la <abbr title="Société coopérative et participative">SCOP</abbr> dont je fais partie depuis six ans. Je recommande au préalable de lire <a href="/david/blog/2018/cout-site/" title="Attention ce qui va suivre peut vous choquer">combien coûte un site</a> et pourquoi je suis plutôt favorable à une <a href="/david/pro/devis/" title="Discutons-en !">non-demande de devis</a>.
  729. </p>
  730. <p>
  731. Je ne traque pas ta navigation mais mon
  732. <abbr title="Alwaysdata, 62 rue Tiquetonne 75002 Paris, +33.184162340">hébergeur</abbr>
  733. conserve des logs d’accès.
  734. </p>
  735. </div>
  736. </footer>
  737. <script type="text/javascript">
  738. ;(_ => {
  739. const jumper = document.getElementById('jumper')
  740. jumper.addEventListener('click', e => {
  741. e.preventDefault()
  742. const anchor = e.target.getAttribute('href')
  743. const targetEl = document.getElementById(anchor.substring(1))
  744. targetEl.scrollIntoView({behavior: 'smooth'})
  745. })
  746. })()
  747. </script>