A place to cache linked articles (think custom and personal wayback machine)
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

4 лет назад
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. title: One Concern, One File
  2. url: http://tech.pro/blog/6968/one-concern-one-file
  3. hash_url: 5f89f7653ed5ada2aee9ad649b984141
  4. <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>
  5. <p>It's a good rule.</p>
  6. <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>
  7. <h2 id="lets_talk_about_mvc">Let's talk about MVC</h2>
  8. <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>
  9. <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>
  10. <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>
  11. <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>
  12. <pre><code>- Server
  13. - Models
  14. Post.js
  15. - Views
  16. Post.ejs
  17. - Controllers
  18. Post.js
  19. </code></pre>
  20. <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>
  21. <p>So we just created <strong>three</strong> files for our <strong>one</strong> new thing: the Post entity. </p>
  22. <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>
  23. <p>According to MVC we are doing everything right... but we aren't done implementing our "post" yet...</p>
  24. <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>
  25. <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>
  26. <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>
  27. <p>So just to recap, our project may look a little closer to this now:</p>
  28. <pre><code>- Server
  29. - Repositories
  30. Post.js
  31. - Models
  32. Post.js
  33. - Views
  34. Post.ejs
  35. - Controllers
  36. Post.js
  37. - Client
  38. - Scripts
  39. Post.js
  40. - Styles
  41. Post.css
  42. </code></pre>
  43. <p>This is getting a little crazy, right? </p>
  44. <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>
  45. <h3 id="lets_make_a_quick_change">Let's make a quick change</h3>
  46. <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>
  47. <ol>
  48. <li>Update the <code>Repositories/Post.js</code> file so that it included the <code>image</code> property in the data query.</li>
  49. <li>Update the <code>Models/Post.js</code> file so that the model included the <code>image</code> property.</li>
  50. <li>Update the <code>Views/Post.ejs</code> file so that it actually renders the included image now.</li>
  51. <li>Update the <code>Scripts/Post.js</code> file to have some cool hover effect over the image, because why not?</li>
  52. <li>Update the <code>Styles/Post.css</code> file to include some new basic styling for the image.</li>
  53. </ol>
  54. <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>
  55. <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>
  56. <h2 id="are_we_separating_by_the_wrong_concerns">Are we separating by the wrong concerns?</h2>
  57. <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>
  58. <p>That's pretty cool, and no one is arguing that that wouldn't be a big win for that scenario...</p>
  59. <p>However, which of the following scenarios happens more often:</p>
  60. <p><img src="http://tpstatic.com/img/usermedia/-2YBoUduq0aLXbRU_I5O7g/w645.png" alt="enter image description here"/></p>
  61. <p>If your answer is the latter, you should run far, far away from that job and never look back.</p>
  62. <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>
  63. <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>
  64. <h2 id="what_about_flux">What about Flux?</h2>
  65. <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>
  66. <pre><code>- Server
  67. - API
  68. Post.js
  69. - Client
  70. - Actions
  71. PostActions.js
  72. - Stores
  73. PostStore.js
  74. - Components
  75. Post.js
  76. - Styles
  77. Post.css
  78. </code></pre>
  79. <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>
  80. <h2 id="how_can_we_make_it_better">How can we make it better?</h2>
  81. <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>
  82. <h2 id="step_1:_components">Step 1: Components</h2>
  83. <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>
  84. <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>
  85. <p>Result: Our <code>Scripts/Post.js</code> file and our <code>Views/Post.ejs</code> file have now become one.</p>
  86. <h2 id="step_2:_css_in_your_js">Step 2: CSS in your JS</h2>
  87. <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>
  88. <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>
  89. <p>For instance, the way we do things like now might look kinda like this:</p>
  90. <pre><code>// FooComponent.css
  91. .foo-component {
  92. padding: 10px;
  93. background-color: red;
  94. }
  95. .foo-component-inner {
  96. width: 80px;
  97. border: 1px solid #000;
  98. }
  99. // FooComponent.js
  100. module.exports = React.createClass({
  101. render() {
  102. return (
  103. &lt;div class="foo-component"&gt;
  104. Foo Component
  105. &lt;div class="foo-component-inner"&gt;
  106. Inner
  107. &lt;/div&gt;
  108. &lt;/div&gt;
  109. );
  110. }
  111. });
  112. </code></pre>
  113. <p>Embracing component styling from within JS, we might instead have something that looks like this:</p>
  114. <pre><code>// FooComponent.js
  115. var styles = {
  116. outer: {
  117. padding: 10,
  118. backgroundColor: 'red'
  119. },
  120. inner: {
  121. width: 80,
  122. border: '1px solid #000'
  123. }
  124. };
  125. module.exports = React.createClass({
  126. render() {
  127. return (
  128. &lt;div style={styles.outer}&gt;
  129. Foo Component
  130. &lt;div style={styles.inner}&gt;
  131. Inner
  132. &lt;/div&gt;
  133. &lt;/div&gt;
  134. );
  135. }
  136. });
  137. </code></pre>
  138. <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>
  139. <ol>
  140. <li>Scope: We know, with absolute certainty, that these styles aren't used elsewhere</li>
  141. <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>
  142. <li>Now that we are in JS-land, we can create/mix/compute styles with the full power of javascript behind us!</li>
  143. <li>Removed global </li>
  144. <li>We've eliminated a file! No mare needing to switch between these two coupled files</li>
  145. </ol>
  146. <h2 id="step_3:_data_retrieval_in_your_view">Step 3: Data Retrieval in your View</h2>
  147. <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>
  148. <p>Let's continue our post example from above. A dumb, simple <code>Post</code> component in React might look like the following:</p>
  149. <pre><code>// PostComponent.js
  150. var Post = React.createClass({
  151. render() {
  152. var post = this.props.post;
  153. return (
  154. &lt;div&gt;
  155. &lt;h1&gt;{post.title}&lt;/h1&gt;
  156. &lt;h4&gt;by {post.author.name}&lt;/h4&gt;
  157. &lt;div&gt;{post.body}&lt;/div&gt;
  158. &lt;/div&gt;
  159. );
  160. }
  161. });
  162. </code></pre>
  163. <p>Great. Nice and simple. </p>
  164. <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>
  165. <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>
  166. <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>
  167. <p>Consider this: let your component <em>declare</em> it's data requirements, and do the data retrieval itself.</p>
  168. <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>
  169. <pre><code>// PostComponent.js
  170. var Post = React.createClass({
  171. render() {
  172. var post = this.props.post;
  173. return (
  174. &lt;div&gt;
  175. &lt;h1&gt;{post.title}&lt;/h1&gt;
  176. &lt;h4&gt;by {post.author.name}&lt;/h4&gt;
  177. &lt;div&gt;{post.body}&lt;/div&gt;
  178. &lt;/div&gt;
  179. );
  180. }
  181. });
  182. module.exports = Relay.createContainer(Post, {
  183. queries: {
  184. post: graphql`
  185. Post {
  186. title,
  187. body,
  188. author {
  189. name
  190. }
  191. }
  192. `
  193. }
  194. });
  195. </code></pre>
  196. <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>
  197. <p>A powerful side effect of this strategy is that our data requirements are now as composable as our components.</p>
  198. <p>Let's say our Post component's render method instead looked something like this:</p>
  199. <pre><code>render() {
  200. var post = this.props.post;
  201. return (
  202. &lt;div&gt;
  203. &lt;h1&gt;{post.title}&lt;/h1&gt;
  204. &lt;UserInfo user={post.author} /&gt;
  205. &lt;div&gt;{post.body}&lt;/div&gt;
  206. &lt;/div&gt;
  207. );
  208. }
  209. </code></pre>
  210. <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>
  211. <pre><code>module.exports = Relay.createContainer(Post, {
  212. queries: {
  213. post: graphql`
  214. Post {
  215. title,
  216. body,
  217. author {
  218. ${UserInfo.getQuery('user')}
  219. }
  220. }
  221. `
  222. }
  223. });
  224. </code></pre>
  225. <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>
  226. <p>Similar to the CSS example, we see that this approach can have several immediate benefits:</p>
  227. <ol>
  228. <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>
  229. <li>Since Components are composable, we can also have component's data contracts simply reference the data contracts of other</li>
  230. <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>
  231. <li>We brought more files together!</li>
  232. </ol>
  233. <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>
  234. <h2 id="recap">Recap</h2>
  235. <h3 id="what_i_am_not_saying:">What I am NOT saying:</h3>
  236. <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>
  237. <pre><code>Foo.js
  238. Bar.js
  239. Baz.js
  240. Biz.js
  241. </code></pre>
  242. <h3 id="what_i_am_saying:">What I AM saying:</h3>
  243. <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>
  244. <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>
  245. <h2 id="whats_next">What's next?</h2>
  246. <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>