index.html 203 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="generator" content="pandoc">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
  7. <title>RePractise – </title>
  8. <style type="text/css">code{white-space: pre;}</style>
  9. <style type="text/css">
  10. div.sourceCode { overflow-x: auto; }
  11. table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
  12. margin: 0; padding: 0; vertical-align: baseline; border: none; }
  13. table.sourceCode { width: 100%; line-height: 100%; }
  14. td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
  15. td.sourceCode { padding-left: 5px; }
  16. code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
  17. code > span.dt { color: #902000; } /* DataType */
  18. code > span.dv { color: #40a070; } /* DecVal */
  19. code > span.bn { color: #40a070; } /* BaseN */
  20. code > span.fl { color: #40a070; } /* Float */
  21. code > span.ch { color: #4070a0; } /* Char */
  22. code > span.st { color: #4070a0; } /* String */
  23. code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
  24. code > span.ot { color: #007020; } /* Other */
  25. code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
  26. code > span.fu { color: #06287e; } /* Function */
  27. code > span.er { color: #ff0000; font-weight: bold; } /* Error */
  28. code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
  29. code > span.cn { color: #880000; } /* Constant */
  30. code > span.sc { color: #4070a0; } /* SpecialChar */
  31. code > span.vs { color: #4070a0; } /* VerbatimString */
  32. code > span.ss { color: #bb6688; } /* SpecialString */
  33. code > span.im { } /* Import */
  34. code > span.va { color: #19177c; } /* Variable */
  35. code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
  36. code > span.op { color: #666666; } /* Operator */
  37. code > span.bu { } /* BuiltIn */
  38. code > span.ex { } /* Extension */
  39. code > span.pp { color: #bc7a00; } /* Preprocessor */
  40. code > span.at { color: #7d9029; } /* Attribute */
  41. code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
  42. code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
  43. code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
  44. code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
  45. </style>
  46. <link rel="stylesheet" href="style.css">
  47. <!--[if lt IE 9]>
  48. <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
  49. <![endif]-->
  50. <meta name="viewport" content="width=device-width">
  51. </head>
  52. <body>
  53. <p>
  54. <h1>RePractise</h1>
  55. <h3>By Phodal Huang(<a href="http://www.phodal.com">Geek's Life</a>)</h3>
  56. </p>
  57. <div>
  58. <iframe src="http://ghbtns.com/github-btn.html?user=phodal&repo=repractise&type=watch&count=true"
  59. allowtransparency="true" frameborder="0" scrolling="0" width="110px" height="20px"></iframe>
  60. <div>
  61. <nav id="TOC">
  62. <ul>
  63. <li><a href="#repractise">RePractise</a><ul>
  64. <li><a href="#关于作者">关于作者</a></li>
  65. </ul></li>
  66. <li><a href="#引言">引言</a><ul>
  67. <li><a href="#re-practise">Re-Practise</a></li>
  68. <li><a href="#技术与业务">技术与业务</a></li>
  69. <li><a href="#资讯爆炸">资讯爆炸</a></li>
  70. <li><a href="#lost">Lost</a></li>
  71. </ul></li>
  72. <li><a href="#前端篇-前端演进史">前端篇: 前端演进史</a><ul>
  73. <li><a href="#什么是前端">什么是前端?</a></li>
  74. <li><a href="#前端演进史">前端演进史</a><ul>
  75. <li><a href="#数据-模板-样式混合">数据-模板-样式混合</a></li>
  76. <li><a href="#model-view-controller">Model-View-Controller</a></li>
  77. <li><a href="#从桌面版到移动版">从桌面版到移动版</a></li>
  78. <li><a href="#app与过渡期api">APP与过渡期API</a></li>
  79. <li><a href="#过渡期spa">过渡期SPA</a></li>
  80. <li><a href="#hybird与viewmodel">Hybird与ViewModel</a></li>
  81. <li><a href="#一次构建跨平台运行">一次构建,跨平台运行</a></li>
  82. </ul></li>
  83. <li><a href="#repractise-1">RePractise</a></li>
  84. </ul></li>
  85. <li><a href="#后台与服务篇">后台与服务篇</a><ul>
  86. <li><a href="#restful与服务化">RESTful与服务化</a><ul>
  87. <li><a href="#设计restful-api">设计RESTful API</a></li>
  88. <li><a href="#资源">资源</a></li>
  89. </ul></li>
  90. <li><a href="#微服务">微服务</a><ul>
  91. <li><a href="#微内核">微内核</a></li>
  92. </ul></li>
  93. <li><a href="#混合微服务">混合微服务</a></li>
  94. <li><a href="#其他">其他</a></li>
  95. </ul></li>
  96. <li><a href="#前后端篇">前后端篇</a><ul>
  97. <li><a href="#前后端分离">前后端分离</a></li>
  98. <li><a href="#单页面应用后台渲染">单页面应用后台渲染</a><ul>
  99. <li><a href="#前后台渲染同一模板">前后台渲染同一模板</a></li>
  100. <li><a href="#prerender方式">PreRender方式</a></li>
  101. <li><a href="#react">React</a></li>
  102. </ul></li>
  103. </ul></li>
  104. <li><a href="#从真实世界到前后端">从真实世界到前后端</a><ul>
  105. <li><a href="#从真实世界到前后端-1">从真实世界到前后端</a><ul>
  106. <li><a href="#便利店与售货员">便利店与售货员</a></li>
  107. <li><a href="#模型领域抽象">模型、领域、抽象</a></li>
  108. </ul></li>
  109. <li><a href="#前后台分离后台">前后台分离:后台</a></li>
  110. <li><a href="#前后台分离前端">前后台分离:前端</a></li>
  111. <li><a href="#repractise-2">RePractise</a></li>
  112. </ul></li>
  113. <li><a href="#重构篇">重构篇</a><ul>
  114. <li><a href="#网站重构">网站重构</a><ul>
  115. <li><a href="#网站重构目的">网站重构目的</a></li>
  116. </ul></li>
  117. <li><a href="#代码重构">代码重构</a></li>
  118. <li><a href="#使用工具重构">使用工具重构</a></li>
  119. <li><a href="#借助工具重构">借助工具重构</a><ul>
  120. <li><a href="#code-climate">Code Climate</a></li>
  121. </ul></li>
  122. <li><a href="#测试驱动开发">测试驱动开发</a><ul>
  123. <li><a href="#一次测试驱动开发的故事">一次测试驱动开发的故事</a></li>
  124. <li><a href="#说说测试驱动开发">说说测试驱动开发</a></li>
  125. <li><a href="#思考">思考</a></li>
  126. </ul></li>
  127. </ul></li>
  128. <li><a href="#架构篇-cms的重构与演进">架构篇: CMS的重构与演进</a><ul>
  129. <li><a href="#动态cms">动态CMS</a><ul>
  130. <li><a href="#cms简介">CMS简介</a></li>
  131. <li><a href="#cms架构与django">CMS架构与Django</a></li>
  132. <li><a href="#编辑-发布分离">编辑-发布分离</a></li>
  133. <li><a href="#基于github的编辑-发布-开发分离">基于Github的编辑-发布-开发分离</a></li>
  134. <li><a href="#repractise-3">Repractise</a></li>
  135. </ul></li>
  136. <li><a href="#构建基于git为数据中心的cms">构建基于Git为数据中心的CMS</a><ul>
  137. <li><a href="#用户场景">用户场景</a></li>
  138. </ul></li>
  139. <li><a href="#code-生成静态页面">Code: 生成静态页面</a></li>
  140. <li><a href="#builder-构建生成工具">Builder: 构建生成工具</a></li>
  141. <li><a href="#contentjson格式">Content:JSON格式</a><ul>
  142. <li><a href="#从schema到数据库">从Schema到数据库</a></li>
  143. <li><a href="#git作为nosql数据库">git作为NoSQL数据库</a></li>
  144. </ul></li>
  145. <li><a href="#一键发布编辑器">一键发布:编辑器</a></li>
  146. <li><a href="#移动应用">移动应用</a><ul>
  147. <li><a href="#小结">小结</a></li>
  148. <li><a href="#其他-2">其他</a></li>
  149. </ul></li>
  150. </ul></li>
  151. <li><a href="#模式篇设计与架构">模式篇:设计与架构</a><ul>
  152. <li><a href="#观察者模式">观察者模式</a><ul>
  153. <li><a href="#ruby观察者模式">Ruby观察者模式</a></li>
  154. <li><a href="#pubsub">PUB/SUB</a></li>
  155. </ul></li>
  156. <li><a href="#模板方法">模板方法</a><ul>
  157. <li><a href="#从基本的app说起">从基本的App说起</a></li>
  158. <li><a href="#template-method">Template Method</a></li>
  159. <li><a href="#template-method实战">Template Method实战</a></li>
  160. </ul></li>
  161. <li><a href="#pipe-and-filters">Pipe and Filters</a><ul>
  162. <li><a href="#unix-shell">Unix Shell</a></li>
  163. <li><a href="#pipe-and-filter模式">Pipe and Filter模式</a></li>
  164. <li><a href="#fluent-api">Fluent API</a></li>
  165. <li><a href="#dsl-表达式生成器">DSL 表达式生成器</a></li>
  166. <li><a href="#pipe-and-filter模式实战">Pipe and Filter模式实战</a></li>
  167. </ul></li>
  168. </ul></li>
  169. <li><a href="#数据与模型篇">数据与模型篇</a><ul>
  170. <li><a href="#数据">数据</a><ul>
  171. <li><a href="#数据库">数据库</a></li>
  172. <li><a href="#建模">建模</a></li>
  173. </ul></li>
  174. </ul></li>
  175. <li><a href="#领域篇">领域篇</a><ul>
  176. <li><a href="#ddd">DDD</a></li>
  177. <li><a href="#dsl">DSL</a><ul>
  178. <li><a href="#dsl示例">DSL示例</a></li>
  179. </ul></li>
  180. </ul></li>
  181. </ul>
  182. </nav>
  183. <h1 id="repractise">RePractise</h1>
  184. <h2 id="关于作者">关于作者</h2>
  185. <p>黄峰达(Phodal Huang)是一个创客、工程师、咨询师和作家。他毕业于西安文理学院电子信息工程专业,现作为一个咨询师就职于 ThoughtWorks 深圳。长期活跃于开源软件社区 GitHub,目前专注于物联网和前端领域。</p>
  186. <p>作为一个开源软件作者,著有 Growth、Stepping、Lan、Echoesworks 等软件。其中开源学习应用 Growth,广受读者和用户好评,可在 APP Store 及各大 Android 应用商店下载。</p>
  187. <p>作为一个技术作者,著有《自己动手设计物联网》(电子工业出版社)、《全栈应用开发:精益实践》(电子工业出版社,正在出版)。并在 GitHub 上开源有《Growth: 全栈增长工程师指南》、《GitHub 漫游指南》等七本电子书。</p>
  188. <p>作为技术专家,他为英国 Packt 出版社审阅有物联网书籍《Learning IoT》、《Smart IoT》,前端书籍《Angular 2 Serices》、《Getting started with Angular》等技术书籍。</p>
  189. <p>他热爱编程、写作、设计、旅行、hacking,你可以从他的个人网站:<a href="https://www.phodal.com/" class="uri">https://www.phodal.com/</a> 了解到更多的内容。</p>
  190. <p>其它相关信息:</p>
  191. <ul>
  192. <li>微博:<a href="http://weibo.com/phodal" class="uri">http://weibo.com/phodal</a></li>
  193. <li>GitHub: <a href="https://github.com/phodal" class="uri">https://github.com/phodal</a></li>
  194. <li>知乎:<a href="https://www.zhihu.com/people/phodal" class="uri">https://www.zhihu.com/people/phodal</a></li>
  195. <li>SegmentFault:<a href="https://segmentfault.com/u/phodal" class="uri">https://segmentfault.com/u/phodal</a></li>
  196. </ul>
  197. <p>当前为预览版,在使用的过程中遇到任何问题请及时与我联系。阅读过程中的问题,不妨在GitHub上提出来: <a href="https://github.com/phodal/fe/issues">Issues</a></p>
  198. <p>阅读过程中遇到语法错误、拼写错误、技术错误等等,不妨来个Pull Request,这样可以帮助到其他阅读这本电子书的童鞋。</p>
  199. <p>我的电子书:</p>
  200. <ul>
  201. <li>《<a href="https://github.com/phodal/github-roam">GitHub 漫游指南</a>》</li>
  202. <li>《<a href="https://github.com/phodal/fe">我的职业是前端工程师</a>》</li>
  203. <li>《<a href="https://github.com/phodal/serverless">Serverless 架构应用开发指南</a>》</li>
  204. <li>《<a href="https://github.com/phodal/growth-ebook">Growth: 全栈增长工程师指南</a>》</li>
  205. <li>《<a href="https://github.com/phodal/ideabook">Phodal’s Idea实战指南</a>》</li>
  206. <li>《<a href="https://github.com/phodal/designiot">一步步搭建物联网系统</a>》</li>
  207. <li>《<a href="https://github.com/phodal/repractise">RePractise</a>》</li>
  208. <li>《<a href="https://github.com/phodal/growth-in-action">Growth: 全栈增长工程师实战</a>》</li>
  209. </ul>
  210. <p>我的微信公众号:</p>
  211. <figure>
  212. <img src="./img/wechat.jpg" alt="作者微信公众号:phodal-weixin" /><figcaption>作者微信公众号:phodal-weixin</figcaption>
  213. </figure>
  214. <p>支持作者,可以加入作者的小密圈:</p>
  215. <figure>
  216. <img src="./img/xiaomiquan.jpg" alt="小密圈" /><figcaption>小密圈</figcaption>
  217. </figure>
  218. <p>或者转账:</p>
  219. <p><img src="./img/alipay.png" alt="支付宝" /> <img src="./img/wechat-pay.png" alt="微信" /></p>
  220. <h1 id="引言">引言</h1>
  221. <p>回到一年前的今天(2014.09.29),一边在准备着去沙漠之旅,一边在准备国庆后的印度培训。</p>
  222. <p>当时我还在用我的Lumia 920,上面没有各式各样的软件,除了我最需要的地图、相机。所以,我需要为我的手机写一个应用,用于在地图上显示图片信息及照片。</p>
  223. <p>今天Github已经可以支持geojson了,于是你可以看到我在之前生成的geojson在地图上的效果<a href="https://github.com/phodal-archive/onmap/blob/master/gps.geojson">gps.geojson</a>。</p>
  224. <h2 id="re-practise">Re-Practise</h2>
  225. <p>在过去的近一年时期里,花费了很多时间在提高代码质量与构建架构知识。试着学习某一方面的架构知识,应用到某个熟悉领域。</p>
  226. <ol type="1">
  227. <li><p>所谓的一万小时天才理论一直在说明练习的重要性,你需要不断地去练习。但是并不是说你练习了一万小时之后就可以让你成为一个专家,而练习是必须的。</p></li>
  228. <li><p>让我想起了在大学时代学的PID算法,虽然我没有掌握好控制领域的相关理论及算法,但是我对各种调节还算有点印象。简单地来说,我们需要不断调整自己的方向。</p></li>
  229. </ol>
  230. <p>现在还存在的那些互联网公司或者说开源项目,我们会发现两个不算有趣的规律:</p>
  231. <ol type="1">
  232. <li>一个一直在运行的软件。</li>
  233. <li>尝试了几个产品,最后找到了一个合适的方向。</li>
  234. </ol>
  235. <p>我发现我属于不断尝试地类型。一直想构建一个开源软件,但是似乎一直没有找对合理的用户?但是,我们会发现上述地两者都在不断地retry,不断地retry归根于那些人在不断的repractise。与之成为反例的便是:</p>
  236. <ol type="1">
  237. <li>一个成功发布几次的软件,但是最后失败了</li>
  238. <li>尝试了不同的几个产品,但是失败了</li>
  239. </ol>
  240. <p>所谓的失败,就是你离开人世了。所以,在我们还活着的时候,我们总会有机会去尝试。在那之前,我们都是在不断地re-practise。</p>
  241. <p>这让我想到了Linux,这算是一个不错地软件,从一开始就存活到了现在。但是有多少开源软件就没有这么幸运,时间在淘汰越来越多的过去想法。人们创造事物的能力也越来越强,但是那只是因为创造变得越来越简单。</p>
  242. <p>在我们看到的那些走上人生巅峰的CEO,还都在不断地re-practise。</p>
  243. <h2 id="技术与业务">技术与业务</h2>
  244. <p>于是,我又再次回到了这样一个现实的问题。技术可以不断地练习,不断地调整方向。但是技术地成本在不断地降低,代码的长度在不断地降低。整个技术的门槛越来越低,新出现的技术总会让新生代的程序员获利。但是不可避免地,业务地复杂度并没有因此而降低。这就是一个复杂的话题,难道业务真的很复杂吗?</p>
  245. <p>人们总会提及写好CSS很难,但是写好Java就是一件容易的事。因为每天我们都在用Java、JavaScript去写代码,但是我们并没有花费时间去学。</p>
  246. <p>因为我们一直将我们的时候花费的所谓的业务上,我们可以不断地将一些重复的代码抽象成一个库。但是我们并没有花费过多的时间去整理我们的业务,作为程序员,我们切换工作很容易只是因为相同的技术栈。作为一些营销人员,他们从一个领域到一个新的领域,不需要过多的学习,因为本身是相通的。</p>
  247. <p>技术本身是如此,业务本身也是如此。</p>
  248. <p>从技术到技术-领域是一条难走通的路?</p>
  249. <h2 id="资讯爆炸">资讯爆炸</h2>
  250. <p>回顾到近几年出现的各种资讯程序——开发者头条、极客头条、掘金、博乐头条等等,他们帮助我们的是丰富我们的信息,而不是简化我们的信息。</p>
  251. <p>作为一个开发人员,过去我们并不需要关注那么多的内容。如果我们没有关注那么多的点,那么我们就可以集中于我们的想法里。实现上,我们需要的是一个更智能的时代。</p>
  252. <p>业务本身是一种重复,技术本身也是重复的。只是在某个特定的时刻,一个好的技术可以帮助我们更好地Re-Practise。如推荐算法本身依赖于人为对信息进行分类,但是我们需要去区分大量地信息。而人本身的经历是足够有险的,这时候就需要机器来帮我们做很多事。</p>
  253. <p>今天我在用MX5,但是发现不及Lumia 1020来得安静。功能越强大的同时,意味着我在上面花费的时间会更多。事情有好的一面总会有不好的一面,不好的一面也就意味着有机会寻找好的一面。</p>
  254. <p>我们需要摒弃一些东西,以重新纠正我们的方向。于是,我需要再次回到Lumia 1020上。</p>
  255. <h2 id="lost">Lost</h2>
  256. <blockquote>
  257. <p>一开始就输在起跑线上</p>
  258. </blockquote>
  259. <p>这是一个很有意思的话题,尽管试图将本章中从书中删除,但是我还是忍了下来。如果你学得比别人晚,在很长的一段时间里(可能直到进棺材)输给别人是必然的——落后就要挨打。就好像我等毕业于一所二本垫底的学校里,如果在过去我一直保持着和别人(各种重点)一样的学习速度,那么我只能一直是Loser。</p>
  260. <p>需要注意的是,对你来说考上二本很难,并不是因为你比别人笨。教育资源分配不均的问题,在某种程度上导致了新的阶级制度的出现。如我的首页说的那样: THE ONLY FAIR IS NOT FAIR——唯一公平的是它是不公平的。我们可以做的还有很多——CREATE &amp; SHARE。真正的不幸是,因为营养不良导致的教育问题。如果你还有机会正常地思想,那说明这个世界对你还是公平的。</p>
  261. <h1 id="前端篇-前端演进史">前端篇: 前端演进史</h1>
  262. <p>细细整理了过去接触过的那些前端技术,发现前端演进是段特别有意思的历史。人们总是在过去就做出未来需要的框架,而现在流行的是过去的过去发明过的。如,响应式设计不得不提到的一个缺点是:<strong>他只是将原本在模板层做的事,放到了样式(CSS)层来完成</strong>。</p>
  263. <p>复杂度同力一样不会消失,也不会凭空产生,它总是从一个物体转移到另一个物体或一种形式转为另一种形式。</p>
  264. <p>如果六、七年前的移动网络速度和今天一样快,那么直接上的技术就是响应式设计,APP、SPA就不会流行得这么快。尽管我们可以预见未来这些领域会变得更好,但是更需要的是改变现状。改变现状的同时也需要预见未来的需求。</p>
  265. <h3 id="什么是前端">什么是前端?</h3>
  266. <p>维基百科是这样说的:前端Front-end和后端back-end是描述进程开始和结束的通用词汇。前端作用于采集输入信息,后端进行处理。计算机程序的界面样式,视觉呈现属于前端。</p>
  267. <p>这种说法给人一种很模糊的感觉,但是他说得又很对,它负责视觉展示。在MVC结构或者MVP中,负责视觉显示的部分只有View层,而今天大多数所谓的View层已经超越了View层。前端是一个很神奇的概念,但是而今的前端已经发生了很大的变化。</p>
  268. <p>你引入了Backbone、Angluar,你的架构变成了MVP、MVVM。尽管发生了一些架构上的变化,但是项目的开发并没有因此而发生变化。这其中涉及到了一些职责的问题,如果某一个层级中有太多的职责,那么它是不是加重了一些人的负担?</p>
  269. <h2 id="前端演进史">前端演进史</h2>
  270. <p>过去一直想整理一篇文章来说说前端发展的历史,但是想着这些历史已经被人们所熟知。后来发现并非如此,大抵是幸存者偏见——关注到的都知道这些历史。</p>
  271. <h3 id="数据-模板-样式混合">数据-模板-样式混合</h3>
  272. <p>在有限的前端经验里,我还是经历了那段用Table来作样式的年代。大学期间曾经有偿帮一些公司或者个人开发、维护一些CMS,而Table是当时帮某个网站更新样式接触到的——ASP.Net(maybe)。当时,我们启动这个CMS用的是一个名为<code>aspweb.exe</code>的程序。于是,在我的移动硬盘里找到了下面的代码。</p>
  273. <div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw">&lt;TABLE</span><span class="ot"> cellSpacing=</span><span class="st">0</span><span class="ot"> cellPadding=</span><span class="st">0</span><span class="ot"> width=</span><span class="st">910</span><span class="ot"> align=</span><span class="st">center</span><span class="ot"> border=</span><span class="st">0</span><span class="kw">&gt;</span>
  274. <span class="kw">&lt;TBODY&gt;</span>
  275. <span class="kw">&lt;TR&gt;</span>
  276. <span class="kw">&lt;TD</span><span class="ot"> vAlign=</span><span class="st">top</span><span class="ot"> width=</span><span class="st">188</span><span class="kw">&gt;&lt;TABLE</span><span class="ot"> cellSpacing=</span><span class="st">0</span><span class="ot"> cellPadding=</span><span class="st">0</span><span class="ot"> width=</span><span class="st">184</span><span class="ot"> align=</span><span class="st">center</span><span class="ot"> border=</span><span class="st">0</span><span class="kw">&gt;</span>
  277. <span class="kw">&lt;TBODY&gt;</span>
  278. <span class="kw">&lt;TR&gt;</span>
  279. <span class="kw">&lt;TD&gt;&lt;IMG</span><span class="ot"> src=</span><span class="st">&quot;Images/xxx.gif&quot;</span><span class="ot"> width=</span><span class="st">184</span><span class="kw">&gt;&lt;/TD&gt;&lt;/TR&gt;</span>
  280. <span class="kw">&lt;TR&gt;</span>
  281. <span class="kw">&lt;TD&gt;</span>
  282. <span class="kw">&lt;TABLE</span><span class="ot"> cellSpacing=</span><span class="st">0</span><span class="ot"> cellPadding=</span><span class="st">0</span><span class="ot"> width=</span><span class="st">184</span><span class="ot"> align=</span><span class="st">center</span>
  283. <span class="ot"> background=</span><span class="st">Images/xxx.gif</span><span class="ot"> border=</span><span class="st">0</span><span class="kw">&gt;</span></code></pre></div>
  284. <p>虽然,我也已经在HEAD里找到了现代的雏形——DIV + CSS,然而这仍然是一个Table的年代。</p>
  285. <div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw">&lt;LINK</span><span class="ot"> href=</span><span class="st">&quot;img/xxx.css&quot;</span><span class="ot"> type=</span><span class="st">text/css</span><span class="ot"> rel=</span><span class="st">stylesheet</span><span class="kw">&gt;</span></code></pre></div>
  286. <p><strong>人们一直在说前端很难,问题是你学过么???</strong></p>
  287. <p><strong>人们一直在说前端很难,问题是你学过么???</strong></p>
  288. <p><strong>人们一直在说前端很难,问题是你学过么???</strong></p>
  289. <p>也许,你也一直在说CSS不好写,但是CSS真的不好写么?人们总在说JS很难用,但是你学过么?只在需要的时候才去学,那肯定很难。<strong>你不曾花时间去学习一门语言,但是却能直接写出可以work的代码,说明他们容易上手</strong>。如果你看过一些有经验的Ruby、Scala、Emacs Lisp开发者写出来的代码,我想会得到相同的结论。有一些语言可以让写程序的人Happy,但是看的人可能就不Happy了。做事的方法不止一种,但是不是所有的人都要用那种方法去做。</p>
  290. <p>过去的那些程序员都是<strong>真正的全栈程序员</strong>,这些程序员不仅仅做了前端的活,还做了数据库的工作。</p>
  291. <div class="sourceCode"><pre class="sourceCode sql"><code class="sourceCode sql"><span class="kw">Set</span> rs = Server.CreateObject(<span class="ot">&quot;ADODB.Recordset&quot;</span>)
  292. sql = <span class="ot">&quot;select id,title,username,email,qq,adddate,content,Re_content,home,face,sex from Fl_Book where ispassed=1 order by id desc&quot;</span>
  293. rs.open sql, Conn, <span class="dv">1</span>, <span class="dv">1</span>
  294. fl.SqlQueryNum = fl.SqlQueryNum + <span class="dv">1</span></code></pre></div>
  295. <p>在这个ASP文件里,它从数据库里查找出了数据,然后Render出HTML。如果可以看到历史版本,那么我想我会看到有一个作者将style=“”的代码一个个放到css文件中。</p>
  296. <p>在这里的代码里也免不了有动态生成JavaScript代码的方法:</p>
  297. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript">show_other <span class="op">=</span> <span class="st">&quot;&lt;SCRIPT language=javascript&gt;&quot;</span>
  298. show_other <span class="op">=</span> show_other <span class="op">&amp;</span> <span class="st">&quot;function checkform()&quot;</span>
  299. show_other <span class="op">=</span> show_other <span class="op">&amp;</span> <span class="st">&quot;{&quot;</span>
  300. show_other <span class="op">=</span> show_other <span class="op">&amp;</span> <span class="st">&quot;if (document.add.title.value==&#39;&#39;)&quot;</span>
  301. show_other <span class="op">=</span> show_other <span class="op">&amp;</span> <span class="st">&quot;{&quot;</span></code></pre></div>
  302. <p>请尽情嘲笑,然后再看一段代码:</p>
  303. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="im">import</span> React <span class="im">from</span> <span class="st">&quot;react&quot;</span><span class="op">;</span>
  304. <span class="im">import</span> <span class="op">{</span> getData <span class="op">}</span> <span class="im">from</span> <span class="st">&quot;../../common/request&quot;</span><span class="op">;</span>
  305. <span class="im">import</span> styles <span class="im">from</span> <span class="st">&quot;./style.css&quot;</span><span class="op">;</span>
  306. <span class="im">export</span> <span class="im">default</span> <span class="kw">class</span> HomePage <span class="kw">extends</span> <span class="va">React</span>.<span class="at">Component</span> <span class="op">{</span>
  307. <span class="at">componentWillMount</span>() <span class="op">{</span>
  308. <span class="va">console</span>.<span class="at">log</span>(<span class="st">&quot;[HomePage] will mount with server response: &quot;</span><span class="op">,</span> <span class="kw">this</span>.<span class="va">props</span>.<span class="va">data</span>.<span class="at">home</span>)<span class="op">;</span>
  309. <span class="op">}</span>
  310. <span class="at">render</span>() <span class="op">{</span>
  311. <span class="kw">let</span> <span class="op">{</span> title <span class="op">}</span> <span class="op">=</span> <span class="kw">this</span>.<span class="va">props</span>.<span class="va">data</span>.<span class="at">home</span><span class="op">;</span>
  312. <span class="cf">return</span> (
  313. <span class="op">&lt;</span>div className<span class="op">={</span><span class="va">styles</span>.<span class="at">content</span><span class="op">}&gt;</span>
  314. <span class="op">&lt;</span>h1<span class="op">&gt;{</span>title<span class="op">}&lt;</span><span class="ss">/h1&gt;</span>
  315. <span class="ss"> &lt;p className={styles.welcomeText}&gt;Thanks for joining!&lt;/p</span><span class="op">&gt;</span>
  316. <span class="op">&lt;</span><span class="ss">/div&gt;</span>
  317. <span class="ss"> </span><span class="sc">)</span><span class="ss">;</span>
  318. <span class="ss"> }</span>
  319. <span class="ss"> static fetchData = function</span><span class="sc">(</span><span class="ss">params</span><span class="sc">)</span><span class="ss"> {</span>
  320. <span class="ss"> return getData</span><span class="sc">(</span><span class="ss">&quot;/home</span><span class="st">&quot;);</span>
  321. <span class="op">}</span>
  322. <span class="op">}</span></code></pre></div>
  323. <p>10年前和10年后的代码,似乎没有太多的变化。有所不同的是数据层已经被独立出去了,如果你的component也混合了数据层,即直接查询数据库而不是调用数据层接口,那么你就需要好好思考下这个问题。你只是在追随潮流,还是在改变。用一个View层更换一个View层,用一个Router换一个Router的意义在哪?</p>
  324. <h3 id="model-view-controller">Model-View-Controller</h3>
  325. <p>人们在不断地反思这其中复杂的过程,整理了一些好的架构模式,其中不得不提到的是我司Martin Folwer的《企业应用架构模式》。该书中文译版出版的时候是2004年,那时对于系统的分层是</p>
  326. <table>
  327. <thead>
  328. <tr class="header">
  329. <th>层次</th>
  330. <th>职责</th>
  331. </tr>
  332. </thead>
  333. <tbody>
  334. <tr class="odd">
  335. <td>表现层</td>
  336. <td>提供服务、显示信息、用户请求、HTTP请求和命令行调用。</td>
  337. </tr>
  338. <tr class="even">
  339. <td>领域层</td>
  340. <td>逻辑处理,系统中真正的核心。</td>
  341. </tr>
  342. <tr class="odd">
  343. <td>数据层</td>
  344. <td>与数据库、消息系统、事物管理器和其他软件包通讯。</td>
  345. </tr>
  346. </tbody>
  347. </table>
  348. <p>化身于当时最流行的Spring,就是MVC。人们有了iBatis这样的数据持久层框架,即ORM,对象关系映射。于是,你的package就会有这样的几个文件夹:</p>
  349. <pre><code>|____mappers
  350. |____model
  351. |____service
  352. |____utils
  353. |____controller</code></pre>
  354. <p>在mappers这一层,我们所做的莫过于如下所示的数据库相关查询:</p>
  355. <div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="at">@Insert</span>(
  356. <span class="st">&quot;INSERT INTO users(username, password, enabled) &quot;</span> +
  357. <span class="st">&quot;VALUES (#{userName}, #{passwordHash}, #{enabled})&quot;</span>
  358. )
  359. <span class="at">@Options</span>(keyProperty = <span class="st">&quot;id&quot;</span>, keyColumn = <span class="st">&quot;id&quot;</span>, useGeneratedKeys = <span class="kw">true</span>)
  360. <span class="dt">void</span> <span class="fu">insert</span>(User user);</code></pre></div>
  361. <p>model文件夹和mappers文件夹都是数据层的一部分,只是两者间的职责不同,如:</p>
  362. <div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">public</span> <span class="bu">String</span> <span class="fu">getUserName</span>() {
  363. <span class="kw">return</span> userName;
  364. }
  365. <span class="kw">public</span> <span class="dt">void</span> <span class="fu">setUserName</span>(<span class="bu">String</span> userName) {
  366. <span class="kw">this</span>.<span class="fu">userName</span> = userName;
  367. }</code></pre></div>
  368. <p>而他们最后都需要在Controller,又或者称为ModelAndView中处理:</p>
  369. <div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="at">@RequestMapping</span>(value = {<span class="st">&quot;/disableUser&quot;</span>}, method = RequestMethod.<span class="fu">POST</span>)
  370. <span class="kw">public</span> ModelAndView <span class="fu">processUserDisable</span>(HttpServletRequest request, ModelMap model) {
  371. <span class="bu">String</span> userName = request.<span class="fu">getParameter</span>(<span class="st">&quot;userName&quot;</span>);
  372. User user = userService.<span class="fu">getByUsername</span>(userName);
  373. userService.<span class="fu">disable</span>(user);
  374. <span class="bu">Map</span>&lt;<span class="bu">String</span>,User&gt; map = <span class="kw">new</span> <span class="bu">HashMap</span>&lt;<span class="bu">String</span>,User&gt;();
  375. <span class="bu">Map</span> &lt;User,<span class="bu">String</span>&gt; usersWithRoles= userService.<span class="fu">getAllUsersWithRole</span>();
  376. model.<span class="fu">put</span>(<span class="st">&quot;usersWithRoles&quot;</span>,usersWithRoles);
  377. <span class="kw">return</span> <span class="kw">new</span> <span class="fu">ModelAndView</span>(<span class="st">&quot;redirect:users&quot;</span>,map);
  378. }</code></pre></div>
  379. <p>在多数时候,Controller不应该直接与数据层的一部分,而将业务逻辑放在Controller层又是一种错误,这时就有了Service层,如下图:</p>
  380. <figure>
  381. <img src="./img/frontend/service-mvc.png" alt="Service MVC" /><figcaption>Service MVC</figcaption>
  382. </figure>
  383. <p>然而对于Domain相关的Service应该放在哪一层,总会有不同的意见:</p>
  384. <figure>
  385. <img src="./img/frontend/mvcplayer.gif" alt="MVC Player" /><figcaption>MVC Player</figcaption>
  386. </figure>
  387. <figure>
  388. <img src="./img/frontend/ms-mvc.png" alt="MS MVC" /><figcaption>MS MVC</figcaption>
  389. </figure>
  390. <p>Domain(业务)是一个相当复杂的层级,这里是业务的核心。一个合理的Controller只应该做自己应该做的事,它不应该处理业务相关的代码:</p>
  391. <div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">if</span> (isNewnameEmpty == <span class="kw">false</span> &amp;&amp; newuser == <span class="kw">null</span>){
  392. user.<span class="fu">setUserName</span>(newUsername);
  393. <span class="bu">List</span>&lt;Post&gt; myPosts = postService.<span class="fu">findMainPostByAuthorNameSortedByCreateTime</span>(principal.<span class="fu">getName</span>());
  394. <span class="kw">for</span> (<span class="dt">int</span> k = <span class="dv">0</span>;k &lt; myPosts.<span class="fu">size</span>();k++){
  395. Post post = myPosts.<span class="fu">get</span>(k);
  396. post.<span class="fu">setAuthorName</span>(newUsername);
  397. postService.<span class="fu">save</span>(post);
  398. }
  399. userService.<span class="fu">update</span>(user);
  400. Authentication oldAuthentication = SecurityContextHolder.<span class="fu">getContext</span>().<span class="fu">getAuthentication</span>();
  401. Authentication authentication = <span class="kw">null</span>;
  402. <span class="kw">if</span>(oldAuthentication == <span class="kw">null</span>){
  403. authentication = <span class="kw">new</span> <span class="fu">UsernamePasswordAuthenticationToken</span>(newUsername,user.<span class="fu">getPasswordHash</span>());
  404. }<span class="kw">else</span>{
  405. authentication = <span class="kw">new</span> <span class="fu">UsernamePasswordAuthenticationToken</span>(newUsername,user.<span class="fu">getPasswordHash</span>(),oldAuthentication.<span class="fu">getAuthorities</span>());
  406. }
  407. SecurityContextHolder.<span class="fu">getContext</span>().<span class="fu">setAuthentication</span>(authentication);
  408. map.<span class="fu">clear</span>();
  409. map.<span class="fu">put</span>(<span class="st">&quot;user&quot;</span>,user);
  410. model.<span class="fu">addAttribute</span>(<span class="st">&quot;myPosts&quot;</span>, myPosts);
  411. model.<span class="fu">addAttribute</span>(<span class="st">&quot;namesuccess&quot;</span>, <span class="st">&quot;User Profile updated successfully&quot;</span>);
  412. <span class="kw">return</span> <span class="kw">new</span> <span class="fu">ModelAndView</span>(<span class="st">&quot;user/profile&quot;</span>, map);
  413. }</code></pre></div>
  414. <p>我们在Controller层应该做的事是:</p>
  415. <ol type="1">
  416. <li>处理请求的参数</li>
  417. <li>渲染和重定向</li>
  418. <li>选择Model和Service</li>
  419. <li>处理Session和Cookies</li>
  420. </ol>
  421. <p>业务是善变的,昨天我们可能还在和对手竞争谁先推出新功能,但是今天可能已经合并了。我们很难预见业务变化,但是我们应该能预见Controller是不容易变化的。在一些设计里面,这种模式就是Command模式。</p>
  422. <p>View层是一直在变化的层级,人们的品味一直在更新,有时甚至可能因为竞争对手而产生变化。在已经取得一定市场的情况下,Model-Service-Controller通常都不太会变动,甚至不敢变动。企业意识到创新的两面性,要么带来死亡,要么占领更大的市场。但是对手通常都比你想象中的更聪明一些,所以这时<strong>开创新的业务是一个更好的选择</strong>。</p>
  423. <p>高速发展期的企业和发展初期的企业相比,更需要前端开发人员。在用户基数不够、业务待定的情形中,View只要可用并美观就行了,这时可能就会有大量的业务代码放在View层:</p>
  424. <div class="sourceCode"><pre class="sourceCode jsp"><code class="sourceCode jsp"><span class="kw">&lt;c:choose&gt;</span>
  425. <span class="kw">&lt;c:when</span><span class="ot"> test</span>=<span class="dt">&quot;</span>${ hasError }<span class="dt">&quot;</span><span class="kw">&gt;</span>
  426. &lt;p<span class="ot"> class</span>=<span class="dt">&quot;prompt-error&quot;</span>&gt;
  427. ${errors.username} ${errors.password}
  428. &lt;/p&gt;
  429. <span class="kw">&lt;/c:when&gt;</span>
  430. <span class="kw">&lt;c:otherwise&gt;</span>
  431. &lt;p<span class="ot"> class</span>=<span class="dt">&quot;prompt&quot;</span>&gt;
  432. Woohoo, User &lt;span<span class="ot"> class</span>=<span class="dt">&quot;username&quot;</span>&gt;${user.userName}&lt;/span&gt; has been created successfully!
  433. &lt;/p&gt;
  434. <span class="kw">&lt;/c:otherwise&gt;</span>
  435. <span class="kw">&lt;/c:choose&gt;</span> </code></pre></div>
  436. <p>不同的情形下,人们都会对此有所争议,但只要符合当前的业务便是最好的选择。作为一个前端开发人员,在过去我需要修改JSP、PHP文件,这期间我需要去了解这些Template:</p>
  437. <div class="sourceCode"><pre class="sourceCode php"><code class="sourceCode php">{<span class="kw">foreach</span> <span class="kw">$lists</span> <span class="kw">as</span> <span class="kw">$v</span>}
  438. &lt;li itemprop=<span class="st">&quot;breadcrumb&quot;</span>&gt;&lt;span{<span class="kw">if</span><span class="ot">(</span>newest<span class="ot">(</span><span class="kw">$v</span><span class="ot">[</span><span class="st">&#39;addtime&#39;</span><span class="ot">],</span><span class="dv">24</span><span class="ot">))</span>} style=<span class="st">&quot;color:red&quot;</span>{/<span class="kw">if</span>}&gt;<span class="ot">[</span>{fun <span class="fu">date</span><span class="ot">(</span><span class="st">&#39;Y-m-d&#39;</span><span class="ot">,</span><span class="kw">$v</span><span class="ot">[</span><span class="st">&#39;addtime&#39;</span><span class="ot">])</span>}<span class="ot">]</span>&lt;/span&gt;&lt;a href=<span class="st">&quot;</span><span class="kw">{$v[&#39;url&#39;]}</span><span class="st">&quot;</span> style=<span class="st">&quot;</span><span class="kw">{$v[&#39;style&#39;]}</span><span class="st">&quot;</span> target=<span class="st">&quot;_blank&quot;</span>&gt;{<span class="kw">$v</span><span class="ot">[</span><span class="st">&#39;title&#39;</span><span class="ot">]</span>}&lt;/a&gt;&lt;/li&gt;
  439. {/<span class="kw">foreach</span>}</code></pre></div>
  440. <p>有时像Django这一类,自称为Model-Template-View的框架,更容易让人理解其意图:</p>
  441. <div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html">{% for blog_post in blog_posts.object_list %}
  442. {% block blog_post_list_post_title %}
  443. <span class="kw">&lt;section</span><span class="ot"> class=</span><span class="st">&quot;section--center mdl-grid mdl-grid--no-spacing mdl-shadow--2dp mdl-cell--11-col blog-list&quot;</span><span class="kw">&gt;</span>
  444. {% editable blog_post.title %}
  445. <span class="kw">&lt;div</span><span class="ot"> class=</span><span class="st">&quot;mdl-card__title mdl-card--border mdl-card--expand&quot;</span><span class="kw">&gt;</span>
  446. <span class="kw">&lt;h2</span><span class="ot"> class=</span><span class="st">&quot;mdl-card__title-text&quot;</span><span class="kw">&gt;</span>
  447. <span class="kw">&lt;a</span><span class="ot"> href=</span><span class="st">&quot;{{ blog_post.get_absolute_url }}&quot;</span><span class="ot"> itemprop=</span><span class="st">&quot;headline&quot;</span><span class="kw">&gt;</span>{{ blog_post.title }} › <span class="kw">&lt;/a&gt;</span>
  448. <span class="kw">&lt;/h2&gt;</span>
  449. <span class="kw">&lt;/div&gt;</span>
  450. {% endeditable %}
  451. {% endblock %}</code></pre></div>
  452. <p>作为一个前端人员,我们真正在接触的是View层和Template层,但是MVC并没有说明这些。</p>
  453. <h3 id="从桌面版到移动版">从桌面版到移动版</h3>
  454. <p>Wap出现了,并带来了更多的挑战。随后,分辨率从1024x768变成了176×208,开发人员不得不面临这些挑战。当时所需要做的仅仅是修改View层,而View层随着iPhone的出现又发生了变化。</p>
  455. <figure>
  456. <img src="./img/frontend/wap.gif" alt="WAP 网站" /><figcaption>WAP 网站</figcaption>
  457. </figure>
  458. <p>这是一个短暂的历史,PO还需要为手机用户制作一个怎样的网站?于是他们把桌面版的网站搬了过去变成了移动版。由于网络的原因,每次都需要重新加载页面,这带来了不佳的用户体验。</p>
  459. <p>幸运的是,人们很快意识到了这个问题,于是就有了SPA。<strong>如果当时的移动网络速度可以更快的话,我想很多SPA框架就不存在了</strong>。</p>
  460. <p>先说说jQuery Mobile,在那之前,先让我们来看看两个不同版本的代码,下面是一个手机版本的blog详情页:</p>
  461. <div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw">&lt;ul</span><span class="ot"> data-role=</span><span class="st">&quot;listview&quot;</span><span class="ot"> data-inset=</span><span class="st">&quot;true&quot;</span><span class="ot"> data-splittheme=</span><span class="st">&quot;a&quot;</span><span class="kw">&gt;</span>
  462. {% for blog_post in blog_posts.object_list %}
  463. <span class="kw">&lt;li&gt;</span>
  464. {% editable blog_post.title blog_post.publish_date %}
  465. <span class="kw">&lt;h2</span><span class="ot"> class=</span><span class="st">&quot;blog-post-title&quot;</span><span class="kw">&gt;&lt;a</span><span class="ot"> href=</span><span class="st">&quot;{% url &quot;</span><span class="er">blog_post_detail&quot;</span><span class="ot"> blog_post.slug</span> <span class="er">%}&quot;</span><span class="kw">&gt;</span>{{ blog_post.title }}<span class="kw">&lt;/a&gt;&lt;/h2&gt;</span>
  466. <span class="kw">&lt;em</span><span class="ot"> class=</span><span class="st">&quot;since&quot;</span><span class="kw">&gt;</span>{% blocktrans with sometime=blog_post.publish_date|timesince %}{{ sometime }} ago{% endblocktrans %}<span class="kw">&lt;/em&gt;</span>
  467. {% endeditable %}
  468. <span class="kw">&lt;/li&gt;</span>
  469. {% endfor %}
  470. <span class="kw">&lt;/ul&gt;</span></code></pre></div>
  471. <p>而下面是桌面版本的片段:</p>
  472. <div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html">{% for blog_post in blog_posts.object_list %}
  473. {% block blog_post_list_post_title %}
  474. {% editable blog_post.title %}
  475. <span class="kw">&lt;h2&gt;</span>
  476. <span class="kw">&lt;a</span><span class="ot"> href=</span><span class="st">&quot;{{ blog_post.get_absolute_url }}&quot;</span><span class="kw">&gt;</span>{{ blog_post.title }}<span class="kw">&lt;/a&gt;</span>
  477. <span class="kw">&lt;/h2&gt;</span>
  478. {% endeditable %}
  479. {% endblock %}
  480. {% block blog_post_list_post_metainfo %}
  481. {% editable blog_post.publish_date %}
  482. <span class="kw">&lt;h6</span><span class="ot"> class=</span><span class="st">&quot;post-meta&quot;</span><span class="kw">&gt;</span>
  483. {% trans &quot;Posted by&quot; %}:
  484. {% with blog_post.user as author %}
  485. <span class="kw">&lt;a</span><span class="ot"> href=</span><span class="st">&quot;{% url &quot;</span><span class="er">blog_post_list_author&quot;</span><span class="ot"> author</span> <span class="er">%}&quot;</span><span class="kw">&gt;</span>{{ author.get_full_name|default:author.username }}<span class="kw">&lt;/a&gt;</span>
  486. {% endwith %}
  487. {% with blog_post.categories.all as categories %}
  488. {% if categories %}
  489. {% trans &quot;in&quot; %}
  490. {% for category in categories %}
  491. <span class="kw">&lt;a</span><span class="ot"> href=</span><span class="st">&quot;{% url &quot;</span><span class="er">blog_post_list_category&quot;</span><span class="ot"> category.slug</span> <span class="er">%}&quot;</span><span class="kw">&gt;</span>{{ category }}<span class="kw">&lt;/a&gt;</span>{% if not forloop.last %}, {% endif %}
  492. {% endfor %}
  493. {% endif %}
  494. {% endwith %}
  495. {% blocktrans with sometime=blog_post.publish_date|timesince %}{{ sometime }} ago{% endblocktrans %}
  496. <span class="kw">&lt;/h6&gt;</span>
  497. {% endeditable %}
  498. {% endblock %}</code></pre></div>
  499. <p>人们所做的只是<strong>重载View层</strong>。这也是一个有效的SEO策略,上面这些代码是我博客过去的代码。对于桌面版和移动版都是不同的模板和不同的JS、CSS。</p>
  500. <figure>
  501. <img src="./img/frontend/mobile-web.png" alt="移动版网页" /><figcaption>移动版网页</figcaption>
  502. </figure>
  503. <p>在这一时期,桌面版和移动版的代码可能在同一个代码库中。他们使用相同的代码,调用相同的逻辑,只是View层不同了。但是,每次改动我们都要维护两份代码。</p>
  504. <p>随后,人们发现了一种更友好的移动版应用——APP。</p>
  505. <h3 id="app与过渡期api">APP与过渡期API</h3>
  506. <p>这是一个艰难的时刻,过去我们的很多API都是在原来的代码库中构建的,即桌面版和移动版一起。我们已经在这个代码库中开发了越来越多的功能,系统开发变得臃肿。如《Linux/Unix设计思想》中所说,这是一个伟大的系统,但是它臃肿而又缓慢。</p>
  507. <p>我们是选择重新开发一个结合第一和第二系统的最佳特性的第三个系统,还是继续臃肿下去。我想你已经有答案了。随后我们就有了APP API,构建出了博客的APP。</p>
  508. <figure>
  509. <img src="./img/frontend/mobile-app.jpg" alt="应用" /><figcaption>应用</figcaption>
  510. </figure>
  511. <p>最开始,人们越来越喜欢用APP,因为与移动版网页相比,其响应速度更快,而且更流畅。对于服务器来说,也是一件好事,因为请求变少了。</p>
  512. <p>但是并非所有的人都会下载APP——<strong>有时只想看看上面有没有需要的东西</strong>。对于刚需不强的应用,人们并不会下载,只会访问网站。</p>
  513. <p>有了APP API之后,我们可以向网页提供API,我们就开始设想要有一个好好的移动版。</p>
  514. <h3 id="过渡期spa">过渡期SPA</h3>
  515. <p>Backbone诞生于2010年,和响应式设计出现在同一个年代里,但他们似乎在同一个时代里火了起来。如果CSS3早点流行开来,似乎就没有Backbone啥事了。不过移动网络还是限制了响应式的流行,只是在今天这些都有所变化。</p>
  516. <p>我们用Ajax向后台请求API,然后Mustache Render出来。因为JavaScript在模块化上的缺陷,所以我们就用Require.JS来进行模块化。</p>
  517. <p>下面的代码就是我在尝试对我的博客进行SPA设计时的代码:</p>
  518. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">define</span>([
  519. <span class="st">&#39;zepto&#39;</span><span class="op">,</span>
  520. <span class="st">&#39;underscore&#39;</span><span class="op">,</span>
  521. <span class="st">&#39;mustache&#39;</span><span class="op">,</span>
  522. <span class="st">&#39;js/ProductsView&#39;</span><span class="op">,</span>
  523. <span class="st">&#39;json!/configure.json&#39;</span><span class="op">,</span>
  524. <span class="st">&#39;text!/templates/blog_details.html&#39;</span><span class="op">,</span>
  525. <span class="st">&#39;js/renderBlog&#39;</span>
  526. ]<span class="op">,</span><span class="kw">function</span>($<span class="op">,</span> _<span class="op">,</span> Mustache<span class="op">,</span> ProductsView<span class="op">,</span> configure<span class="op">,</span> blogDetailsTemplate<span class="op">,</span> GetBlog)<span class="op">{</span>
  527. <span class="kw">var</span> BlogDetailsView <span class="op">=</span> <span class="va">Backbone</span>.<span class="va">View</span>.<span class="at">extend</span> (<span class="op">{</span>
  528. <span class="dt">el</span><span class="op">:</span> <span class="at">$</span>(<span class="st">&quot;#content&quot;</span>)<span class="op">,</span>
  529. <span class="dt">initialize</span><span class="op">:</span> <span class="kw">function</span> () <span class="op">{</span>
  530. <span class="kw">this</span>.<span class="at">params</span> <span class="op">=</span> <span class="st">&#39;#content&#39;</span><span class="op">;</span>
  531. <span class="op">},</span>
  532. <span class="dt">getBlog</span><span class="op">:</span> <span class="kw">function</span>(slug) <span class="op">{</span>
  533. <span class="kw">var</span> getblog <span class="op">=</span> <span class="kw">new</span> <span class="at">GetBlog</span>(<span class="kw">this</span>.<span class="at">params</span><span class="op">,</span> configure[<span class="st">&#39;blogPostUrl&#39;</span>] <span class="op">+</span> slug<span class="op">,</span> blogDetailsTemplate)<span class="op">;</span>
  534. <span class="va">getblog</span>.<span class="at">renderBlog</span>()<span class="op">;</span>
  535. <span class="op">}</span>
  536. <span class="op">}</span>)<span class="op">;</span>
  537. <span class="cf">return</span> BlogDetailsView<span class="op">;</span>
  538. <span class="op">}</span>)<span class="op">;</span></code></pre></div>
  539. <p>从API获取数据,结合Template来Render出Page。但是这无法改变我们需要Client Side Render和Server Side Render的两种Render方式,除非我们可以像淘宝一样不需要考虑SEO——因为它不那么依靠搜索引擎带来流量。</p>
  540. <p>这时,我们还是基于类MVC模式。只是数据的获取方式变成了Ajax,我们就犯了一个错误——将大量的业务逻辑放在前端。这时候我们已经不能再从View层直接访问Model层,从安全的角度来说有点危险。</p>
  541. <p>如果你的View层还可以直接访问Model层,那么说明你的架构还是MVC模式。之前我在Github上构建一个Side Project的时候直接用View层访问了Model层,由于Model层是一个ElasticSearch的搜索引擎,它提供了JSON API,这使得我要在View层处理数据——即业务逻辑。将上述的JSON API放入Controller,尽管会加重这一层的复杂度,但是业务逻辑就不再放置于View层。</p>
  542. <p>如果你在你的View层和Model层总有一层接口,那么你采用的就是MVP模式——MVC模式的衍生(PS:为了区别别的事情,总会有人取个表意的名称)。</p>
  543. <p>一夜之前,我们又回到了过去。我们离开了JSP,将View层变成了Template与Controller。而原有的Services层并不是只承担其原来的责任,这些Services开始向ViewModel改变。</p>
  544. <p>一些团队便将Services抽成多个Services,美其名为微服务。传统架构下的API从下图</p>
  545. <figure>
  546. <img src="./img/frontend/api-gateway.png" alt="API Gateway" /><figcaption>API Gateway</figcaption>
  547. </figure>
  548. <p>变成了直接调用的微服务:</p>
  549. <figure>
  550. <img src="./img/frontend/microservices.png" alt="Micro Services" /><figcaption>Micro Services</figcaption>
  551. </figure>
  552. <p>对于后台开发者来说,这是一件大快人心的大好事,但是对于应用端/前端来说并非如此。调用的服务变多了,在应用程序端进行功能测试变得更复杂,需要Mock的API变多了。</p>
  553. <h3 id="hybird与viewmodel">Hybird与ViewModel</h3>
  554. <p>这时候遇到问题的不仅仅只在前端,而在App端,小的团队已经无法承受开发成本。人们更多的注意力放到了Hybird应用上。Hybird应用解决了一些小团队在开发初期遇到的问题,这部分应用便交给了前端开发者。</p>
  555. <p>前端开发人员先熟悉了单纯的JS + CSS + HTML,又熟悉了Router + PageView + API的结构,现在他们又需要做手机APP。这时候只好用熟悉的jQuer Mobile + Cordova。</p>
  556. <p>随后,人们先从Cordova + jQuery Mobile,变成了Cordova + Angular的 Ionic。在那之前,一些团队可能已经用Angular代换了Backbone。他们需要更好的交互,需要data binding。</p>
  557. <p>接着,我们可以直接将我们的Angular代码从前端移到APP,比如下面这种博客APP的代码:</p>
  558. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> .<span class="at">controller</span>(<span class="st">&#39;BlogCtrl&#39;</span><span class="op">,</span> <span class="kw">function</span> ($scope<span class="op">,</span> Blog) <span class="op">{</span>
  559. <span class="va">$scope</span>.<span class="at">blogs</span> <span class="op">=</span> <span class="kw">null</span><span class="op">;</span>
  560. <span class="va">$scope</span>.<span class="at">blogOffset</span> <span class="op">=</span> <span class="dv">0</span><span class="op">;</span>
  561. <span class="co">//</span>
  562. <span class="va">$scope</span>.<span class="at">doRefresh</span> <span class="op">=</span> <span class="kw">function</span> () <span class="op">{</span>
  563. <span class="va">Blog</span>.<span class="at">async</span>(<span class="st">&#39;https://www.phodal.com/api/v1/app/?format=json&#39;</span>).<span class="at">then</span>(<span class="kw">function</span> (results) <span class="op">{</span>
  564. <span class="va">$scope</span>.<span class="at">blogs</span> <span class="op">=</span> <span class="va">results</span>.<span class="at">objects</span><span class="op">;</span>
  565. <span class="op">}</span>)<span class="op">;</span>
  566. <span class="va">$scope</span>.<span class="at">$broadcast</span>(<span class="st">&#39;scroll.refreshComplete&#39;</span>)<span class="op">;</span>
  567. <span class="va">$scope</span>.<span class="at">$apply</span>()
  568. <span class="op">};</span>
  569. <span class="va">Blog</span>.<span class="at">async</span>(<span class="st">&#39;https://www.phodal.com/api/v1/app/?format=json&#39;</span>).<span class="at">then</span>(<span class="kw">function</span> (results) <span class="op">{</span>
  570. <span class="va">$scope</span>.<span class="at">blogs</span> <span class="op">=</span> <span class="va">results</span>.<span class="at">objects</span><span class="op">;</span>
  571. <span class="op">}</span>)<span class="op">;</span>
  572. <span class="va">$scope</span>.<span class="at">loadMore</span> <span class="op">=</span> <span class="kw">function</span>() <span class="op">{</span>
  573. <span class="va">$scope</span>.<span class="at">blogOffset</span> <span class="op">=</span> <span class="va">$scope</span>.<span class="at">blogOffset</span> <span class="op">+</span> <span class="dv">1</span><span class="op">;</span>
  574. <span class="va">Blog</span>.<span class="at">async</span>(<span class="st">&#39;https://www.phodal.com/api/v1/app/?limit=10&amp;offset=&#39;</span><span class="op">+</span> <span class="va">$scope</span>.<span class="at">blogOffset</span> <span class="op">*</span> <span class="dv">20</span> <span class="op">+</span> <span class="st">&#39;&amp;format=json&#39;</span>).<span class="at">then</span>(<span class="kw">function</span> (results) <span class="op">{</span>
  575. <span class="va">Array</span>.<span class="va">prototype</span>.<span class="va">push</span>.<span class="at">apply</span>(<span class="va">$scope</span>.<span class="at">blogs</span><span class="op">,</span> <span class="va">results</span>.<span class="at">objects</span>)<span class="op">;</span>
  576. <span class="va">$scope</span>.<span class="at">$broadcast</span>(<span class="st">&#39;scroll.infiniteScrollComplete&#39;</span>)<span class="op">;</span>
  577. <span class="op">}</span>)
  578. <span class="op">};</span>
  579. <span class="op">}</span>)</code></pre></div>
  580. <p>结果<strong>时间轴又错了</strong>,人们总是<strong>超前一个时期做错了一个在未来是正确的决定</strong>。人们遇到了网页版的用户授权问题,于是发明了JWT——Json Web Token。</p>
  581. <p>然而,由于WebView在一些早期的Android手机上出现了性能问题,人们开始考虑替换方案。接着出现了两个不同的解决方案:</p>
  582. <ol type="1">
  583. <li>React Native</li>
  584. <li>新的WebView——Crosswalk</li>
  585. </ol>
  586. <p>开发人员开始欢呼React Native这样的框架。但是,他们并没有预见到<strong>人们正在厌恶APP</strong>,APP在我们的迭代里更新着,可能是一星期,可能是两星期,又或者是一个月。谁说APP内自更新不是一件坏事,但是APP的提醒无时无刻不在干扰着人们的生活,噪声越来越多。<strong>不要和用户争夺他们手机的使用权</strong></p>
  587. <h3 id="一次构建跨平台运行">一次构建,跨平台运行</h3>
  588. <p>在我们需要学习C语言的时候,GCC就有了这样的跨平台编译。</p>
  589. <p>在我们开发桌面应用的时候,QT就有了这样的跨平台能力。</p>
  590. <p>在我们构建Web应用的时候,Java就有了这样的跨平台能力。</p>
  591. <p>在我们需要开发跨平台应用的时候,Cordova就有了这样的跨平台能力。</p>
  592. <p>现在,React这样的跨平台框架又出现了,而响应式设计也是跨平台式的设计。</p>
  593. <p>响应式设计不得不提到的一个缺点是:<strong>他只是将原本在模板层做的事,放到了样式(CSS)层</strong>。你还是在针对着不同的设备进行设计,两种没有什么多大的不同。复杂度不会消失,也不会凭空产生,它只会从一个物体转移到另一个物体或一种形式转为另一种形式。</p>
  594. <p>React,将一小部分复杂度交由人来消化,将另外一部分交给了React自己来消化。在用Spring MVC之前,也许我们还在用CGI编程,而Spring降低了这部分复杂度,但是这和React一样降低的只是新手的复杂度。在我们不能以某种语言的方式写某相关的代码时,这会带来诸多麻烦。</p>
  595. <h2 id="repractise-1">RePractise</h2>
  596. <p>如果你是一只辛勤的蜜蜂,那么我想你应该都玩过上面那些技术。你是在练习前端的技术,还是在RePractise?如果你不花点时间整理一下过去,顺便预测一下未来,那么你就是在白搭。</p>
  597. <p>前端的演进在这一年特别快,Ruby On Rails也在一个合适的年代里出现,在那个年代里也流行得特别快。RoR开发效率高的优势已然不再突显,语法灵活性的副作用就是运行效率降低,同时后期维护难——每个人元编程了自己。</p>
  598. <p>如果不能把Controller、Model Mapper变成ViewModel,又或者是Micro Services来解耦,那么ES6 + React只是在现在带来更高的开发效率。而所谓的高效率,只是相比较而意淫出来的,因为他只是一层View层。将Model和Controller再加回View层,以后再拆分出来?</p>
  599. <p>现有的结构只是将View层做了View层应该做的事。</p>
  600. <p>首先,你应该考虑的是一种可以让View层解耦于Domain或者Service层。今天,桌面、平板、手机并不是唯一用户设备,虽然你可能在明年统一了这三个平台,现在新的设备的出现又将设备分成两种类型——桌面版和手机版。一开始桌面版和手机版是不同的版本,后来你又需要合并这两个设备。</p>
  601. <p>其次,你可以考虑用混合Micro Services优势的Monolithic Service来分解业务。如果可以举一个成功的例子,那么就是Linux,一个混合内核的“Service”。</p>
  602. <p>最后,Keep Learning。我们总需要在适当的时候做出改变,尽管我们觉得一个Web应用代码库中含桌面版和移动版代码会很不错,但是在那个时候需要做出改变。</p>
  603. <p>对于复杂的应用来说,其架构肯定不是只有纯MVP或者纯MVVM这么简单的。如果一个应用混合了MVVM、MVP和MVC,那么他也变成了MVC——因为他直接访问了Model层。但是如果细分来看,只有访问了Model层的那一部分才是MVC模式。</p>
  604. <p>模式,是人们对于某个解决方案的描述。在一段代码中可能有各种各样的设计模式,更何况是架构。</p>
  605. <h1 id="后台与服务篇">后台与服务篇</h1>
  606. <p>尽管在最初我也想去写一篇文章来说说后台的发展史,后来想了想还是让我们把它划分成不同的几部分。以便于我们可以更好的说说这些内容,不过相信这是一个好的开始。</p>
  607. <h2 id="restful与服务化">RESTful与服务化</h2>
  608. <h3 id="设计restful-api">设计RESTful API</h3>
  609. <blockquote>
  610. <p>REST从资源的角度来观察整个网络,分布在各处的资源由URI确定,而客户端的应用通过URI来获取资源的表征。获得这些表征致使这些应用程序转变了其状态。随着不断获取资源的表征,客户端应用不断地在转变着其状态,所谓表征状态转移。</p>
  611. </blockquote>
  612. <p>因为我们需要的是一个Machine到Machine沟通的平台,需要设计一个API。而设计一个API来说,RESTful是很不错的一种选择,也是主流的选择。而设计一个RESTful服务,的首要步骤便是设计资源模型。</p>
  613. <h3 id="资源">资源</h3>
  614. <p>互联网上的一切信息都可以看作是一种资源。</p>
  615. <table>
  616. <thead>
  617. <tr class="header">
  618. <th>HTTP Method</th>
  619. <th>Operation Performed</th>
  620. </tr>
  621. </thead>
  622. <tbody>
  623. <tr class="odd">
  624. <td>GET</td>
  625. <td>Get a resource (Read a resource)</td>
  626. </tr>
  627. <tr class="even">
  628. <td>POST</td>
  629. <td>Create a resource</td>
  630. </tr>
  631. <tr class="odd">
  632. <td>PUT</td>
  633. <td>Update a resource</td>
  634. </tr>
  635. <tr class="even">
  636. <td>DELETE</td>
  637. <td>Delete Resource</td>
  638. </tr>
  639. </tbody>
  640. </table>
  641. <p>设计RESTful API是一个有意思的话题。下面是一些常用的RESTful设计原则:</p>
  642. <ul>
  643. <li>组件间交互的可伸缩性</li>
  644. <li>接口的通用性</li>
  645. <li>组件的独立部署</li>
  646. <li>通过中间组件来减少延迟、实施安全策略和封装已有系统</li>
  647. </ul>
  648. <p>判断是否是 RESTful的约束条件</p>
  649. <ul>
  650. <li>客户端-服务器分离</li>
  651. <li>无状态</li>
  652. <li>可缓存</li>
  653. <li>多层系统</li>
  654. <li>统一接口</li>
  655. <li>随需代码(可选)</li>
  656. </ul>
  657. <h2 id="微服务">微服务</h2>
  658. <h3 id="微内核">微内核</h3>
  659. <p>这只是由微服务与传统架构之间对比而引发的一个思考,让我引一些资料来当参考吧.</p>
  660. <blockquote>
  661. <p>单内核:也称为宏内核。将内核从整体上作为一个大过程实现,并同时运行在一个单独的地址空间。所有的内核服务都在一个地址空间运行,相互之间直接调用函数,简单高效。微内核:功能被划分成独立的过程,过程间通过IPC进行通信。模块化程度高,一个服务失效不会影响另外一个服务。Linux是一个单内核结构,同时又吸收了微内核的优点:模块化设计,支持动态装载内核模块。Linux还避免了微内核设计上的缺陷,让一切都运行在内核态,直接调用函数,无需消息传递。</p>
  662. </blockquote>
  663. <p>对就的微内核便是:</p>
  664. <blockquote>
  665. <p>微内核――在微内核中,大部分内核都作为单独的进程在特权状态下运行,他们通过消息传递进行通讯。在典型情况下,每个概念模块都有一个进程。因此,假如在设计中有一个系统调用模块,那么就必然有一个相应的进程来接收系统调用,并和能够执行系统调用的其他进程(或模块)通讯以完成所需任务。</p>
  666. </blockquote>
  667. <p>如果读过《操作系统原理》及其相关书籍的人应该很了解这些,对就的我们就可以一目了然地解决我们当前是的微服务的问题。</p>
  668. <p>文章的来源是James Lewis与Martin Fowler写的<a href="http://martinfowler.com/articles/microservices.html">Microservices</a>。对就于上面的</p>
  669. <ul>
  670. <li>monolithic kernel</li>
  671. <li>microkernel</li>
  672. </ul>
  673. <p>与文中的</p>
  674. <ul>
  675. <li>monolithic services</li>
  676. <li>microservices</li>
  677. </ul>
  678. <p>我们还是将其翻译成<code>微服务</code>与<code>宏服务</code>。</p>
  679. <p>引起原文中对于微服务的解释:</p>
  680. <blockquote>
  681. <p>简短地说,微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,通过轻量的通讯机制联系,经常是基于HTTP资源API,这些服务基于业务能力构建,能够通过自动化部署方式独立部署,这些服务自己有一些小型集中化管理,可以是使用不同的编程语言编写,正如不同的数据存储技术一样。</p>
  682. </blockquote>
  683. <p>原文是:</p>
  684. <blockquote>
  685. <p>In short, the microservice architectural style [1] is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare mininum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.</p>
  686. </blockquote>
  687. <p>而关于微服务的提出是早在2011年的5月份</p>
  688. <blockquote>
  689. <p>The term “microservice” was discussed at a workshop of software architects near Venice in May, 2011 to describe what the participants saw as a common architectural style that many of them had been recently exploring.</p>
  690. </blockquote>
  691. <p>简单地与微内核作一些对比。微内核,<strong>微内核部分经常只但是是个消息转发站</strong>,而微服务从某种意义上也是如此,他们都有着下面的优点。</p>
  692. <ul>
  693. <li>有助于实现模块间的隔离</li>
  694. <li>在不影响系统其他部分的情况下,用更高效的实现代替现有文档系统模块的工作将会更加容易。</li>
  695. </ul>
  696. <p>对于微服务来说</p>
  697. <ul>
  698. <li>每个服务本身都是很简单的</li>
  699. <li>对于每个服务,我们可以选择最好和最合适的工具来开发</li>
  700. <li>系统本质上是松耦合的</li>
  701. <li>不同的团队可以工作在不同的服务中</li>
  702. <li>可以持续发布,而其他部分还是稳定的</li>
  703. </ul>
  704. <p>从某种意义上来说微服务更适合于大型企业架构,而不是一般的应用,对于一般的应用来说他们的都在同一台主机上。无力于支付更多的系统开销,于是如<strong>微服务不是免费的午餐</strong>一文所说</p>
  705. <ul>
  706. <li>微服务带来很多的开销操作</li>
  707. <li>大量的DevOps技能要求</li>
  708. <li>隐式接口</li>
  709. <li>重复努力</li>
  710. <li>分布式系统的复杂性</li>
  711. <li>异步性是困难的!</li>
  712. <li>可测试性挑战</li>
  713. </ul>
  714. <p>因而不得不再后面补充一些所知的额外的东西。</p>
  715. <p>针对于同样的话题,开始了解其中的一些问题。当敏捷的思想贯穿于开发过程时,我们不得不面对持续集成与发布这样的问题。我们确实可以在不同的服务下工作,然而当我们需要修改API时,就对我们的集成带来很多的问题。我们需要同时修改两个API!我们也需要同时部署他们!</p>
  716. <h2 id="混合微服务">混合微服务</h2>
  717. <p>在设计所谓的“Next-Generation CMS”,即Echoes CMS的时候,对于我这种懒得自己写Django App的人来说,通过我会去复制别人的代码,于是我继续在Github上漫游。接着找到了DjangoProject.com的源码,又看了看Mezzanine(ps: 我博客用的就是这个CMS)。于是从DjangoProject复制了Blog的代码,从Mezzanine复制了conf的代码,然后就有了Echoes的codebase。然后,继之前的文章(《微服务的小思考》我想了想, 这不就是我想要的模型么?</p>
  718. <p>微服务与Django</p>
  719. <p>Django 应用架构 Django MVC结构如下如示:</p>
  720. <figure>
  721. <img src="./img/backend/django_mvc.png" alt="Django MVC" /><figcaption>Django MVC</figcaption>
  722. </figure>
  723. <p>然后,记住这张图,忘记上面的MVC,Django实际上是一个MTV</p>
  724. <ul>
  725. <li>Model</li>
  726. <li>Template</li>
  727. <li>View</li>
  728. </ul>
  729. <p>主要是Django中的views.py通常是在做Controller的事。</p>
  730. <p>然而对于一个Django的应用来说,他的架构如下所示:</p>
  731. <figure>
  732. <img src="./img/backend/django-app.jpg" alt="Django apps architecture" /><figcaption>Django apps architecture</figcaption>
  733. </figure>
  734. <p>Django的每个App就代表着程序的一个功能。每个App有自己的models、views、urls、templates所以对于一个app来说他的结构如下:</p>
  735. <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">.</span>
  736. <span class="kw">|</span><span class="ex">______init__.py</span>
  737. <span class="kw">|</span><span class="ex">____models.py</span>
  738. <span class="kw">|</span><span class="ex">____tests.py</span>
  739. <span class="kw">|</span><span class="ex">____views.py</span></code></pre></div>
  740. <p>如果是新版的Django那么它的结构如下:</p>
  741. <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">.</span>
  742. <span class="kw">|</span><span class="ex">______init__.py</span>
  743. <span class="kw">|</span><span class="ex">____admin.py</span>
  744. <span class="kw">|</span><span class="ex">____migrations</span>
  745. <span class="kw">|</span> <span class="kw">|</span><span class="ex">______init__.py</span>
  746. <span class="kw">|</span><span class="ex">____models.py</span>
  747. <span class="kw">|</span><span class="ex">____tests.py</span>
  748. <span class="kw">|</span><span class="ex">____views.py</span></code></pre></div>
  749. <p>上面少了templates,最后会有一个总的URL,即第一张图的URL Dispatcher。接着,让我们看看微服务是怎样的。</p>
  750. <p>一个典型的微服务如下所示:</p>
  751. <figure>
  752. <img src="./img/backend/microservices_a.jpg" alt="microservices architecture" /><figcaption>microservices architecture</figcaption>
  753. </figure>
  754. <p>有不同的技术栈python、spring、scala,但是他们看上去和Django应用的图差不多,除了数据库不一样。</p>
  755. <p>与其将复杂的测试、逻辑部分变得不可测,不如把这些部分放置于系统内部。</p>
  756. <figure>
  757. <img src="./img/backend/linux_os.jpg" alt="Linux OS Hybrid" /><figcaption>Linux OS Hybrid</figcaption>
  758. </figure>
  759. <p>当我们在我们的服务器上部署微服务的时候,也就意味着实现所以的服务都是在我们系统的内部,我们有一个Kernel以及他们的Kernel Moduels,即微服务群们。他们调用DB,或者某些第三方服务。</p>
  760. <p>System Libraries相当于我们的URL Dispatcher。而我们的URL Dispatcher实际上所做的便是将各自调用的服务指向各自的app。</p>
  761. <p>这样我们即可以解决部署的问题,又可以减少内部耦合。</p>
  762. <h2 id="其他">其他</h2>
  763. <blockquote>
  764. <p>我猜,微服务的流行是因为程序员可以欢乐地使用自己的语言,哪怕是Logo。</p>
  765. </blockquote>
  766. <p>参考</p>
  767. <p><a href="http://highscalability.com/blog/2014/4/8/microservices-not-a-free-lunch.html">Microservices - Not A Free Lunch!</a></p>
  768. <p><a href="http://martinfowler.com/articles/microservices.html">Microservices</a></p>
  769. <h1 id="前后端篇">前后端篇</h1>
  770. <h2 id="前后端分离">前后端分离</h2>
  771. <p>这是一个很古老的话题,对于大公司来说就是部门大了,需要拆分。因此开始之前,先提一下“康威定律”:</p>
  772. <blockquote>
  773. <p>Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations.</p>
  774. </blockquote>
  775. <p>换成中文,即:<strong>设计系统的组织,其产生的设计和架构等价于组织间的沟通结构</strong>。上图</p>
  776. <figure>
  777. <img src="./img/front-back-end/conway.jpg" alt="Conway" /><figcaption>Conway</figcaption>
  778. </figure>
  779. <p>这张图可以解释相当多的软件开发过程中的问题,而我们知道软件开发的主要问题是沟通问题。组织结构影响了我们的沟通结构,进而影响了我们的软件系统结构。好吧,我承认可能离题有点远。不过,我想说的是组织结构可能不允许我们做出一些好的系统架构。</p>
  780. <p>如我们在《<a href="http://mp.weixin.qq.com/s?src=3&amp;timestamp=1467515714&amp;ver=1&amp;signature=z1onJvKn4TSrUmXm384CQUF1IZBVsLShsQ4DpmumN6xY0Gm5RR9XKdbf6ELzdRqg-mxdtxceTg-4-KrhYHZQC6wiSEWsP64vh0sl2Je4G16hnS6MsuZaD-u01HAENCSKuw3mQL-F2Gc5WYvti9SQlw==">RePractise前端篇: 前端演进史</a>》中提到的那样:我们已经有了一个桌面版网页,然后我们打造了一个APP。然而,总有些客户想在手机上浏览但是又不想下APP,我们就需要一个移动版。为什么会这样?因为用户已经被养成了这样的习惯,大部分的网站提到了桌面版、移动版、APP。要维护这样的三个不同的系统,对于大部分的业务公司来说成本太高了。</p>
  781. <p>于是,大部分公司来说解决方案就是 后台 + 大前端 (桌面前端、移动Web、手机APP)。Angular和React就是为了解决这样的问题,而出现了不同的解决方案——基于Angular.js的混合应用框架Ionic、以及React Native。不过在当前,我对React Native的共用UI还是持观望态度。有人可能会提到Vue和Weex,但是我觉得并没有那么好用。或许是因为我接触React比较早,我觉得Vue的语法四不像。</p>
  782. <p>在这样的情形下,我们只需要几个后台开发人员和几个前端开发人员就可以完成系统的设计了。这种前端开发人员就是最近几年人们“最想要”的。</p>
  783. <h2 id="单页面应用后台渲染">单页面应用后台渲染</h2>
  784. <p>我已经想不到一个好的关于前端分享的主题了,于是联想到最近想要做的一件事,就想到了这个标题。或许这是一个好的主题,又或许这不是一个好的主题。但是至少我可以Share一下我的经验:</p>
  785. <ul>
  786. <li>基于Mustache模板引擎的前后台渲染。</li>
  787. <li>基于PreRender方式的Angular.js应用的后台渲染</li>
  788. <li>服务端渲染的React</li>
  789. </ul>
  790. <p>开始之前,我希望即使你们需要后台渲染,你们也应该前后端分离!由<strong>后台</strong>来提供API数据,前端用自己的后台来渲染页面。听上去有点绕,简单的来说就是不要把大量的业务逻辑放前台来,只把显示逻辑放在前台上。这样一来,即使有一天我们换了新的前端,如移动应用,那么我们的后台也是可用的。。</p>
  791. <h3 id="前后台渲染同一模板">前后台渲染同一模板</h3>
  792. <p>我接触的第一个SPA应用是一个基于Spring MVC和Backbone的移动网站,但是它比一般的SPA应该要复杂——由于SEO的缘故,它需要支持后台渲染。</p>
  793. <p>当搜索引擎通过URL访问我们的网站的时候,我们就需要返回相应的HTML。这意味着我们需要在后台有对应的模板引擎来支持,而由于SPA的性质又决定了,这需要使用一个纯前端的模板引擎。因此,我们并不能使用两个模板引擎来做这件事,维护两套模板注定会是一件痛苦的事,并且当时还没有React这种模板引擎在。不过,后来我们发现维护两种不同的渲染方式也是一件痛苦的事。因此,我们就会有了类似于下图的架构:</p>
  794. <figure>
  795. <img src="./img/front-back-end/spring-backbone.png" alt="Spring MVC Backbone" /><figcaption>Spring MVC Backbone</figcaption>
  796. </figure>
  797. <p>我们在后台使用Spring MVC作为基础架构、Mustache作为模板引擎,和使用JSP作为模板引擎相比没有多大的区别——由Controller去获取对应的Model,再渲染给用户。多数时候搜索引擎都是依据Sitemap来进行索引的,所以我们的后台很容易就可以处理这些请求。同样的当用户访问相应的页面的时候,也返回同样的页面内容。当完成页面渲染的时候,就交由Backbone来处理相应的逻辑了。换句话来说,从这时候它就变成了一个单页面应用。</p>
  798. <p>尽管这是一个三年年前开始的项目,但是在今天看来,这种做法仍然相应地有趣: 大部分的单页面应用只有一个首页,并由HTTP服务器(如Nginx)、Web框架(如Express、Koa)对路由做一些处理,可以让用户通过特定地URL访问特定地页面。而我们需要保证所有的用户访问地都是真实的页面,既然JavaScript没有加载完,用户也能看到完整的页面。</p>
  799. <p>在这个项目里,最大的挑战就是如何保证后台渲染和前台渲染的业务逻辑是一样的。如当我们想要针对不同的产品显示不同的内容时,我们就需要在JavaScript中赋予一些逻辑,我们还需要在Java在有同样的逻辑。相比于在同一个代码里有桌面版、移动版来说,逻辑有更加复杂的趋势——因为在这种情况下,我们只需要维护两个不同的模板即可。而在SPA的情况下,我们要维护<strong>两套逻辑</strong>。后来,这个框架交由下文中的React与响应式设计重写。</p>
  800. <p>在今天你仍然可以使用这样的方式来渲染,JDK 1.8自带了嵌入式JavaScript引擎Nashorn,完成支持ECMAScript 5.1规范以及一些扩展。</p>
  801. <h3 id="prerender方式">PreRender方式</h3>
  802. <p>在我们重新设计系统的时候,曾经考虑过类似的做法。将我们的所有页面渲染成静态的HTML,然后用爬虫抓取我们的所有页面,再上传到AWS即可。当时我们咨询了其他小组的做法,其中有一个小组正是采用了这种PreRender的方式——在本地运行起一个Server,由PhantomJS来渲染页面,再保存为对应的HTML。</p>
  803. <p>PreRender就是预先渲染好HTML,并针对于爬虫返回特定的HTML。(PS:不过作为一个很有经验的SEO开发人员,我一点不喜欢这种作法。要知道Google有时候会模拟成真实的用户,不带有爬虫的那些参数和标志,去访问页面。如果你返回给Google的两个页面差异太大——可能是你忘记更新了频率,那么Google可能就会认为你在<strong>作弊</strong>。)</p>
  804. <figure>
  805. <img src="./img/front-back-end/angular-prerender.jpg" alt="PreRender" /><figcaption>PreRender</figcaption>
  806. </figure>
  807. <p>对于一般用户来说就不会返回后台渲染的结果了:</p>
  808. <figure>
  809. <img src="./img/front-back-end/angular-phantomjs-prereder.jpg" alt="Angular PreRender" /><figcaption>Angular PreRender</figcaption>
  810. </figure>
  811. <p>和上面的第一种情况相比,这种作法可以大大减少服务器地负担,并且可以直接交由CDN就可以了。这时我们只需要考虑要渲染哪些页面即可,对于数据量比较少的网站来说这是一个不错的做法,但是多了就不一样了。</p>
  812. <p>对于我们来说,有两个问题:一个是速度的问题,他们有上万条数据就需要近一天左右的时间来生成(渲染时间长),而我们有上百万条数据。二是数据的实时问题,我们的产品数据每天都会更新一次。</p>
  813. <h3 id="react">React</h3>
  814. <p>对于使用React的开发人员来说,要处理后台渲染就是一种更简单的事,毕竟React中提供了一个方法叫 renderToString()。我们所要做的就是用Express或者Koa对路由进行处理,然后返回对应的内容即可:</p>
  815. <figure>
  816. <img src="./img/front-back-end/react-server-side-render.png" alt="React Server Side Render" /><figcaption>React Server Side Render</figcaption>
  817. </figure>
  818. <p>然后,剩下的事都可以交由React来解决,就是这么简单。</p>
  819. <p>因为在这个时候我们在前后台使用的都是JavaScript,我们可以在这个地方直接实现对数据库的操作,就会出现我们在开头说到的前后台分离的问题。这样做并不合理,后台只应该返回我们所需要的数据,并且它可以随时被其他语言替换掉。</p>
  820. <h1 id="从真实世界到前后端">从真实世界到前后端</h1>
  821. <p>RePractise终于又迎来了新的一篇,要知道上一篇可是在半年前呢——《Repractise前端篇: 前端演进史 》。照RePractise惯例,这又是一篇超长文以及个人的扯淡过程。</p>
  822. <p>当然这也是一个神奇的标题,因为我已经想不到一个好的名字了,不过先这样吧。这篇文章算是我最近两三个月的一篇思考。在上一个项目的打杂生涯里,我开始去学习架构方面的知识,开始去接触DDD的思想。从编码到架构,再回到实际的编码中,总会有很多的灵感闪现。</p>
  823. <h2 id="从真实世界到前后端-1">从真实世界到前后端</h2>
  824. <p>我们所写的代码在某种程度上都反应了真实世界的模型、行为等等。一个比较常见的模型就是:购物模型。同时, 这也是一个很好的展示前后端分离的模型。</p>
  825. <figure>
  826. <img src="./img/no-stacks/store-model.jpg" alt="store-model.jpg" /><figcaption>store-model.jpg</figcaption>
  827. </figure>
  828. <p>(PS: 原谅我的画工)</p>
  829. <h3 id="便利店与售货员">便利店与售货员</h3>
  830. <p>对于一般的便利店来说,只有一个销售员,ta负责整个商店的一系列事务。从某种意义上来说,ta就是整个系统的核心,负责了系统的业务和事件。</p>
  831. <p>一般来说在一个购买流程里,会有三个主要的人或物:</p>
  832. <ul>
  833. <li>售货员。一般来说,ta只会在最后的结账流程中出以及顾客询问时做出响应。</li>
  834. <li>货物。没啥可解释的,就是一堆模型。0</li>
  835. <li>顾客 。浏览商店、对比商店、blabla等等。</li>
  836. </ul>
  837. <p>如果我们要构建这样一个系统,我们只需要区分出系统的各个部分,那么剩下的事情就变得很简单了。</p>
  838. <figure>
  839. <img src="./img/no-stacks/domain.jpg" alt="domain.jpg" /><figcaption>domain.jpg</figcaption>
  840. </figure>
  841. <p>由于整个系统仍然是相当复杂的,我们在这里只关注于用户购买的过程。</p>
  842. <h3 id="模型领域抽象">模型、领域、抽象</h3>
  843. <p>从购买过程来说,顾客所要做的事情就是:</p>
  844. <ul>
  845. <li>浏览、对比商品</li>
  846. <li>加到购物车</li>
  847. <li>结账、付钱</li>
  848. </ul>
  849. <p>对应的也就是有模型、领域和抽象几个概念。</p>
  850. <h4 id="模型"><strong>模型</strong></h4>
  851. <p>这些商品实现上就是相当于一系列的模型及数据。在用户购买之前,我们只需要一个去获取一个个的数据接口,并展示这些数据。</p>
  852. <p>对应于这些商品要建起Schema来是一件容易的事。作为一个商品,他们都拥有着一些共同的元素:price, name, description, location, manufacturer等等的信息。其中一些属性,还会有复杂的对应关系:</p>
  853. <figure>
  854. <img src="./img/no-stacks/product-schema.png" alt="Product Schema" /><figcaption>Product Schema</figcaption>
  855. </figure>
  856. <p>这些需要在我们建立数据库的时候,尽可能地明确好这些关系。由于业务本身是难以预料的,你可能和我们之前的项目一样需要一个addtionInfo的字段,来用JSON存储一些额外的字段。当然如果你使用的是NoSQL,那就再好不过了。</p>
  857. <p>最好你还使用了<strong>读写分离架构</strong>,一种比较常见的用法就是CMS网站,人们使用数据库来存储内容,使用静态页面来展示这些内容。比较好的实践还有CQRS(Command Query Responsibility Segregation, 命令查询职责分离模式),用于CRUD(增、删、改,当然也可以查)的Command,以及Query的查询分开。简单的来说,就是有两个不同的数据持久化中心:</p>
  858. <figure>
  859. <img src="./img/no-stacks/basic-cqrs.png" alt="Basic CQRS" /><figcaption>Basic CQRS</figcaption>
  860. </figure>
  861. <p>这一点特别适合于那些查询、搜索为主的网站,如淘宝。哈哈,我们离题有点远了,总之我们就是在这里提供了数据库的灵气,并对一些字段做一些简单的处理。听上去感觉GraphQL更适合做这样的事。</p>
  862. <h4 id="领域"><strong>领域</strong></h4>
  863. <p>而顾客及售货员在整个过程中做的事情就是领域(Domain,《实现领域驱动设计》)——即一个组织所做的事情以及其中所包含的一切。对于顾客和售货员来说,他们在各自的领域里做着不同的事。</p>
  864. <p>对于顾客来说,其整个浏览过程,基本上都是在前端完成:</p>
  865. <ul>
  866. <li>搜索、查找商品 -&gt; 获得商品列表</li>
  867. <li>查找商品详细</li>
  868. <li>切换到下一个商品</li>
  869. </ul>
  870. <p>这个场景下就特别适合于上面说到的读写分离架构。在浏览过程中,对用户的数据进行监控,以用于了解用户的行为,改善用户体验。这也是很常见的功能,或者说他们是无处不在的模式:</p>
  871. <ul>
  872. <li>结果页 / 列表页</li>
  873. <li>详情页</li>
  874. </ul>
  875. <p>随后的用户收藏、添加到购物车、购买、交付等流程都需要与后台进行更紧密的交付。而这些都和售货员都有紧密的关系,而这些就不是一种简单的事。</p>
  876. <p>从用户购买完成以后,剩下的就是一堆琐碎的事了,而这些都是由后端来完成的:</p>
  877. <ul>
  878. <li>订单子系统</li>
  879. <li>物流系统</li>
  880. <li>发票系统</li>
  881. <li>支付系统</li>
  882. </ul>
  883. <p>等等。</p>
  884. <p>对于用户来说,一种最简单的情况就是亚马逊,你只需要按一下“一键下单”即可。不需要关心后面的操作了,同样的这也适合于我们的业务场景。</p>
  885. <h4 id="抽象"><strong>抽象</strong></h4>
  886. <p>抽象本来不打算写的,但是后来想了想还是写。总的来说整个过程还是需要相对比较好的抽象能力,要不我就很难讲述清楚这里面的过程了。</p>
  887. <p>抽象是很神奇的东西,也可以分为几个不同的境界——但是我也不知道有几个境界,简单的来说就是不同的人看上去就有不同的东西。如有的人看到下面的画就是一坨shit——还不如小学生画的呢,有的人就会惊呼大师。</p>
  888. <figure>
  889. <img src="./img/no-stacks/2012070208374547914.jpg" alt="星空" /><figcaption>星空</figcaption>
  890. </figure>
  891. <p>反正,我也很看不懂。这一点倒类似于最初我对设计模型的理解一样:</p>
  892. <ul>
  893. <li>一开始不以为然</li>
  894. <li>然后发现很棒</li>
  895. <li>接着使用过度</li>
  896. <li>最后就和最好的编程器Emacs一样</li>
  897. </ul>
  898. <figure>
  899. <img src="./img/no-stacks/editor-learning-curve.png" alt="Editor Learning Curve" /><figcaption>Editor Learning Curve</figcaption>
  900. </figure>
  901. <p>这些都在随着编程生涯的展开而发生一些变化,我们不断地抽象出一些概念,以至于到了最后刚进入这个行业的人都看不懂。但是,这些都是一点点在一层层抽象的基础上产生的。</p>
  902. <figure>
  903. <img src="./img/no-stacks/needs.jpg" alt="Needs" /><figcaption>Needs</figcaption>
  904. </figure>
  905. <p>所以,我就把这一小小节扯完了,反正我是不想说得太抽象了。接着,让我们再扯点技术性的话题。</p>
  906. <h2 id="前后台分离后台">前后台分离:后台</h2>
  907. <p>典型的Web应用框架就是类似于这样的架构:</p>
  908. <figure>
  909. <img src="./img/no-stacks/spring-web-app-architecture.png" alt="Spring Web App Architecture" /><figcaption>Spring Web App Architecture</figcaption>
  910. </figure>
  911. <p>又或者是MVC架构,但是这已经不重要了。我们都前后端分离了,是时候把V层去掉了。</p>
  912. <figure>
  913. <img src="./img/no-stacks/mvc_role_diagram.png" alt="MVC Role Diagram" /><figcaption>MVC Role Diagram</figcaption>
  914. </figure>
  915. <p>我们继续以上面的浏览来购买流程来做扯淡,后台除了提高上面的商品信息以外,在购买的时候还需要对用户进行授权。当然注册就是另外一个话题了,另外一个很大的话题。</p>
  916. <p>所有的这些我们都可以向前台提供对应的API即可。理想的情况下,我们对应于不同的模块可以有不同的服务:</p>
  917. <figure>
  918. <img src="./img/no-stacks/microservices.png" alt="MicroServices" /><figcaption>MicroServices</figcaption>
  919. </figure>
  920. <p>但是现实并不总是这么美好的,而在我们当前情况下则可以——毕竟所有的用户都应该能浏览所有的商品,这时就不需要做特殊的处理了。</p>
  921. <p>在这个过程中,我们还有一系列的操作需要在后台来完成。不过,这些都可以在内部中完成的。而复杂的过程,实际上还存在于前端的逻辑当中。</p>
  922. <h2 id="前后台分离前端">前后台分离:前端</h2>
  923. <p>开始时,我们需要这样做去获取一个个的商品详情。这些数据也可以在返回页面模板的时候,直接将API数据填充到HTML中——带后台渲染的React都是这样做的。然后在用户浏览的过程中,我们还需要遵循这样的数据流程:</p>
  924. <ul>
  925. <li>获取数据。无论是Ajax,还是新的Fetch API都可以做这样的事。</li>
  926. <li>处理数据。依据于业务的需要对数据进行一些特殊的处理,如修改时间、价格的显示格式,描述的长度等等。</li>
  927. <li>显示数据。只需要一个简单的模板引擎,过去的JSP、Mustache、今天的React都在做同样的事。</li>
  928. </ul>
  929. <p>而在进入这个页面之前,我们还需要关注几个基本的元素,虽然这些都是将由框架来解决的。</p>
  930. <ul>
  931. <li>路由。无论你遵不遵循REST的实践,我们只需要给对应的页面一个URL即可。相应的如果我们需要SEO,那么我们也需要在后台有一个对应的URL。理想的情况下,他们应该是由一个URL。</li>
  932. <li>模块化。从过去Require.js的火热,到今天的各式各样的框架内建的模块化框架,他们解决都是一个问题:代码度的问题。这一点和后台采用的微服务架构的缘由好像是一样。</li>
  933. <li>控制器。我也想不起来为什么控制器非在这里不可,但是我想只有这样,我才能快速地找到这个文件。</li>
  934. </ul>
  935. <p>作为前端,我们不仅仅要负责的页面的美观,还要对用户的事件做出响应,并且做好用户体验。</p>
  936. <ul>
  937. <li>事件</li>
  938. </ul>
  939. <p>这就是最近火热的关于DOM的讨论的原因,当顾客想了解一下一些不一样东西的时候,我们就需要对DOM进行操作。</p>
  940. <p>我突然就有一个问题了,你真的有那么多DOM需要操作么?你又不是Facebook,有那么多的Timeline和事件流。还有你的用户体验是不是做得足够好了?顺便再补一刀,如果你使用了React,并且没有进行前后端分离,那就等下一个恶梦。</p>
  941. <p>最后,当用户买下东西的时候,我们也需要这样的交互流程。</p>
  942. <h2 id="repractise-2">RePractise</h2>
  943. <p>因为最近我对DDD又有了一些想法,还在想着如何直接由真实世界来建模。顺便整理了这些思路到一起,但是好似这样的设计更简单。</p>
  944. <h1 id="重构篇">重构篇</h1>
  945. <p>什么是重构?</p>
  946. <blockquote>
  947. <p>重构,一言以蔽之,就是在不改变外部行为的前提下,有条不紊地改善代码。</p>
  948. </blockquote>
  949. <p>相似的</p>
  950. <blockquote>
  951. <p>代码重构(英语:Code refactoring)指对软件代码做任何更动以增加可读性或者简化结构而不影响输出结果。</p>
  952. </blockquote>
  953. <h2 id="网站重构">网站重构</h2>
  954. <p>与上述相似的是:在不改变外部行为的前提下,简化结构、添加可读性,而在网站前端保持一致的行为。也就是说是在不改变UI的情况下,对网站进行优化,在扩展的同时保持一致的UI。</p>
  955. <p>过去人们所说的<code>网站重构</code></p>
  956. <blockquote>
  957. <p>把“未采用CSS,大量使用HTML进行定位、布局,或者虽然已经采用CSS,但是未遵循HTML结构化标准的站点”变成“让标记回归标记的原本意义。通过在HTML文档中使用结构化的标记以及用CSS控制页面表现,使页面的实际内容与它们呈现的格式相分离的站点。”的过程就是网站重构(Website Reconstruction)</p>
  958. </blockquote>
  959. <p>依照我做过的一些案例,对于传统的网站来说重构通常是</p>
  960. <ul>
  961. <li>表格(table)布局改为DIV+CSS</li>
  962. <li>使网站前端兼容于现代浏览器(针对于不合规范的CSS、如对IE6有效的)</li>
  963. <li>对于移动平台的优化</li>
  964. <li>针对于SEO进行优化</li>
  965. </ul>
  966. <p>过去的网站重构就是“DIV+CSS”,想法固然极度局限。但也不是另一部分的人认为是“XHTML+CSS”,因为“XHTML+CSS”只是页面重构。</p>
  967. <p>而真正的网站重构</p>
  968. <blockquote>
  969. <p>应包含结构、行为、表现三层次的分离以及优化,行内分工优化,以及以技术与数据、人文为主导的交互优化等。</p>
  970. </blockquote>
  971. <p>深层次的网站重构应该考虑的方面</p>
  972. <ul>
  973. <li>减少代码间的耦合</li>
  974. <li>让代码保持弹性</li>
  975. <li>严格按规范编写代码</li>
  976. <li>设计可扩展的API</li>
  977. <li>代替旧有的框架、语言(如VB)</li>
  978. <li>增强用户体验</li>
  979. </ul>
  980. <p>通常来说对于速度的优化也包含在重构中</p>
  981. <ul>
  982. <li>压缩JS、CSS、image等前端资源(通常是由服务器来解决)</li>
  983. <li>程序的性能优化(如数据读写)</li>
  984. <li>采用CDN来加速资源加载</li>
  985. <li>对于JS DOM的优化</li>
  986. <li>HTTP服务器的文件缓存</li>
  987. </ul>
  988. <p>可以应用的的方面</p>
  989. <ul>
  990. <li><a href="http://www.phodal.com/blog/nginx-with-ngx-pagespeed-module-improve-website-cache/">使用Ngx_pagespeed优化前端</a></li>
  991. <li>解耦复杂的模块</li>
  992. <li>对缓存进行优化</li>
  993. <li>针对于内容创建或预留API</li>
  994. <li>需要添加新API,如(weChat等的支持)</li>
  995. <li>用新的语言、框架代码旧的框架(如VB.NET,C#.NET)</li>
  996. </ul>
  997. <h3 id="网站重构目的">网站重构目的</h3>
  998. <p>希望自己的网站</p>
  999. <ul>
  1000. <li>成本变得更低</li>
  1001. <li>运行得更好</li>
  1002. <li>访问者更多</li>
  1003. <li>维护愈加简单</li>
  1004. <li>功能更强</li>
  1005. </ul>
  1006. <h2 id="代码重构">代码重构</h2>
  1007. <p>在经历了一年多的工作之后,我平时的主要工作就是修Bug。刚开始的时候觉得无聊,后来才发现修Bug需要更好的技术。有时候你可能要面对着一坨一坨的代码,有时候你可能要花几天的时间去阅读代码。而,你重写那几十代码可能只会花上你不到一天的时间。但是如果你没办法理解当时为什么这么做,你的修改只会带来更多的bug。修Bug,更多的是维护代码。还是前人总结的那句话对:</p>
  1008. <blockquote>
  1009. <p>写代码容易,读代码难。</p>
  1010. </blockquote>
  1011. <h2 id="使用工具重构">使用工具重构</h2>
  1012. <h2 id="借助工具重构">借助工具重构</h2>
  1013. <ul>
  1014. <li>当你写了一大堆代码,你没有意识到里面有一大堆重复。</li>
  1015. <li>当你写了一大堆测试,却不知道覆盖率有多少。</li>
  1016. </ul>
  1017. <p>这就是个问题了,于是偶然间看到了一个叫code climate的网站。</p>
  1018. <h3 id="code-climate">Code Climate</h3>
  1019. <blockquote>
  1020. <p>Code Climate consolidates the results from a suite of static analysis tools into a single, real-time report, giving your team the information it needs to identify hotspots, evaluate new approaches, and improve code quality.</p>
  1021. </blockquote>
  1022. <p>Code Climate整合一组静态分析工具的结果到一个单一的,实时的报告,让您的团队需要识别热点,探讨新的方法,提高代码质量的信息。</p>
  1023. <p>简单地来说:</p>
  1024. <ul>
  1025. <li>对我们的代码评分</li>
  1026. <li>找出代码中的坏味道</li>
  1027. </ul>
  1028. <p>于是,我们先来了个例子</p>
  1029. <table>
  1030. <thead>
  1031. <tr class="header">
  1032. <th>Rating</th>
  1033. <th>Name</th>
  1034. <th>Complexity</th>
  1035. <th>Duplication</th>
  1036. <th>Churn</th>
  1037. <th>C/M</th>
  1038. <th>Coverage</th>
  1039. <th>Smells</th>
  1040. </tr>
  1041. </thead>
  1042. <tbody>
  1043. <tr class="odd">
  1044. <td>A</td>
  1045. <td>lib/coap/coap_request_handler.js</td>
  1046. <td>24</td>
  1047. <td>0</td>
  1048. <td>6</td>
  1049. <td>2.6</td>
  1050. <td>46.4%</td>
  1051. <td>0</td>
  1052. </tr>
  1053. <tr class="even">
  1054. <td>A</td>
  1055. <td>lib/coap/coap_result_helper.js</td>
  1056. <td>14</td>
  1057. <td>0</td>
  1058. <td>2</td>
  1059. <td>3.4</td>
  1060. <td>80.0%</td>
  1061. <td>0</td>
  1062. </tr>
  1063. <tr class="odd">
  1064. <td>A</td>
  1065. <td>lib/coap/coap_server.js</td>
  1066. <td>16</td>
  1067. <td>0</td>
  1068. <td>5</td>
  1069. <td>5.2</td>
  1070. <td>44.0%</td>
  1071. <td>0</td>
  1072. </tr>
  1073. <tr class="even">
  1074. <td>A</td>
  1075. <td>lib/database/db_factory.js</td>
  1076. <td>8</td>
  1077. <td>0</td>
  1078. <td>3</td>
  1079. <td>3.8</td>
  1080. <td>92.3%</td>
  1081. <td>0</td>
  1082. </tr>
  1083. <tr class="odd">
  1084. <td>A</td>
  1085. <td>lib/database/iot_db.js</td>
  1086. <td>7</td>
  1087. <td>0</td>
  1088. <td>6</td>
  1089. <td>1.0</td>
  1090. <td>58.8%</td>
  1091. <td>0</td>
  1092. </tr>
  1093. <tr class="even">
  1094. <td>A</td>
  1095. <td>lib/database/mongodb_helper.js</td>
  1096. <td>63</td>
  1097. <td>0</td>
  1098. <td>11</td>
  1099. <td>4.5</td>
  1100. <td>35.0%</td>
  1101. <td>0</td>
  1102. </tr>
  1103. <tr class="odd">
  1104. <td>C</td>
  1105. <td>lib/database/sqlite_helper.js</td>
  1106. <td>32</td>
  1107. <td>86</td>
  1108. <td>10</td>
  1109. <td>4.5</td>
  1110. <td>35.0%</td>
  1111. <td>2</td>
  1112. </tr>
  1113. <tr class="even">
  1114. <td>B</td>
  1115. <td>lib/rest/rest_helper.js</td>
  1116. <td>19</td>
  1117. <td>62</td>
  1118. <td>3</td>
  1119. <td>4.7</td>
  1120. <td>37.5%</td>
  1121. <td>2</td>
  1122. </tr>
  1123. <tr class="odd">
  1124. <td>A</td>
  1125. <td>lib/rest/rest_server.js</td>
  1126. <td>17</td>
  1127. <td>0</td>
  1128. <td>2</td>
  1129. <td>8.6</td>
  1130. <td>88.9%</td>
  1131. <td>0</td>
  1132. </tr>
  1133. <tr class="even">
  1134. <td>A</td>
  1135. <td>lib/url_handler.js</td>
  1136. <td>9</td>
  1137. <td>0</td>
  1138. <td>5</td>
  1139. <td>2.2</td>
  1140. <td>94.1%</td>
  1141. <td>0</td>
  1142. </tr>
  1143. </tbody>
  1144. </table>
  1145. <p>分享得到的最后的结果是:</p>
  1146. <figure>
  1147. <img src="./img/refactor/coverage.png" alt="Coverage" /><figcaption>Coverage</figcaption>
  1148. </figure>
  1149. <h4 id="代码的坏味道">代码的坏味道</h4>
  1150. <p>于是我们就打开<code>lib/database/sqlite_helper.js</code>,因为其中有两个坏味道</p>
  1151. <blockquote>
  1152. <p>Similar code found in two :expression_statement nodes (mass = 86)</p>
  1153. </blockquote>
  1154. <p>在代码的 <code>lib/database/sqlite_helper.js:58…61 &lt; &gt;</code></p>
  1155. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">deleteData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
  1156. <span class="st">&#39;use strict&#39;</span><span class="op">;</span>
  1157. <span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">&quot;DELETE FROM &quot;</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">&quot; where &quot;</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
  1158. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span>(sql_command<span class="op">,</span> callback)<span class="op">;</span></code></pre></div>
  1159. <p>lib/database/sqlite_helper.js:64…67 &lt; &gt;</p>
  1160. <p>与</p>
  1161. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">getData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
  1162. <span class="st">&#39;use strict&#39;</span><span class="op">;</span>
  1163. <span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">&quot;SELECT * FROM &quot;</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">&quot; where &quot;</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
  1164. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span>(sql_command<span class="op">,</span> callback)<span class="op">;</span></code></pre></div>
  1165. <p>只是这是之前修改过的重复。。</p>
  1166. <p>原来的代码是这样的</p>
  1167. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">postData</span> <span class="op">=</span> <span class="kw">function</span> (block<span class="op">,</span> callback) <span class="op">{</span>
  1168. <span class="st">&#39;use strict&#39;</span><span class="op">;</span>
  1169. <span class="kw">var</span> db <span class="op">=</span> <span class="kw">new</span> <span class="va">sqlite3</span>.<span class="at">Database</span>(<span class="va">config</span>.<span class="at">db_name</span>)<span class="op">;</span>
  1170. <span class="kw">var</span> str <span class="op">=</span> <span class="kw">this</span>.<span class="at">parseData</span>(<span class="va">config</span>.<span class="at">keys</span>)<span class="op">;</span>
  1171. <span class="kw">var</span> string <span class="op">=</span> <span class="kw">this</span>.<span class="at">parseData</span>(block)<span class="op">;</span>
  1172. <span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">&quot;insert or replace into &quot;</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">&quot; (&quot;</span> <span class="op">+</span> str <span class="op">+</span> <span class="st">&quot;) VALUES (&quot;</span> <span class="op">+</span> string <span class="op">+</span> <span class="st">&quot;);&quot;</span><span class="op">;</span>
  1173. <span class="va">db</span>.<span class="at">all</span>(sql_command<span class="op">,</span> <span class="kw">function</span> (err) <span class="op">{</span>
  1174. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">errorHandler</span>(err)<span class="op">;</span>
  1175. <span class="va">db</span>.<span class="at">close</span>()<span class="op">;</span>
  1176. <span class="at">callback</span>()<span class="op">;</span>
  1177. <span class="op">}</span>)<span class="op">;</span>
  1178. <span class="op">};</span>
  1179. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">deleteData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
  1180. <span class="st">&#39;use strict&#39;</span><span class="op">;</span>
  1181. <span class="kw">var</span> db <span class="op">=</span> <span class="kw">new</span> <span class="va">sqlite3</span>.<span class="at">Database</span>(<span class="va">config</span>.<span class="at">db_name</span>)<span class="op">;</span>
  1182. <span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">&quot;DELETE FROM &quot;</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">&quot; where &quot;</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
  1183. <span class="va">db</span>.<span class="at">all</span>(sql_command<span class="op">,</span> <span class="kw">function</span> (err) <span class="op">{</span>
  1184. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">errorHandler</span>(err)<span class="op">;</span>
  1185. <span class="va">db</span>.<span class="at">close</span>()<span class="op">;</span>
  1186. <span class="at">callback</span>()<span class="op">;</span>
  1187. <span class="op">}</span>)<span class="op">;</span>
  1188. <span class="op">};</span>
  1189. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">getData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
  1190. <span class="st">&#39;use strict&#39;</span><span class="op">;</span>
  1191. <span class="kw">var</span> db <span class="op">=</span> <span class="kw">new</span> <span class="va">sqlite3</span>.<span class="at">Database</span>(<span class="va">config</span>.<span class="at">db_name</span>)<span class="op">;</span>
  1192. <span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">&quot;SELECT * FROM &quot;</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">&quot; where &quot;</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
  1193. <span class="va">db</span>.<span class="at">all</span>(sql_command<span class="op">,</span> <span class="kw">function</span> (err<span class="op">,</span> rows) <span class="op">{</span>
  1194. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">errorHandler</span>(err)<span class="op">;</span>
  1195. <span class="va">db</span>.<span class="at">close</span>()<span class="op">;</span>
  1196. <span class="at">callback</span>(<span class="va">JSON</span>.<span class="at">stringify</span>(rows))<span class="op">;</span>
  1197. <span class="op">}</span>)<span class="op">;</span>
  1198. <span class="op">};</span></code></pre></div>
  1199. <p>说的也是大量的重复,重构完的代码</p>
  1200. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span> <span class="op">=</span> <span class="kw">function</span>(sql<span class="op">,</span> db_callback)<span class="op">{</span>
  1201. <span class="st">&#39;use strict&#39;</span><span class="op">;</span>
  1202. <span class="kw">var</span> db <span class="op">=</span> <span class="kw">new</span> <span class="va">sqlite3</span>.<span class="at">Database</span>(<span class="va">config</span>.<span class="at">db_name</span>)<span class="op">;</span>
  1203. <span class="va">db</span>.<span class="at">all</span>(sql<span class="op">,</span> <span class="kw">function</span> (err<span class="op">,</span> rows) <span class="op">{</span>
  1204. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">errorHandler</span>(err)<span class="op">;</span>
  1205. <span class="va">db</span>.<span class="at">close</span>()<span class="op">;</span>
  1206. <span class="at">db_callback</span>(<span class="va">JSON</span>.<span class="at">stringify</span>(rows))<span class="op">;</span>
  1207. <span class="op">}</span>)<span class="op">;</span>
  1208. <span class="op">};</span>
  1209. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">postData</span> <span class="op">=</span> <span class="kw">function</span> (block<span class="op">,</span> callback) <span class="op">{</span>
  1210. <span class="st">&#39;use strict&#39;</span><span class="op">;</span>
  1211. <span class="kw">var</span> str <span class="op">=</span> <span class="kw">this</span>.<span class="at">parseData</span>(<span class="va">config</span>.<span class="at">keys</span>)<span class="op">;</span>
  1212. <span class="kw">var</span> string <span class="op">=</span> <span class="kw">this</span>.<span class="at">parseData</span>(block)<span class="op">;</span>
  1213. <span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">&quot;insert or replace into &quot;</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">&quot; (&quot;</span> <span class="op">+</span> str <span class="op">+</span> <span class="st">&quot;) VALUES (&quot;</span> <span class="op">+</span> string <span class="op">+</span> <span class="st">&quot;);&quot;</span><span class="op">;</span>
  1214. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span>(sql_command<span class="op">,</span> callback)<span class="op">;</span>
  1215. <span class="op">};</span>
  1216. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">deleteData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
  1217. <span class="st">&#39;use strict&#39;</span><span class="op">;</span>
  1218. <span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">&quot;DELETE FROM &quot;</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">&quot; where &quot;</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
  1219. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span>(sql_command<span class="op">,</span> callback)<span class="op">;</span>
  1220. <span class="op">};</span>
  1221. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">getData</span> <span class="op">=</span> <span class="kw">function</span> (url<span class="op">,</span> callback) <span class="op">{</span>
  1222. <span class="st">&#39;use strict&#39;</span><span class="op">;</span>
  1223. <span class="kw">var</span> sql_command <span class="op">=</span> <span class="st">&quot;SELECT * FROM &quot;</span> <span class="op">+</span> <span class="va">config</span>.<span class="at">table_name</span> <span class="op">+</span> <span class="st">&quot; where &quot;</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getKeyFromURL</span>(url) <span class="op">+</span> <span class="st">&quot;=&quot;</span> <span class="op">+</span> <span class="va">URLHandler</span>.<span class="at">getValueFromURL</span>(url)<span class="op">;</span>
  1224. <span class="va">SQLiteHelper</span>.<span class="va">prototype</span>.<span class="at">basic</span>(sql_command<span class="op">,</span> callback)<span class="op">;</span>
  1225. <span class="op">};</span></code></pre></div>
  1226. <p>重构完后的代码比原来还长,这似乎是个问题~~</p>
  1227. <h2 id="测试驱动开发">测试驱动开发</h2>
  1228. <h3 id="一次测试驱动开发的故事">一次测试驱动开发的故事</h3>
  1229. <p>之前正在重写一个<a href="http://www.phodal.com/iot">物联网</a>的服务端,主要便是结合CoAP、MQTT、HTTP等协议构成一个物联网的云服务。现在,主要的任务是集中于协议与授权。由于,不同协议间的授权是不一样的,最开始的时候我先写了一个http put授权的功能,而在起先的时候是如何测试的呢?</p>
  1230. <pre><code>curl --user root:root -X PUT -d &#39;{ &quot;dream&quot;: 1 }&#39; -H &quot;Content-Type: application/json&quot; http://localhost:8899/topics/test</code></pre>
  1231. <p>我只要顺利在request中看有无<code>req.headers.authorization</code>,我便可以继续往下,接着给个判断。毕竟,我们对HTTP协议还是蛮清楚的。</p>
  1232. <pre><code> if (!req.headers.authorization) {
  1233. res.statusCode = 401;
  1234. res.setHeader(&#39;WWW-Authenticate&#39;, &#39;Basic realm=&quot;Secure Area&quot;&#39;);
  1235. return res.end(&#39;Unauthorized&#39;);
  1236. }</code></pre>
  1237. <p>可是除了HTTP协议,还有MQTT和CoAP。对于MQTT协议来说,那还算好,毕竟自带授权,如:</p>
  1238. <pre><code> mosquitto_pub -u root -P root -h localhost -d -t lettuce -m &quot;Hello, MQTT. This is my first message.&quot;</code></pre>
  1239. <p>便可以让我们简单地完成这个功能,然而有的协议是没有这样的功能如CoAP协议中是用Option来进行授权的。现在的工具如libcoap只能有如下的简单功能</p>
  1240. <pre><code>coap-client -m get coap://127.0.0.1:5683/topics/zero -T</code></pre>
  1241. <p>于是,先写了个测试脚本来验证功能。</p>
  1242. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> <span class="kw">var</span> coap <span class="op">=</span> <span class="at">require</span>(<span class="st">&#39;coap&#39;</span>)<span class="op">;</span>
  1243. <span class="kw">var</span> request <span class="op">=</span> <span class="va">coap</span>.<span class="at">request</span><span class="op">;</span>
  1244. <span class="kw">var</span> req <span class="op">=</span> <span class="at">request</span>(<span class="op">{</span><span class="dt">hostname</span><span class="op">:</span> <span class="st">&#39;localhost&#39;</span><span class="op">,</span><span class="dt">port</span><span class="op">:</span><span class="dv">5683</span><span class="op">,</span><span class="dt">pathname</span><span class="op">:</span> <span class="st">&#39;&#39;</span><span class="op">,</span><span class="dt">method</span><span class="op">:</span> <span class="st">&#39;POST&#39;</span><span class="op">}</span>)<span class="op">;</span>
  1245. ...
  1246. <span class="va">req</span>.<span class="at">setHeader</span>(<span class="st">&quot;Accept&quot;</span><span class="op">,</span> <span class="st">&quot;application/json&quot;</span>)<span class="op">;</span>
  1247. <span class="va">req</span>.<span class="at">setOption</span>(<span class="st">&#39;Block2&#39;</span><span class="op">,</span> [<span class="kw">new</span> <span class="at">Buffer</span>(<span class="st">&#39;phodal&#39;</span>)<span class="op">,</span> <span class="kw">new</span> <span class="at">Buffer</span>(<span class="st">&#39;phodal&#39;</span>)])<span class="op">;</span>
  1248. ...
  1249. <span class="va">req</span>.<span class="at">end</span>()<span class="op">;</span></code></pre></div>
  1250. <p>写完测试脚本后发现不对了,这个不应该是测试的代码吗? 于是将其放到了spec中,接着发现了上面的全部功能的实现过程为什么不用TDD实现呢?</p>
  1251. <h3 id="说说测试驱动开发">说说测试驱动开发</h3>
  1252. <p>测试驱动开发是一个很“古老”的程序开发方法,然而由于国内的开发流程的问题——即开发人员负责功能的测试,导致这么好的一项技术没有在国内推广。</p>
  1253. <p>测试驱动开发的主要过程是:</p>
  1254. <ol type="1">
  1255. <li>先写功能的测试</li>
  1256. <li>实现功能代码</li>
  1257. <li>提交代码(commit -&gt; 保证功能正常)</li>
  1258. <li>重构功能代码</li>
  1259. </ol>
  1260. <p>而对于这样的一个物联网项目来说,我已经有了几个有利的前提:</p>
  1261. <ol type="1">
  1262. <li>已经有了原型</li>
  1263. <li>框架设计</li>
  1264. </ol>
  1265. <h3 id="思考">思考</h3>
  1266. <p>通常在我的理解下,TDD是可有可无的。既然我知道了我要实现的大部分功能,而且我也知道如何实现。与此同时,对Code Smell也保持着警惕、要保证功能被测试覆盖。那么,总的来说TDD带来的价值并不大。</p>
  1267. <p>然而,在当前这种情况下,我知道我想要的功能,但是我并不理解其深层次的功能。我需要花费大量的时候来理解,它为什么是这样的,需要先有一些脚本来知道它是怎么工作的。TDD变显得很有价值,换句话来说,在现有的情况下,TDD对于我们不了解的一些事情,可以驱动出更多的开发。毕竟在我们完成测试脚本之后,我们也会发现这些测试脚本成为了代码的一部分。</p>
  1268. <p>在这种理想的情况下,我们为什么不TDD呢?</p>
  1269. <h1 id="架构篇-cms的重构与演进">架构篇: CMS的重构与演进</h1>
  1270. <p>重构系统是一项非常具有挑战性的事情。通常来说,在我们的系统是第二个系统的时候才需要重构,即这个系统本身已经很臃肿。我们花费了太量的时间在代码间的逻辑,开发新的功能变得越来越慢。这不仅仅可能只是因为我们之前的架构没有设计好,而且在我们开发的过程中没有保持着原先设计时的一些原则。如果是这样的情况,那么这就是一个复杂的过程。</p>
  1271. <p>还有一种情况是我们发现了一种更符合我们当前业务的框架。</p>
  1272. <h2 id="动态cms">动态CMS</h2>
  1273. <h3 id="cms简介">CMS简介</h3>
  1274. <p>CMS是Content Management System的缩写,意为“内容管理系统”.它可以做很多的事情,但是总的来说就是Page和Blog——即我们要创建一些页面可以用于写一些About US、Contact Me,以及持续更新的博客或者新闻,以及其他子系统——通常更新不活跃。通过对这些博客或者新闻进行分类,我们就可以有不同的信息内容,如下图:</p>
  1275. <figure>
  1276. <img src="./img/cms/cms-blogs.png" alt="不同分类的内容" /><figcaption>不同分类的内容</figcaption>
  1277. </figure>
  1278. <p>CMS是政府和企业都需要的系统,他们有很多的信息需要公开,并且需要对其组织进行宣传。在我有限的CMS交付经验里(大学时期),一般第一次交付CMS的时候,已经创建了大部分页面。有时候这些页面可能直接存储在数据库中,后来发现这不是一个好的方案,于是很多页面变成了静态页面。随后,在CMS的生命周期里就是更新内容。</p>
  1279. <p>因而,CMS中起其主导的东西还是Content,即内容。而内容是一些持续可变的东西。这也就是为什么WordPress这么流行于CMS界,它是一个博客系统,但是多数时候我们只需要更新内容。除此不得不提及的一个CMS框架是Drupal,两者一对比会发现Drupal比较强大。通常来说,强大的一个负作用就是——复杂。</p>
  1280. <p>WordPress和Drupal这一类的系统都属于发布系统,而其后台可以称为编辑系统。</p>
  1281. <p>一般来说CMS有下面的特点:</p>
  1282. <ul>
  1283. <li>支持多用户。</li>
  1284. <li>角色控制-内容管理。如InfoQ的编辑后台就会有这样的机制,社区编辑负责创建内容,而审核发布则是另外的人做的。</li>
  1285. <li>插件管理。如WordPress和Drupal在这一方面就很强大,基本可以满足日常的需要。</li>
  1286. <li>快捷简便地存储内容。简单地来说就是所见即所得编辑器,但是对于开发者来说,Markdown似乎是好的选择。</li>
  1287. <li>预发布。这是一个很重要的特性,特别是如果你的系统后台没有相对应的预览机制。</li>
  1288. <li>子系统。由于这属于定制化的系统,并不方便进行总结。</li>
  1289. <li>…</li>
  1290. </ul>
  1291. <p>CMS一直就是这样一个紧耦合的系统。</p>
  1292. <h3 id="cms架构与django">CMS架构与Django</h3>
  1293. <p>说起来,我一直是一个CMS党。主要原因还在于我可以随心所欲地去修改网站的内容,修改网站的架构。好的CMS总的来说都有其架构图,下图似乎是Drupal的模块图</p>
  1294. <figure>
  1295. <img src="./img/cms/drupal-modular.png" alt="Drupal 框架" /><figcaption>Drupal 框架</figcaption>
  1296. </figure>
  1297. <p>一般来说,其底层都会有:</p>
  1298. <ul>
  1299. <li>ORM</li>
  1300. <li>User Management</li>
  1301. <li>I18n / L10n</li>
  1302. <li>Templates</li>
  1303. </ul>
  1304. <p>我一直在使用一个名为Django的Python Web框架,它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的,即是CMS(内容管理系统)软件。它是一个MTV框架——与多数的框架并没有太大的区别。</p>
  1305. <table>
  1306. <thead>
  1307. <tr class="header">
  1308. <th>层次</th>
  1309. <th>职责</th>
  1310. </tr>
  1311. </thead>
  1312. <tbody>
  1313. <tr class="odd">
  1314. <td>模型(Model),即数据存取层</td>
  1315. <td>处理与数据相关的所有事务:如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等。</td>
  1316. </tr>
  1317. <tr class="even">
  1318. <td>模板(Template),即表现层</td>
  1319. <td>处理与表现相关的决定: 如何在页面或其他类型文档中进行显示。</td>
  1320. </tr>
  1321. <tr class="odd">
  1322. <td>视图(View),即业务逻辑层</td>
  1323. <td>存取模型及调取恰当模板的相关逻辑。模型与模板之间的桥梁。</td>
  1324. </tr>
  1325. </tbody>
  1326. </table>
  1327. <p>从框架本身来上看它和别的系统没有太大的区别。</p>
  1328. <figure>
  1329. <img src="./img/cms/django-architecture.jpg" alt="Django Architecture" /><figcaption>Django Architecture</figcaption>
  1330. </figure>
  1331. <p>但是如果我们已经有多外模块(即Django中app的概念),那么系统的架构就有所不同了。</p>
  1332. <figure>
  1333. <img src="./img/cms/django-apps.jpg" alt="Django App架构" /><figcaption>Django App架构</figcaption>
  1334. </figure>
  1335. <p>这就是为何我喜欢用这个CMS的原因了,我的每个子系统都以APP的形式提供服务——博客是一个app,sitemap是一个app,api是一个app。系统直接解耦为类似于混合服务的架构,即不像微服务一样多语言化,又不会有宏应用的紧耦合问题。</p>
  1336. <h3 id="编辑-发布分离">编辑-发布分离</h3>
  1337. <p>我们的编辑和发布系统在某种意义上紧耦合在一起了,当用户访问量特别大的时候,这样会让我们的应用变得特定慢。有时候编辑甚至发布不了新的东西,如下图引示:</p>
  1338. <figure>
  1339. <img src="./img/cms/editor-publisher.png" alt="发布-编辑" /><figcaption>发布-编辑</figcaption>
  1340. </figure>
  1341. <p>或者你认识出了上图是源自Martin Folwer的<a href="http://martinfowler.com/bliki/EditingPublishingSeparation.html">编辑-发布分离</a></p>
  1342. <p>编辑-发布分离是几年前解耦复杂系统游来开来带来的一个成果。今天这个似乎已经很常见了,编辑的时候是在后台进行的,等到发布的时候已经变成了一个静态的HTML。</p>
  1343. <p>已经有足够多的CMS支持这样的特性,运行起来似乎特别不错,当然这样的系统也会有缓存的问题。有了APP这后,这个趋势就更加明显了——人们需要提供一个API。到底是在现有的系统里提供一个新的API,还是创建一个新的API。</p>
  1344. <p>这时候,我更愿意选择后者——毕竟紧耦合一个系统总会在后期带来足够多的麻烦。而且基于数据库构建一个只读的RESTful API并不是一个复杂的过程,而且也危险。这时候的瓶颈就是数据库,但是似乎数据库都是多数系统的瓶颈。人们想出了各种各样的技术来解决这个瓶颈。</p>
  1345. <p>于是之前我试着用Node.js + RESTify将我的博客重构成了一个SPA,当然这个时候CMS还在运行着。出于SEO的原因我并没有在最后采用这个方案,因为<a href="https://www.phodal.com">我网站</a>的主要流量来源是Google和是百度。但是我在另外的网站里混合了SPA与MPA,其中的性能与应用是相当的,除了第一次加载页面的时候会带来一些延时。</p>
  1346. <p>除了Node.js + RESTify,也试了试Python + Falcon(一个高性能的RESTful框架)。这个API理论上也应该可以给APP直接使用,并且可以直接拿来生成静态页面。</p>
  1347. <h4 id="编辑-发布-开发分离静态站点生成">编辑-发布-开发分离:静态站点生成</h4>
  1348. <p>如React一样解决DOM性能的问题就是跳过DOM这个坑,要跳过动态网站的性能问题就是让网站变成静态。</p>
  1349. <p>越来越多的开发人员开始在使用Github Pages作为他们的博客,这是一个很有意思的转变。主要的原因是这是免费的,并且基本上可以保证24x7小时是可用的——当且仅当Github发现故障的时候才会不可访问。</p>
  1350. <p>在这一类静态站点生成器(Github)里面,比较流行的有下面的内容(数据来源: <a href="http://segmentfault.com/a/1190000002476681" class="uri">http://segmentfault.com/a/1190000002476681</a>):</p>
  1351. <ol type="1">
  1352. <li>Jekyll / OctoPress。Jekyll和OctoPress是最流行的静态博客系统。</li>
  1353. <li>Hexo。Hexo是NodeJS编写的静态博客系统,其生成速度快,主题数量相对也比较丰富。是OctoPress的优秀替代者。</li>
  1354. <li>Sculpin。Sculpin是PHP的静态站点系统。Hexo和Octopress专注于博客,而有时候我们的需求不仅仅是博客,而是有类似CMS的页面生成需求。Sculpin是一个泛用途的静态站点生成系统,在支持博客常见的分页、分类tag等同时,也能较好地支持非博客的一般页面生成。</li>
  1355. <li>Hugo。Hugo是GO语言编写的静态站点系统。其生成速度快,且在较好支持博客和非博客内容的同时提供了比较完备的主题系统。无论是自己写主题还是套用别人的主题都比较顺手。</li>
  1356. </ol>
  1357. <p>通常这一类的工具里会有下面的内容:</p>
  1358. <ol type="1">
  1359. <li>模板</li>
  1360. <li>支持Markdown</li>
  1361. <li>元数据</li>
  1362. </ol>
  1363. <p>如Hexo这样的框架甚至提供了<code>一键部署</code>的功能。</p>
  1364. <p>在我们写了相关的代码之后,随后要做的就是生成HTML。对于个人博客来说,这是一个非常不错的系统,但是对于一些企业级的系统来说,我们的要求就更高了。如下图是Carrot采用的架构:</p>
  1365. <figure>
  1366. <img src="./img/cms/carrot.png" alt="Editor Develoepr" /><figcaption>Editor Develoepr</figcaption>
  1367. </figure>
  1368. <p>这与我们在项目上的系统架构目前相似。作为一个博主,通常来说我们修改博客的主题的频率会比较低, 可能是半年一次。如果你经常修改博客的主题,你博客上的文章一定是相当的少。</p>
  1369. <p>上图中的编辑者通过一个名为Contentful CMS来创建他们的内容,接着生成RESTful API。而类似的事情,我们也可以用Wordpress + RESTful 插件来完成。如果做得好,那么我想这个API也可以直接给APP使用。</p>
  1370. <p>上图中的开发者需要不断地将修改的主题或者类似的东西PUSH到版本管理系统上,接着会有webhook监测到他们的变化,然后编译出新的静态页面。</p>
  1371. <p>最后通过Netlify,他们编译到了一起,然后部署到生产环境。除了Netlify,你也可以编写生成脚本,然后用Bamboo、Go这类的CI工具进行编译。</p>
  1372. <p>通常来说,生产环境可以使用CDN,如CloudFront服务。与动态网站相比,静态网站很容易直接部署到CDN,并可以直接从离用户近的本地缓存提供服务。除此,直接使用AWS S3的静态网站托管也是一个非常不错的选择。</p>
  1373. <h3 id="基于github的编辑-发布-开发分离">基于Github的编辑-发布-开发分离</h3>
  1374. <p>尽管我们已经在项目上实施了基于Github的部分内容管理已经有些日子里,但是由于找不到一些相关的资料,便不好透露相关的细节。直到我看到了《<a href="https://www.thoughtworks.com/insights/blog/incremental-approach-content-management-using-git">An Incremental Approach to Content Management Using Git 1</a>》,我才意识到这似乎已经是一个成熟的技术了。看样子这项技术首先已经应用到了ThoughtWorks的官网上了。</p>
  1375. <p>文中提到了使用这种架构的几个点:</p>
  1376. <ol type="1">
  1377. <li>快速地开始项目,而不是学习或者配置框架。</li>
  1378. <li>需要使用我们信奉的原则,如TDD。而这是大部分CMS所不支持的。</li>
  1379. <li>基于服务的架构。</li>
  1380. <li>灵活的语言和工具</li>
  1381. <li>我们是开发人员。</li>
  1382. </ol>
  1383. <p>So,so,这些开发人员做了些什么:</p>
  1384. <ol type="1">
  1385. <li>内容存储为静态文件</li>
  1386. <li>不是所有的内容都是平等的</li>
  1387. <li>引入内容服务</li>
  1388. <li>使用Github。所有的content会提交到一个repo里,同时在我们push内容的时候,可以实时更新这些内容。</li>
  1389. <li>允许内容通过内容服务更新</li>
  1390. <li>使用Github API</li>
  1391. </ol>
  1392. <p>于是,有了一个名为<a href="https://github.com/haciendaio/hacienda">Hacienda</a>的框架用于管理内容,并存储为JSON。这意味着什么?</p>
  1393. <figure>
  1394. <img src="./img/cms/github-edit-publish-code.png" alt="基于Github的编辑-发布-开发分离" /><figcaption>基于Github的编辑-发布-开发分离</figcaption>
  1395. </figure>
  1396. <p>因为使用了Git,我们可以了解到一个文件内容的历史版本,相比于WordPress来说更直观,而且更容易 上手。</p>
  1397. <p>开发人员修改完他们的代码后,就可以直接提交,不会影响到Editor使用网站。Editor通过一个编辑器添加内容,在保存后,内容以JSON的形式出现直接提交代码到Github上相应的代码库中。CI或者Builder监测到他们的办法,就会生成新的静态页面。在这时候,我们可以选择有一个预览的平台,并且可以一键部署。那么,事情似乎就完成得差不多了。</p>
  1398. <p>如果我们有APP,那么我们就可以使用Content Servies来做这些事情。甚至可以直接拿其搭建一个SPA。</p>
  1399. <p>如果我们需要全文搜索功能,也变得很简单。我们已经不需要直接和数据库交互,我们可以直接读取JSON并且构建索引。这时候需要一个简单的Web服务,而且这个服务还是只读的。</p>
  1400. <p>在需要的时候,如手机APP,我们可以通过Content Servies来创建博客。</p>
  1401. <h3 id="repractise-3">Repractise</h3>
  1402. <blockquote>
  1403. <p>动态网页是下一个要解决的难题。我们从数据库中读取数据,再用动态去渲染出一个静态页面,并且缓存服务器来缓存这个页面。既然我们都可以用Varnish、Squid这样的软件来缓存页面——表明它们可以是静态的,为什么不考虑直接使用静态网页呢?</p>
  1404. </blockquote>
  1405. <p>思考完这些后,我想到了一个符合学习的场景。</p>
  1406. <figure>
  1407. <img src="./img/cms/travis-edit-publish-code.png" alt="基于Travis CI的编辑-发布-开发分离" /><figcaption>基于Travis CI的编辑-发布-开发分离</figcaption>
  1408. </figure>
  1409. <p>我们构建的核心都可以基于Travis CI来完成,唯一存在风险的环节是我们似乎需要暴露我们的Key。</p>
  1410. <h4 id="其他-1">其他</h4>
  1411. <p>参考文章:</p>
  1412. <ol type="1">
  1413. <li><a href="http://www.infoq.com/cn/news/2015/11/LAMP-CDN">静态网站生成器将会成为下一个大热门</a></li>
  1414. <li><a href="http://martinfowler.com/bliki/EditingPublishingSeparation.html">EditingPublishingSeparation</a></li>
  1415. <li><a href="https://www.thoughtworks.com/insights/blog/incremental-approach-content-management-using-git">An Incremental Approach to Content Management Using Git 1</a></li>
  1416. <li><a href="https://www.thoughtworks.com/insights/blog/implementing-content-management-and-publication-using-git">Part 2: Implementing Content Management and Publication Using Git</a></li>
  1417. </ol>
  1418. <h2 id="构建基于git为数据中心的cms">构建基于Git为数据中心的CMS</h2>
  1419. <p>或许你也用过Hexo / Jekyll / Octopress这样的静态博客,他们的原理都是类似的。我们有一个代码库用于生成静态页面,然后这些静态页面会被PUSH到Github Pages上。</p>
  1420. <p>从我们设计系统的角度来说,我们会在Github上有三个主要代码库:</p>
  1421. <ol type="1">
  1422. <li>Content。用于存放编辑器生成的JSON文件,这样我们就可以GET这些资源,并用Backbone / Angular / React 这些前端框架来搭建SPA。</li>
  1423. <li>Code。开发者在这里存放他们的代码,如主题、静态文件生成器、资源文件等等。</li>
  1424. <li>Builder。在这里它是运行于Travis CI上的一些脚本文件,用于Clone代码,并执行Code中的脚本。</li>
  1425. </ol>
  1426. <p>以及一些额外的服务,当且仅当你有一些额外的功能需求的时候。</p>
  1427. <ol type="1">
  1428. <li>Extend Service。当我们需要搜索服务时,我们就需要这样的一些服务。如我正考虑使用Python的whoosh来完成这个功能,这时候我计划用Flask框架,但是只是计划中——因为没有合适的中间件。</li>
  1429. <li>Editor。相比于前面的那些知识这一步适合更重要,也就是为什么生成的格式是JSON而不是Markdown的原理。对于非程序员来说,要熟练掌握Markdown不是一件容易的事。于是,一个考虑中的方案就是使用 Electron + Node.js来生成API,最后通过GitHub API V3来实现上传。</li>
  1430. <li>Mobile App。</li>
  1431. </ol>
  1432. <p>So,这一个过程是如何进行的。</p>
  1433. <h3 id="用户场景">用户场景</h3>
  1434. <p>整个过程的Pipeline如下所示:</p>
  1435. <ol type="1">
  1436. <li>编辑使用他们的编辑器来编辑的内容并点击发布,然后这个内容就可以通过GitHub API上传到Content这个Repo里。</li>
  1437. <li>这时候需要有一个WebHooks监测到了Content代码库的变化,便运行Builder这个代码库的Travis CI。</li>
  1438. <li>这个Builder脚本首先,会设置一些基本的git配置。然后clone Content和Code的代码,接着运行构建命令,生成新的内容。</li>
  1439. <li>然后Builder Commit内容,并PUSH内容。</li>
  1440. </ol>
  1441. <p>在这种情形中,编辑能否完成工作就不依赖于网站——脱稿又少了 个借口。这时候网站出错的概率太小了——你不需要一个缓存服务器、HTTP服务器,由于没有动态生成的内容,你也不需要守护进程。这些内容都是静态文件,你可以将他们放在任何可以提供静态文件托管的地方——CloudFront、S3等等。或者你再相信自己的服务器,Nginx可是全球第二好(第一还没出现)的静态文件服务器。</p>
  1442. <p>开发人员只在需要的时候去修改网站的一些内容。So,你可能会担心如果这时候修改的东西有问题了怎么办。</p>
  1443. <ol type="1">
  1444. <li>使用这种模式就意味着你需要有测试来覆盖这些构建工具、生成工具。</li>
  1445. <li>相比于自己的代码,别人的CMS更可靠?</li>
  1446. </ol>
  1447. <p>需要注意的是如果你上一次构建成功,你生成的文件都是正常的,那么你只需要回滚开发相关的代码即可。旧的代码仍然可以工作得很好。其次,由于生成的是静态文件,查错的成本就比较低。最后,重新放上之前的静态文件。</p>
  1448. <h2 id="code-生成静态页面">Code: 生成静态页面</h2>
  1449. <p>Assemble是一个使用Node.js,Grunt.js,Gulp,Yeoman 等来实现的静态网页生成系统。这样的生成器有很多,Zurb Foundation, Zurb Ink, Less.js / lesscss.org, Topcoat, Web Experience Toolkit等组织都使用这个工具来生成。这个工具似乎上个Release在一年多以前,现在正在开始0.6。虽然,这并不重要,但是还是顺便一说。</p>
  1450. <p>我们所要做的就是在我们的<code>Gruntfile.js</code>中写相应的生成代码。</p>
  1451. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> assemble<span class="op">:</span> <span class="op">{</span>
  1452. <span class="dt">options</span><span class="op">:</span> <span class="op">{</span>
  1453. <span class="dt">flatten</span><span class="op">:</span> <span class="kw">true</span><span class="op">,</span>
  1454. <span class="dt">partials</span><span class="op">:</span> [<span class="st">&#39;templates/includes/*.hbs&#39;</span>]<span class="op">,</span>
  1455. <span class="dt">layoutdir</span><span class="op">:</span> <span class="st">&#39;templates/layouts&#39;</span><span class="op">,</span>
  1456. <span class="dt">data</span><span class="op">:</span> <span class="st">&#39;content/blogs.json&#39;</span><span class="op">,</span>
  1457. <span class="dt">layout</span><span class="op">:</span> <span class="st">&#39;default.hbs&#39;</span>
  1458. <span class="op">},</span>
  1459. <span class="dt">site</span><span class="op">:</span> <span class="op">{</span>
  1460. <span class="dt">files</span><span class="op">:</span> <span class="op">{</span><span class="st">&#39;dest/&#39;</span><span class="op">:</span> [<span class="st">&#39;templates/*.hbs&#39;</span>]<span class="op">}</span>
  1461. <span class="op">},</span>
  1462. <span class="dt">blogs</span><span class="op">:</span> <span class="op">{</span>
  1463. <span class="dt">options</span><span class="op">:</span> <span class="op">{</span>
  1464. <span class="dt">flatten</span><span class="op">:</span> <span class="kw">true</span><span class="op">,</span>
  1465. <span class="dt">layoutdir</span><span class="op">:</span> <span class="st">&#39;templates/layouts&#39;</span><span class="op">,</span>
  1466. <span class="dt">data</span><span class="op">:</span> <span class="st">&#39;content/*.json&#39;</span><span class="op">,</span>
  1467. <span class="dt">partials</span><span class="op">:</span> [<span class="st">&#39;templates/includes/*.hbs&#39;</span>]<span class="op">,</span>
  1468. <span class="dt">pages</span><span class="op">:</span> pages
  1469. <span class="op">},</span>
  1470. <span class="dt">files</span><span class="op">:</span> [
  1471. <span class="op">{</span> <span class="dt">dest</span><span class="op">:</span> <span class="st">&#39;./dest/blog/&#39;</span><span class="op">,</span> <span class="dt">src</span><span class="op">:</span> <span class="st">&#39;!*&#39;</span> <span class="op">}</span>
  1472. ]
  1473. <span class="op">}</span>
  1474. <span class="op">}</span></code></pre></div>
  1475. <p>配置中的site用于生成页面相关的内容,blogs则可以根据json文件的文件名生成对就的html文件存储到blog目录中。</p>
  1476. <p>生成后的目录结果如下图所示:</p>
  1477. <pre><code> .
  1478. ├── about.html
  1479. ├── blog
  1480. │   ├── blog-posts.html
  1481. │   └── blogs.html
  1482. ├── blog.html
  1483. ├── css
  1484. │   ├── images
  1485. │   │   └── banner.jpg
  1486. │   └── style.css
  1487. ├── index.html
  1488. └── js
  1489. ├── jquery.min.js
  1490. └── script.js
  1491. 7 directories, 30 files</code></pre>
  1492. <p>这里的静态文件内容就是最后我们要发布的内容。</p>
  1493. <p>还需要做的一件事情就是:</p>
  1494. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">grunt</span>.<span class="at">registerTask</span>(<span class="st">&#39;dev&#39;</span><span class="op">,</span> [<span class="st">&#39;default&#39;</span><span class="op">,</span> <span class="st">&#39;connect:server&#39;</span><span class="op">,</span> <span class="st">&#39;watch:site&#39;</span>])<span class="op">;</span></code></pre></div>
  1495. <p>用于开发阶段这样的代码就够了,这个和你使用WebPack + React 似乎相差不了多少。</p>
  1496. <h2 id="builder-构建生成工具">Builder: 构建生成工具</h2>
  1497. <p>Github与Travis之间,可以做一个自动部署的工具。相信已经有很多人在Github上玩过这样的东西——先在Github上生成Token,然后用travis加密:</p>
  1498. <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">travis</span> encrypt-file ssh_key --add</code></pre></div>
  1499. <p>加密后的Key就会保存到<code>.travis.yml</code>文件里,然后就可以在Travis CI上push你的代码到Github上了。</p>
  1500. <p>接着,你需要创建个deploy脚本,并且在<code>after_success</code>执行它:</p>
  1501. <div class="sourceCode"><pre class="sourceCode yml"><code class="sourceCode yaml"><span class="fu">after_success:</span>
  1502. <span class="kw">-</span> test $TRAVIS_PULL_REQUEST == <span class="st">&quot;false&quot;</span> &amp;&amp; test $TRAVIS_BRANCH == <span class="st">&quot;master&quot;</span> &amp;&amp; bash deploy.sh</code></pre></div>
  1503. <p>在这个脚本里,你所需要做的就是clone content和code中的代码,并执行code中的生成脚本,生成新的内容后,提交代码。</p>
  1504. <pre><code>#!/bin/bash
  1505. set -o errexit -o nounset
  1506. rev=$(git rev-parse --short HEAD)
  1507. cd stage/
  1508. git init
  1509. git config user.name &quot;Robot&quot;
  1510. git config user.email &quot;robot@phodal.com&quot;
  1511. git remote add upstream &quot;https://$GH_TOKEN@github.com/phodal-archive/echeveria-deploy.git&quot;
  1512. git fetch upstream
  1513. git reset upstream/gh-pages
  1514. git clone https://github.com/phodal-archive/echeveria-deploy code
  1515. git clone https://github.com/phodal-archive/echeveria-content content
  1516. pwd
  1517. cp -a content/contents code/content
  1518. cd code
  1519. npm install
  1520. npm install grunt-cli -g
  1521. grunt
  1522. mv dest/* ../
  1523. cd ../
  1524. rm -rf code
  1525. rm -rf content
  1526. touch .
  1527. if [ ! -f CNAME ]; then
  1528. echo &quot;deploy.baimizhou.net&quot; &gt; CNAME
  1529. fi
  1530. git add -A .
  1531. git commit -m &quot;rebuild pages at ${rev}&quot;
  1532. git push -q upstream HEAD:gh-pages</code></pre>
  1533. <p>这就是这个builder做的事情——其中最主要的一个任务是<code>grunt</code>,它所做的就是:</p>
  1534. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">grunt</span>.<span class="at">registerTask</span>(<span class="st">&#39;default&#39;</span><span class="op">,</span> [<span class="st">&#39;clean&#39;</span><span class="op">,</span> <span class="st">&#39;assemble&#39;</span><span class="op">,</span> <span class="st">&#39;copy&#39;</span>])<span class="op">;</span></code></pre></div>
  1535. <h2 id="contentjson格式">Content:JSON格式</h2>
  1536. <p>在使用Github和Travis CI完成Content的时候,发现没有一个好的Webhook。虽然我们的Content只能存储一些数据,但是放一个trigger脚本也是可以原谅的。</p>
  1537. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="kw">var</span> Travis <span class="op">=</span> <span class="at">require</span>(<span class="st">&#39;travis-ci&#39;</span>)<span class="op">;</span>
  1538. <span class="kw">var</span> repo <span class="op">=</span> <span class="st">&quot;phodal-archive/echeveria-deploy&quot;</span><span class="op">;</span>
  1539. <span class="kw">var</span> travis <span class="op">=</span> <span class="kw">new</span> <span class="at">Travis</span>(<span class="op">{</span>
  1540. <span class="dt">version</span><span class="op">:</span> <span class="st">&#39;2.0.0&#39;</span>
  1541. <span class="op">}</span>)<span class="op">;</span>
  1542. <span class="va">travis</span>.<span class="at">authenticate</span>(<span class="op">{</span>
  1543. <span class="dt">github_token</span><span class="op">:</span> <span class="va">process</span>.<span class="va">env</span>.<span class="at">GH_TOKEN</span>
  1544. <span class="op">},</span> <span class="kw">function</span> (err<span class="op">,</span> res) <span class="op">{</span>
  1545. <span class="cf">if</span> (err) <span class="op">{</span>
  1546. <span class="cf">return</span> <span class="va">console</span>.<span class="at">error</span>(err)<span class="op">;</span>
  1547. <span class="op">}</span>
  1548. <span class="va">travis</span>.<span class="at">repos</span>(<span class="va">repo</span>.<span class="at">split</span>(<span class="st">&#39;/&#39;</span>)[<span class="dv">0</span>]<span class="op">,</span> <span class="va">repo</span>.<span class="at">split</span>(<span class="st">&#39;/&#39;</span>)[<span class="dv">1</span>]).<span class="va">builds</span>.<span class="at">get</span>(<span class="kw">function</span> (err<span class="op">,</span> res) <span class="op">{</span>
  1549. <span class="cf">if</span> (err) <span class="op">{</span>
  1550. <span class="cf">return</span> <span class="va">console</span>.<span class="at">error</span>(err)<span class="op">;</span>
  1551. <span class="op">}</span>
  1552. <span class="va">travis</span>.<span class="va">requests</span>.<span class="at">post</span>(<span class="op">{</span>
  1553. <span class="dt">build_id</span><span class="op">:</span> <span class="va">res</span>.<span class="at">builds</span>[<span class="dv">0</span>].<span class="at">id</span>
  1554. <span class="op">},</span> <span class="kw">function</span> (err<span class="op">,</span> res) <span class="op">{</span>
  1555. <span class="cf">if</span> (err) <span class="op">{</span>
  1556. <span class="cf">return</span> <span class="va">console</span>.<span class="at">error</span>(err)<span class="op">;</span>
  1557. <span class="op">}</span>
  1558. <span class="va">console</span>.<span class="at">log</span>(<span class="va">res</span>.<span class="at">flash</span>[<span class="dv">0</span>].<span class="at">notice</span>)<span class="op">;</span>
  1559. <span class="op">}</span>)<span class="op">;</span>
  1560. <span class="op">}</span>)<span class="op">;</span>
  1561. <span class="op">}</span>)<span class="op">;</span></code></pre></div>
  1562. <p>这里主要依赖于Travis CI来完成这部分功能,这时候我们还需要数据。</p>
  1563. <h3 id="从schema到数据库">从Schema到数据库</h3>
  1564. <p>我们在我们数据库中定义好了Schema——对一个数据库的结构描述。在《<a href="https://www.phodal.com/blog/editing-publishing-coding-seperate/">编辑-发布-开发分离</a> 》一文中我们说到了echeveria-content的一个数据文件如下所示:</p>
  1565. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> <span class="op">{</span>
  1566. <span class="st">&quot;title&quot;</span><span class="op">:</span> <span class="st">&quot;白米粥&quot;</span><span class="op">,</span>
  1567. <span class="st">&quot;author&quot;</span><span class="op">:</span> <span class="st">&quot;白米粥&quot;</span><span class="op">,</span>
  1568. <span class="st">&quot;url&quot;</span><span class="op">:</span> <span class="st">&quot;baimizhou&quot;</span><span class="op">,</span>
  1569. <span class="st">&quot;date&quot;</span><span class="op">:</span> <span class="st">&quot;2015-10-21&quot;</span><span class="op">,</span>
  1570. <span class="st">&quot;description&quot;</span><span class="op">:</span> <span class="st">&quot;# Blog post </span><span class="sc">\n</span><span class="st"> &gt; This is an example blog post </span><span class="sc">\n</span><span class="st"> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. &quot;</span><span class="op">,</span>
  1571. <span class="st">&quot;blogpost&quot;</span><span class="op">:</span> <span class="st">&quot;# Blog post </span><span class="sc">\n</span><span class="st"> &gt; This is an example blog post </span><span class="sc">\n</span><span class="st"> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. </span><span class="sc">\n</span><span class="st"> Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.&quot;</span>
  1572. <span class="op">}</span></code></pre></div>
  1573. <p>比起之前的直接生成静态页面这里的数据就是更有意思地一步了,我们从数据库读取数据就是为了生成一个JSON文件。何不直接以JSON的形式存储文件呢?</p>
  1574. <p>我们都定义了这每篇文章的基本元素:</p>
  1575. <ol type="1">
  1576. <li>title</li>
  1577. <li>author</li>
  1578. <li>date</li>
  1579. <li>description</li>
  1580. <li>content</li>
  1581. <li>url</li>
  1582. </ol>
  1583. <p>即使我们使用NoSQL我们也很难逃离这种模式。我们定义这些数据,为了在使用的时候更方便。存储这些数据只是这个过程中的一部分,下部分就是取出这些数据并对他们进行过滤,取出我们需要的数据。</p>
  1584. <p>Web的骨架就是这么简单,当然APP也是如此。难的地方在于存储怎样的数据,返回怎样的数据。不同的网站存储着不同的数据,如淘宝存储的是商品的信息,Google存储着各种网站的数据——人们需要不同的方式去存储这些数据,为了更好地存储衍生了更多的数据存储方案——于是有了GFS、Haystack等等。运营型网站想尽办法为最后一公里努力着,成长型的网站一直在想着怎样更好的返回数据,从更好的用户体验到机器学习。而数据则是这个过程中不变的东西。</p>
  1585. <p>尽管,我已经想了很多办法去尽可能减少元素——在最开始的版本里只有标题和内容。然而为了满足我们在数据库中定义的结构,不得不造出来这么多对于一般用户不友好的字段。如链接名是为了存储的文件名而存在的,即这个链接名在最后会变成文件名:</p>
  1586. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">repo</span>.<span class="at">write</span>(<span class="st">&#39;master&#39;</span><span class="op">,</span> <span class="st">&#39;contents/&#39;</span> <span class="op">+</span> <span class="va">data</span>.<span class="at">url</span> <span class="op">+</span> <span class="st">&#39;.json&#39;</span><span class="op">,</span> stringifyData<span class="op">,</span> <span class="st">&#39;Robot: add article &#39;</span> <span class="op">+</span> <span class="va">data</span>.<span class="at">title</span><span class="op">,</span> options<span class="op">,</span> <span class="kw">function</span> (err<span class="op">,</span> data) <span class="op">{</span>
  1587. <span class="cf">if</span>(<span class="va">data</span>.<span class="at">commit</span>)<span class="op">{</span>
  1588. <span class="va">that</span>.<span class="at">setState</span>(<span class="op">{</span><span class="dt">message</span><span class="op">:</span> <span class="st">&quot;上传成功&quot;</span> <span class="op">+</span> <span class="va">JSON</span>.<span class="at">stringify</span>(data)<span class="op">}</span>)<span class="op">;</span>
  1589. <span class="va">that</span>.<span class="va">refs</span>.<span class="va">snackbar</span>.<span class="at">show</span>()<span class="op">;</span>
  1590. <span class="va">that</span>.<span class="at">setState</span>(<span class="op">{</span>
  1591. <span class="dt">sending</span><span class="op">:</span> <span class="dv">0</span>
  1592. <span class="op">}</span>)<span class="op">;</span>
  1593. <span class="op">}</span>
  1594. <span class="op">}</span>)<span class="op">;</span></code></pre></div>
  1595. <p>然后,上面的数据就会变成一个对象存储到“数据库”中。</p>
  1596. <p>今天 ,仍然有很多人用Word、Excel来存储数据。因为对于他们来说,这些软件更为直接,他们简单地操作一下就可以对数据进行排序、筛选。数据以怎样的形式存储并不重要,重要的是他们都以文件的形式存储着。</p>
  1597. <h3 id="git作为nosql数据库">git作为NoSQL数据库</h3>
  1598. <p>不同的数据库会以不同的形式存储到文件中去。blob是git中最为基本的存储单位,我们的每个content都是一个blob。redis可以以rdb文件的形式存储到文件系统中。完成一个CMS,我们并不需要那么多的查询功能。</p>
  1599. <blockquote>
  1600. <p>这些上千年的组织机构,只想让人们知道他们想要说的东西。</p>
  1601. </blockquote>
  1602. <p>我们使用NoSQL是因为:</p>
  1603. <ol type="1">
  1604. <li>不使用关系模型</li>
  1605. <li>在集群中运行良好</li>
  1606. <li>开源</li>
  1607. <li>无模式</li>
  1608. <li>数据交换格式</li>
  1609. </ol>
  1610. <p>我想其中只有两点对于我来说是比较重要的<code>集群</code>与<code>数据格式</code>。但是集群和数据格式都不是我们要考虑的问题。。。</p>
  1611. <p>我们也不存在数据格式的问题、开源的问题,什么问题都没有。。除了,我们之前说到的查询——但是这是可以解决的问题,我们甚至可以返回不同的历史版本的。在这一点上git做得很好,他不会像WordPress那样存储多个版本。</p>
  1612. <p>JSON文件 + Nginx就可以变成这样一个合理的API,甚至是运行方式。我们可以对其进行增、删、改、查,尽管就当前来说查需要一个额外的软件来执行,但是为了实现一个用得比较少的功能,而去花费大把的时间可能就是在浪费。</p>
  1613. <p>git的“API”提供了丰富的增、删、改功能——你需要commit就可以了。我们所要做的就是:</p>
  1614. <ol type="1">
  1615. <li>git commit</li>
  1616. <li>git push</li>
  1617. </ol>
  1618. <p>于是,就会有一个很忙的Travis-Github Robot在默默地为你工作。</p>
  1619. <figure>
  1620. <img src="./img/basis/robot-commit.png" alt="Robot提交代码" /><figcaption>Robot提交代码</figcaption>
  1621. </figure>
  1622. <h2 id="一键发布编辑器">一键发布:编辑器</h2>
  1623. <p>为了实现之前说到的<code>编辑-发布-开发分离</code>的CMS,我还是花了两天的时间打造了一个面向普通用户的编辑器。效果截图如下所示:</p>
  1624. <figure>
  1625. <img src="./img/cms/editor.png" alt="编辑器" /><figcaption>编辑器</figcaption>
  1626. </figure>
  1627. <p>作为一个普通用户,这是一个很简单的软件。除了Electron + Node.js + React作了一个140M左右的软件,尽管压缩完只有40M左右 ,但是还是会把用户吓跑的。不过作为一个快速构建的原型已经很不错了——构建速度很快、并且运行良好。</p>
  1628. <ul>
  1629. <li>Electron</li>
  1630. <li>React</li>
  1631. <li>Material UI</li>
  1632. <li>Alloy Editor</li>
  1633. </ul>
  1634. <p>尽管这个界面看上去还是稍微复杂了一下,还在试着想办法将链接名和日期去掉——问题是为什么会有这两个东西?</p>
  1635. <p>Webpack 打包</p>
  1636. <pre><code> if (process.env.HOT) {
  1637. mainWindow.loadUrl(&#39;file://&#39; + __dirname + &#39;/app/hot-dev-app.html&#39;);
  1638. } else {
  1639. mainWindow.loadUrl(&#39;file://&#39; + __dirname + &#39;/app/app.html&#39;);
  1640. }</code></pre>
  1641. <p>上传代码</p>
  1642. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="va">repo</span>.<span class="at">write</span>(<span class="st">&#39;master&#39;</span><span class="op">,</span> <span class="st">&#39;content/&#39;</span> <span class="op">+</span> <span class="va">data</span>.<span class="at">url</span> <span class="op">+</span> <span class="st">&#39;.json&#39;</span><span class="op">,</span> stringifyData<span class="op">,</span> <span class="st">&#39;Robot: add article &#39;</span> <span class="op">+</span> <span class="va">data</span>.<span class="at">title</span><span class="op">,</span> options<span class="op">,</span> <span class="kw">function</span> (err<span class="op">,</span> data) <span class="op">{</span>
  1643. <span class="cf">if</span>(<span class="va">data</span>.<span class="at">commit</span>)<span class="op">{</span>
  1644. <span class="va">that</span>.<span class="at">setState</span>(<span class="op">{</span><span class="dt">message</span><span class="op">:</span> <span class="st">&quot;上传成功&quot;</span> <span class="op">+</span> <span class="va">JSON</span>.<span class="at">stringify</span>(data)<span class="op">}</span>)<span class="op">;</span>
  1645. <span class="va">that</span>.<span class="va">refs</span>.<span class="va">snackbar</span>.<span class="at">show</span>()<span class="op">;</span>
  1646. <span class="va">that</span>.<span class="at">setState</span>(<span class="op">{</span>
  1647. <span class="dt">sending</span><span class="op">:</span> <span class="dv">0</span>
  1648. <span class="op">}</span>)<span class="op">;</span>
  1649. <span class="op">}</span>
  1650. <span class="op">}</span>)<span class="op">;</span></code></pre></div>
  1651. <p>当我们点下发送的时侯,这个内容就直接提交到了Content Repo下,如上上图所示。</p>
  1652. <p>当我们向Content Push代码的时候,就会运行一下Trigger脚本:</p>
  1653. <div class="sourceCode"><pre class="sourceCode yml"><code class="sourceCode yaml"><span class="fu">after_success:</span>
  1654. <span class="kw">-</span> node trigger-build.js</code></pre></div>
  1655. <p>脚本的代码如下所示:</p>
  1656. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="kw">var</span> Travis <span class="op">=</span> <span class="at">require</span>(<span class="st">&#39;travis-ci&#39;</span>)<span class="op">;</span>
  1657. <span class="kw">var</span> repo <span class="op">=</span> <span class="st">&quot;phodal-archive/echeveria-deploy&quot;</span><span class="op">;</span>
  1658. <span class="kw">var</span> travis <span class="op">=</span> <span class="kw">new</span> <span class="at">Travis</span>(<span class="op">{</span>
  1659. <span class="dt">version</span><span class="op">:</span> <span class="st">&#39;2.0.0&#39;</span>
  1660. <span class="op">}</span>)<span class="op">;</span>
  1661. <span class="va">travis</span>.<span class="at">authenticate</span>(<span class="op">{</span>
  1662. <span class="dt">github_token</span><span class="op">:</span> <span class="va">process</span>.<span class="va">env</span>.<span class="at">GH_TOKEN</span>
  1663. <span class="op">},</span> <span class="kw">function</span> (err<span class="op">,</span> res) <span class="op">{</span>
  1664. <span class="cf">if</span> (err) <span class="op">{</span>
  1665. <span class="cf">return</span> <span class="va">console</span>.<span class="at">error</span>(err)<span class="op">;</span>
  1666. <span class="op">}</span>
  1667. <span class="va">travis</span>.<span class="at">repos</span>(<span class="va">repo</span>.<span class="at">split</span>(<span class="st">&#39;/&#39;</span>)[<span class="dv">0</span>]<span class="op">,</span> <span class="va">repo</span>.<span class="at">split</span>(<span class="st">&#39;/&#39;</span>)[<span class="dv">1</span>]).<span class="va">builds</span>.<span class="at">get</span>(<span class="kw">function</span> (err<span class="op">,</span> res) <span class="op">{</span>
  1668. <span class="cf">if</span> (err) <span class="op">{</span>
  1669. <span class="cf">return</span> <span class="va">console</span>.<span class="at">error</span>(err)<span class="op">;</span>
  1670. <span class="op">}</span>
  1671. <span class="va">travis</span>.<span class="va">requests</span>.<span class="at">post</span>(<span class="op">{</span>
  1672. <span class="dt">build_id</span><span class="op">:</span> <span class="va">res</span>.<span class="at">builds</span>[<span class="dv">0</span>].<span class="at">id</span>
  1673. <span class="op">},</span> <span class="kw">function</span> (err<span class="op">,</span> res) <span class="op">{</span>
  1674. <span class="cf">if</span> (err) <span class="op">{</span>
  1675. <span class="cf">return</span> <span class="va">console</span>.<span class="at">error</span>(err)<span class="op">;</span>
  1676. <span class="op">}</span>
  1677. <span class="va">console</span>.<span class="at">log</span>(<span class="va">res</span>.<span class="at">flash</span>[<span class="dv">0</span>].<span class="at">notice</span>)<span class="op">;</span>
  1678. <span class="op">}</span>)<span class="op">;</span>
  1679. <span class="op">}</span>)<span class="op">;</span>
  1680. <span class="op">}</span>)<span class="op">;</span></code></pre></div>
  1681. <p>由于,我们在这个过程我们的Content提交的是JSON数据,我们可以直接用这些数据做一个APP。</p>
  1682. <h2 id="移动应用">移动应用</h2>
  1683. <p>为了快速开发,这里我们使用了Ionic + ngCordova来开发 ,最后效果图如下所示:</p>
  1684. <figure>
  1685. <img src="./img/basis/app.png" alt="移动应用" /><figcaption>移动应用</figcaption>
  1686. </figure>
  1687. <p>在这个代码库里,主要由两部分组成:</p>
  1688. <ol type="1">
  1689. <li>获取全部文章</li>
  1690. <li>获取特定文章</li>
  1691. </ol>
  1692. <p>为了获取全部文章就意味着,我们在Builder里,需要一个task来合并JSON文件,并删掉其中的一些无用的内容,如articleHTML和article。最后,将生成一个名为articles.json。</p>
  1693. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="cf">if</span> (<span class="op">!</span><span class="va">grunt</span>.<span class="va">file</span>.<span class="at">exists</span>(src))
  1694. <span class="cf">throw</span> <span class="st">&quot;JSON source file </span><span class="sc">\&quot;</span><span class="st">&quot;</span> <span class="op">+</span> <span class="va">chalk</span>.<span class="at">red</span>(src) <span class="op">+</span> <span class="st">&quot;</span><span class="sc">\&quot;</span><span class="st"> not found.&quot;</span><span class="op">;</span>
  1695. <span class="cf">else</span> <span class="op">{</span>
  1696. <span class="kw">var</span> fragment<span class="op">;</span>
  1697. <span class="va">grunt</span>.<span class="va">log</span>.<span class="at">debug</span>(<span class="st">&quot;reading JSON source file </span><span class="sc">\&quot;</span><span class="st">&quot;</span> <span class="op">+</span> <span class="va">chalk</span>.<span class="at">green</span>(src) <span class="op">+</span> <span class="st">&quot;</span><span class="sc">\&quot;</span><span class="st">&quot;</span>)<span class="op">;</span>
  1698. <span class="cf">try</span> <span class="op">{</span>
  1699. fragment <span class="op">=</span> <span class="va">grunt</span>.<span class="va">file</span>.<span class="at">readJSON</span>(src)<span class="op">;</span>
  1700. <span class="op">}</span>
  1701. <span class="cf">catch</span> (e) <span class="op">{</span>
  1702. <span class="va">grunt</span>.<span class="va">fail</span>.<span class="at">warn</span>(e)<span class="op">;</span>
  1703. <span class="op">}</span>
  1704. <span class="va">fragment</span>.<span class="at">description</span> <span class="op">=</span> <span class="at">sanitizeHtml</span>(<span class="va">fragment</span>.<span class="at">article</span>).<span class="at">substring</span>(<span class="dv">0</span><span class="op">,</span> <span class="dv">200</span>)<span class="op">;</span>
  1705. <span class="kw">delete</span> <span class="va">fragment</span>.<span class="at">article</span><span class="op">;</span>
  1706. <span class="kw">delete</span> <span class="va">fragment</span>.<span class="at">articleHTML</span><span class="op">;</span>
  1707. <span class="va">json</span>.<span class="at">push</span>(fragment)<span class="op">;</span>
  1708. <span class="op">}</span></code></pre></div>
  1709. <p>接着,我们就可以获取所有的文章然后显示~~。在这里又顺便加了一个pullToRefresh。</p>
  1710. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> .<span class="at">controller</span>(<span class="st">&#39;ArticleListsCtrl&#39;</span><span class="op">,</span> <span class="kw">function</span> ($scope<span class="op">,</span> Blog) <span class="op">{</span>
  1711. <span class="va">$scope</span>.<span class="at">articles</span> <span class="op">=</span> <span class="kw">null</span><span class="op">;</span>
  1712. <span class="va">$scope</span>.<span class="at">blogOffset</span> <span class="op">=</span> <span class="dv">0</span><span class="op">;</span>
  1713. <span class="va">$scope</span>.<span class="at">doRefresh</span> <span class="op">=</span> <span class="kw">function</span> () <span class="op">{</span>
  1714. <span class="va">Blog</span>.<span class="at">async</span>(<span class="st">&#39;http://deploy.baimizhou.net/api/blog/articles.json&#39;</span>).<span class="at">then</span>(<span class="kw">function</span> (results) <span class="op">{</span>
  1715. <span class="va">$scope</span>.<span class="at">articles</span> <span class="op">=</span> results<span class="op">;</span>
  1716. <span class="op">}</span>)<span class="op">;</span>
  1717. <span class="va">$scope</span>.<span class="at">$broadcast</span>(<span class="st">&#39;scroll.refreshComplete&#39;</span>)<span class="op">;</span>
  1718. <span class="va">$scope</span>.<span class="at">$apply</span>()
  1719. <span class="op">};</span>
  1720. <span class="va">Blog</span>.<span class="at">async</span>(<span class="st">&#39;http://deploy.baimizhou.net/api/blog/articles.json&#39;</span>).<span class="at">then</span>(<span class="kw">function</span> (results) <span class="op">{</span>
  1721. <span class="va">$scope</span>.<span class="at">articles</span> <span class="op">=</span> results<span class="op">;</span>
  1722. <span class="op">}</span>)<span class="op">;</span>
  1723. <span class="op">}</span>)</code></pre></div>
  1724. <p>最后,当我们点击特定的url,将跳转到相应的页面:</p>
  1725. <div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw">&lt;ion-item</span><span class="ot"> class=</span><span class="st">&quot;item item-icon-right&quot;</span><span class="ot"> ng-repeat=</span><span class="st">&quot;article in articles&quot;</span><span class="ot"> type=</span><span class="st">&quot;item-text-wrap&quot;</span><span class="ot"> href=</span><span class="st">&quot;#/app/article/{{article.url}}&quot;</span><span class="kw">&gt;</span>
  1726. <span class="kw">&lt;h2&gt;</span>{{article.title}}<span class="kw">&lt;/h2&gt;</span>
  1727. <span class="kw">&lt;i</span><span class="ot"> class=</span><span class="st">&quot;icon ion-ios-arrow-right&quot;</span><span class="kw">&gt;&lt;/i&gt;</span>
  1728. <span class="kw">&lt;/ion-item&gt;</span></code></pre></div>
  1729. <p>就会交由相应的Controller来处理。</p>
  1730. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> .<span class="at">controller</span>(<span class="st">&#39;ArticleCtrl&#39;</span><span class="op">,</span> <span class="kw">function</span> ($scope<span class="op">,</span> $stateParams<span class="op">,</span> $sanitize<span class="op">,</span> $sce<span class="op">,</span> Blog) <span class="op">{</span>
  1731. <span class="va">$scope</span>.<span class="at">article</span> <span class="op">=</span> <span class="op">{};</span>
  1732. <span class="va">Blog</span>.<span class="at">async</span>(<span class="st">&#39;http://deploy.baimizhou.net/api/&#39;</span> <span class="op">+</span> <span class="va">$stateParams</span>.<span class="at">slug</span> <span class="op">+</span> <span class="st">&#39;.json&#39;</span>).<span class="at">then</span>(<span class="kw">function</span> (results) <span class="op">{</span>
  1733. <span class="va">$scope</span>.<span class="at">article</span> <span class="op">=</span> results<span class="op">;</span>
  1734. <span class="va">$scope</span>.<span class="at">htmlContent</span> <span class="op">=</span> <span class="va">$sce</span>.<span class="at">trustAsHtml</span>(<span class="va">$scope</span>.<span class="va">article</span>.<span class="at">articleHTML</span>)<span class="op">;</span>
  1735. <span class="op">}</span>)<span class="op">;</span>
  1736. <span class="op">}</span>)<span class="op">;</span></code></pre></div>
  1737. <h3 id="小结">小结</h3>
  1738. <p>尽管没有一个更成熟的环境可以探索这其中的问题,但是我想对于当前这种情况来说,它是非常棒的解决方案。我们面向的不是那些技术人员,而是一般的用户。他们能熟练使用的是:编辑器和APP。</p>
  1739. <ol type="1">
  1740. <li>不会因为后台的升级来困扰他们,也不会受其他组件的影响。</li>
  1741. <li>开发人员不需要担心,某个功能影响了编辑器的使用。</li>
  1742. <li>Ops不再担心网站的性能问题——然后要么转为DevOps、要么被Fire。</li>
  1743. </ol>
  1744. <h3 id="其他-2">其他</h3>
  1745. <p>最后的代码库:</p>
  1746. <ol type="1">
  1747. <li>Content: <a href="https://github.com/phodal-archive/echeveria-content" class="uri">https://github.com/phodal-archive/echeveria-content</a></li>
  1748. <li>Code: <a href="https://github.com/phodal-archive/echeveria-deploy" class="uri">https://github.com/phodal-archive/echeveria-deploy</a></li>
  1749. <li>移动应用: <a href="https://github.com/phodal-archive/echeveria-mobile" class="uri">https://github.com/phodal-archive/echeveria-mobile</a></li>
  1750. <li>桌面应用: <a href="https://github.com/phodal/echeveria-editor" class="uri">https://github.com/phodal/echeveria-editor</a></li>
  1751. <li>Github Pages: <a href="https://github.com/phodal-archive/echeveria-deploy/tree/gh-pages" class="uri">https://github.com/phodal-archive/echeveria-deploy/tree/gh-pages</a></li>
  1752. </ol>
  1753. <h1 id="模式篇设计与架构">模式篇:设计与架构</h1>
  1754. <p>设计模式算是在面向对象中比较有趣的东西,特别是对于像我,这样的用得不是很多的。虽然有时候也会用上,但是并不知道用的是怎样的模式。之前学习了一段时间的设计模式,实际上也就是将平常经常用到的一些东西进行了总结,如此而已。学习设计模式的另外一个重要的意义在于,我们使用了设计模式的时候我们会知道自己使用了,并且还会知道用了是怎样的设计模式。</p>
  1755. <p>至于设计模式这个东西和有些东西一样,是发现的而不是发明的,换句话说,我们可以将经常合到一起的几种模式用一个新的模式来命名,它是复合模式,但是也可以用别的模式来命名。</p>
  1756. <p>设计模式算是简化了我们在面向对象设计时候的诸多不足,这个在系统设计的初期有时候会有一定的作用,不过多数时候对于我来说,会用上他的时候,多半是在重构的时候,因为不是很熟悉。</p>
  1757. <h2 id="观察者模式">观察者模式</h2>
  1758. <p>观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。</p>
  1759. <p>观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。</p>
  1760. <p>一个软件系统常常要求在某一个对象的状态发生变化的时候,某些其它的对象做出相应的改变。做到这一点的设计方案有很多,但是为了使系统能够易于复用,应该选择低耦合度的设计方案。减少对象之间的耦合有利于系统的复用,但是同时设计师需要使这些低耦合度的对象之间能够维持行动的协调一致,保证高度的协作(Collaboration)。观察者模式是满足这一要求的各种设计方案中最重要的一种。</p>
  1761. <p>简单的来说,就是当我们监测到一个元素变化的时候,另外的元素依照此而改变。</p>
  1762. <h3 id="ruby观察者模式">Ruby观察者模式</h3>
  1763. <p>Ruby中为实现Observer模式提供了名为observer的库,observer库提供了Observer模块。 其API如下所示</p>
  1764. <table>
  1765. <thead>
  1766. <tr class="header">
  1767. <th>方法名</th>
  1768. <th>功 能</th>
  1769. </tr>
  1770. </thead>
  1771. <tbody>
  1772. <tr class="odd">
  1773. <td>add_observer(observer)</td>
  1774. <td>添加观察者</td>
  1775. </tr>
  1776. <tr class="even">
  1777. <td>delete_observer(observer)</td>
  1778. <td>删除特定观察者</td>
  1779. </tr>
  1780. <tr class="odd">
  1781. <td>delete_observer</td>
  1782. <td>删除观察者</td>
  1783. </tr>
  1784. <tr class="even">
  1785. <td>count_observer</td>
  1786. <td>观察者的数目</td>
  1787. </tr>
  1788. <tr class="odd">
  1789. <td>change(state=true)</td>
  1790. <td>设置更新标志为真</td>
  1791. </tr>
  1792. <tr class="even">
  1793. <td>changed?</td>
  1794. <td>检查更新标志</td>
  1795. </tr>
  1796. <tr class="odd">
  1797. <td>notify_observer(*arg)</td>
  1798. <td>通知更新,如果更新标志为真,调用观察者带参数arg的方法</td>
  1799. </tr>
  1800. </tbody>
  1801. </table>
  1802. <h4 id="ruby观察者简单示例">Ruby观察者简单示例</h4>
  1803. <p>这里要做的就是获取一个json数据,将这个数据更新出来。</p>
  1804. <p>获取json数据,同时解析。</p>
  1805. <div class="sourceCode"><pre class="sourceCode ruby"><code class="sourceCode ruby">require <span class="st">&#39;net/http&#39;</span>
  1806. require <span class="st">&#39;rubygems&#39;</span>
  1807. require <span class="st">&#39;json&#39;</span>
  1808. <span class="kw">class</span> <span class="dt">GetData</span>
  1809. <span class="ot">attr_reader</span><span class="st">:res</span>,<span class="st">:parsed</span>
  1810. <span class="kw">def</span> initialize(uri)
  1811. uri=<span class="dt">URI</span>(uri)
  1812. <span class="ot">@res</span>=<span class="dt">Net</span>::<span class="dt">HTTP</span>.get(uri)
  1813. <span class="ot">@parsed</span>=<span class="dt">JSON</span>.parse(res)
  1814. <span class="kw">end</span>
  1815. <span class="kw">def</span> id
  1816. <span class="ot">@parsed</span>[<span class="dv">0</span>][<span class="st">&quot;id&quot;</span>]
  1817. <span class="kw">end</span>
  1818. <span class="kw">def</span> sensors1
  1819. <span class="ot">@parsed</span>[<span class="dv">0</span>][<span class="st">&quot;sensors1&quot;</span>].round(<span class="dv">2</span>)
  1820. <span class="kw">end</span>
  1821. <span class="kw">def</span> sensors2
  1822. <span class="ot">@parsed</span>[<span class="dv">0</span>][<span class="st">&quot;sensors2&quot;</span>].round(<span class="dv">2</span>)
  1823. <span class="kw">end</span>
  1824. <span class="kw">def</span> temperature
  1825. <span class="ot">@parsed</span>[<span class="dv">0</span>][<span class="st">&quot;temperature&quot;</span>].round(<span class="dv">2</span>)
  1826. <span class="kw">end</span>
  1827. <span class="kw">def</span> led1
  1828. <span class="ot">@parsed</span>[<span class="dv">0</span>][<span class="st">&quot;led1&quot;</span>]
  1829. <span class="kw">end</span>
  1830. <span class="kw">end</span></code></pre></div>
  1831. <p>下面这个也就是重点,和观察者相关的,就是被观察者,由这个获取数据。 通过changed ,同时用notify_observer方法告诉观察者</p>
  1832. <div class="sourceCode"><pre class="sourceCode ruby"><code class="sourceCode ruby">require <span class="st">&#39;rubygems&#39;</span>
  1833. require <span class="st">&#39;thread&#39;</span>
  1834. require <span class="st">&#39;observer&#39;</span>
  1835. require <span class="st">&#39;getdata&#39;</span>
  1836. require <span class="st">&#39;ledstatus&#39;</span>
  1837. <span class="kw">class</span> <span class="dt">Led</span>
  1838. include <span class="dt">Observable</span>
  1839. <span class="ot">attr_reader</span><span class="st">:data</span>
  1840. <span class="kw">def</span> initialize
  1841. <span class="ot">@uri</span>=<span class="st">&#39;http://www.xianuniversity.com/athome/1&#39;</span>
  1842. <span class="kw">end</span>
  1843. <span class="kw">def</span> getdata
  1844. loop <span class="kw">do</span>
  1845. changed()
  1846. data=<span class="dt">GetData</span>.new(<span class="ot">@uri</span>)
  1847. changed
  1848. notify_observers(data.id,data.sensors1,data.sensors2,data.temperature,data.led1)
  1849. sleep <span class="dv">1</span>
  1850. <span class="kw">end</span>
  1851. <span class="kw">end</span>
  1852. <span class="kw">end</span></code></pre></div>
  1853. <p>然后让我们新建一个观察者</p>
  1854. <div class="sourceCode"><pre class="sourceCode ruby"><code class="sourceCode ruby"><span class="kw">class</span> <span class="dt">LedStatus</span>
  1855. <span class="kw">def</span> update(arg,sensors1,sensors2,temperature,led1)
  1856. puts <span class="st">&quot;id:</span><span class="ot">#{</span>arg<span class="ot">}</span><span class="st">,sensors1:</span><span class="ot">#{</span>sensors1<span class="ot">}</span><span class="st">,sensors2:</span><span class="ot">#{</span>sensors2<span class="ot">}</span><span class="st">,temperature:</span><span class="ot">#{</span>temperature<span class="ot">}</span><span class="st">,led1:</span><span class="ot">#{</span>led1<span class="ot">}</span><span class="st">&quot;</span>
  1857. <span class="kw">end</span>
  1858. <span class="kw">end</span></code></pre></div>
  1859. <p>测试</p>
  1860. <div class="sourceCode"><pre class="sourceCode ruby"><code class="sourceCode ruby">require <span class="st">&#39;spec_helper&#39;</span>
  1861. describe <span class="dt">LedStatus</span> <span class="kw">do</span>
  1862. let(<span class="st">:ledstatus</span>){<span class="dt">LedStatus</span>.new()}
  1863. describe <span class="st">&quot;Observable&quot;</span> <span class="kw">do</span>
  1864. it <span class="st">&quot;Should have a result&quot;</span> <span class="kw">do</span>
  1865. led=<span class="dt">Led</span>.new
  1866. led.add_observer(ledstatus)
  1867. led.getdata
  1868. <span class="kw">end</span>
  1869. <span class="kw">end</span>
  1870. <span class="kw">end</span></code></pre></div>
  1871. <p>测试结果如下所示</p>
  1872. <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">phodal@linux-dlkp</span>:~/tw/observer<span class="kw">&amp;</span><span class="ex">gt</span><span class="kw">;</span> <span class="ex">rake</span>
  1873. <span class="ex">/usr/bin/ruby1.9</span> -S rspec ./spec/getdata_spec.rb ./spec/ledstatus_spec.rb
  1874. <span class="ex">id</span>:1,sensors1:22.0,sensors2:11.0,temperature:10.0,led1:0
  1875. <span class="ex">id</span>:1,sensors1:22.0,sensors2:11.0,temperature:10.0,led1:1
  1876. <span class="ex">id</span>:1,sensors1:22.0,sensors2:11.0,temperature:10.0,led1:0
  1877. <span class="ex">id</span>:1,sensors1:22.0,sensors2:11.0,temperature:10.0,led1:1
  1878. <span class="ex">id</span>:1,sensors1:22.0,sensors2:11.0,temperature:10.0,led1:1
  1879. <span class="ex">id</span>:1,sensors1:22.0,sensors2:11.0,temperature:10.0,led1:1</code></pre></div>
  1880. <p>使用Ruby自带的Observer库的优点是,让我们可以简化相互之间的依赖性。同时,也能简化程序的结构,相比于自己写observer的情况下。</p>
  1881. <h3 id="pubsub">PUB/SUB</h3>
  1882. <h2 id="模板方法">模板方法</h2>
  1883. <p>原本对于设计模式的写作还不在当前的计划中,然而因为在写TWU作业的时候,觉得代码写得不好,于是慢慢试着一点点重构,重新看着设计模式。也开始记录这一点点的方法,至少这些步骤是必要的。</p>
  1884. <h3 id="从基本的app说起">从基本的App说起</h3>
  1885. <p>对于一个基本的C/C++/Java/Python的Application来说,他只需要有一个Main函数就够了。对于一个好一点的APP来说,他可能是下面的步骤,</p>
  1886. <div class="sourceCode"><pre class="sourceCode c"><code class="sourceCode c">main(){
  1887. init();
  1888. <span class="cf">while</span>(!condition()){
  1889. <span class="cf">do</span>();
  1890. }
  1891. }</code></pre></div>
  1892. <p>上面的代码是我在学51/AVR等各式嵌入式设备时,经常是按上面的写法写的,对于一个更符合人性的App来说他应该会有一个退出函数。</p>
  1893. <div class="sourceCode"><pre class="sourceCode c"><code class="sourceCode c">main(){
  1894. init();
  1895. <span class="cf">while</span>(!condition()){
  1896. <span class="cf">do</span>();
  1897. }
  1898. exit();
  1899. }</code></pre></div>
  1900. <p>于是很幸运地我找到了这样的一个例子。</p>
  1901. <p>过去看过Arduino的代码,了解过他是如何工作的,对于一个Arduino的代码来说,必要的两个函数就是。</p>
  1902. <div class="sourceCode"><pre class="sourceCode cpp"><code class="sourceCode cpp"><span class="dt">void</span> setup() {
  1903. }
  1904. <span class="dt">void</span> loop() {
  1905. }</code></pre></div>
  1906. <p>setup()函数相当于上面的init(),而loop()函数刚相当于上面的do()。似乎这就是我们想要的东西,看看Arduino目录中的Arduino.h就会发现,如下的代码(删减部分代码)</p>
  1907. <div class="sourceCode"><pre class="sourceCode cpp"><code class="sourceCode cpp"><span class="pp">#include </span><span class="im">&lt;Arduino.h&gt;</span>
  1908. <span class="dt">int</span> main(<span class="dt">void</span>)
  1909. {
  1910. init();
  1911. setup();
  1912. <span class="cf">for</span> (;;) {
  1913. loop();
  1914. <span class="cf">if</span> (serialEventRun) serialEventRun();
  1915. }
  1916. <span class="cf">return</span> <span class="dv">0</span>;
  1917. }</code></pre></div>
  1918. <p>代码中的for(;;)看上去似乎比while(True)容易理解得多,这也就是为什么嵌入式中经常用到的是for(;;),从某种意义上来说两者是等价的。再有不同的地方,就是gcc规定了,main()函数不能是void。so,两者是差不多的。只是没有,并没有在上面看到模板方法,等等。我们在上面所做的事情,便是创建一个框架。</p>
  1919. <h3 id="template-method">Template Method</h3>
  1920. <blockquote>
  1921. <p><strong>模板方法</strong>: 在一方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。</p>
  1922. </blockquote>
  1923. <p>对于我来说,我就是在基本的App中遇到的情况是一样的,在我的例子中,一开始我的代码是这样写的。</p>
  1924. <div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span>(<span class="bu">String</span>[] args) <span class="kw">throws</span> <span class="bu">IOException</span> {
  1925. <span class="fu">initLibrary</span>();
  1926. <span class="kw">while</span>(!isQuit){
  1927. <span class="fu">loop</span>();
  1928. }
  1929. exit;
  1930. }
  1931. <span class="kw">protected</span> <span class="dt">void</span> <span class="fu">initLibrary</span>(); {
  1932. <span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(welcomeMessage);
  1933. }
  1934. <span class="kw">protected</span> <span class="dt">void</span> <span class="fu">loop</span>() {
  1935. <span class="bu">String</span> key = <span class="st">&quot;&quot;</span>;
  1936. <span class="bu">Scanner</span> sc = <span class="kw">new</span> <span class="bu">Scanner</span>(<span class="bu">System</span>.<span class="fu">in</span>);
  1937. key = sc.<span class="fu">nextLine</span>();
  1938. <span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(results);
  1939. <span class="kw">if</span>(key.<span class="fu">equals</span>(<span class="st">&quot;Quit&quot;</span>)){
  1940. <span class="fu">setQuit</span>();
  1941. }
  1942. }
  1943. <span class="kw">protected</span> <span class="dt">void</span> <span class="fu">exit</span>() {
  1944. <span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(<span class="st">&quot;Quit Library&quot;</span>);
  1945. }</code></pre></div>
  1946. <p>只是这样写感觉很是别扭,看上去一点高大上的感觉,也木有。于是,打开书,找找灵感,就在《敏捷软件开发》一书中找到了类似的案例。Template Method模式可以分离能用的算法和具体的上下文,而我们通用的算法便是。</p>
  1947. <div class="sourceCode"><pre class="sourceCode c"><code class="sourceCode c">main(){
  1948. init();
  1949. <span class="cf">while</span>(!condition()){
  1950. <span class="cf">do</span>();
  1951. }
  1952. exit();
  1953. }</code></pre></div>
  1954. <p>看上去正好似乎我们当前的案例,于是便照猫画虎地来了一遍。</p>
  1955. <h3 id="template-method实战">Template Method实战</h3>
  1956. <p>创建了一个名为App的抽象基类,</p>
  1957. <div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">public</span> <span class="kw">abstract</span> <span class="kw">class</span> App {
  1958. <span class="kw">private</span> <span class="dt">boolean</span> isQuit = <span class="kw">false</span>;
  1959. <span class="kw">protected</span> <span class="kw">abstract</span> <span class="dt">void</span> <span class="fu">loop</span>();
  1960. <span class="kw">protected</span> <span class="kw">abstract</span> <span class="dt">void</span> <span class="fu">exit</span>();
  1961. <span class="kw">private</span> <span class="dt">boolean</span> <span class="fu">quit</span>() {
  1962. <span class="kw">return</span> isQuit;
  1963. }
  1964. <span class="kw">protected</span> <span class="dt">boolean</span> <span class="fu">setQuit</span>() {
  1965. <span class="kw">return</span> isQuit = <span class="kw">true</span>;
  1966. }
  1967. <span class="kw">protected</span> <span class="kw">abstract</span> <span class="dt">void</span> <span class="fu">init</span>();
  1968. <span class="kw">public</span> <span class="dt">void</span> <span class="fu">run</span>(){
  1969. <span class="fu">init</span>();
  1970. <span class="kw">while</span>(!<span class="fu">quit</span>()){
  1971. <span class="fu">loop</span>();
  1972. }
  1973. <span class="fu">exit</span>();
  1974. }
  1975. }</code></pre></div>
  1976. <p>而这个也和书中的一样,是一个通用的主循环应用程序。从应用的run函数中,可以看到主循环。而所有的工作也都交付给抽象方法,于是我们的LibraryApp就变成了</p>
  1977. <div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">public</span> <span class="kw">class</span> LibraryApp <span class="kw">extends</span> App {
  1978. <span class="kw">private</span> <span class="dt">static</span> <span class="bu">String</span> welcomeMessage = <span class="st">&quot;Welcome to Biblioteca library&quot;</span>;
  1979. <span class="kw">public</span> <span class="dt">static</span> <span class="dt">void</span> <span class="fu">main</span>(<span class="bu">String</span>[] args) <span class="kw">throws</span> <span class="bu">IOException</span> {
  1980. (<span class="kw">new</span> <span class="fu">LibraryApp</span>()).<span class="fu">run</span>();
  1981. }
  1982. <span class="kw">protected</span> <span class="dt">void</span> <span class="fu">init</span>() {
  1983. <span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(welcomeMessage);
  1984. }
  1985. <span class="kw">protected</span> <span class="dt">void</span> <span class="fu">loop</span>() {
  1986. <span class="bu">String</span> key = <span class="st">&quot;&quot;</span>;
  1987. <span class="bu">Scanner</span> sc = <span class="kw">new</span> <span class="bu">Scanner</span>(<span class="bu">System</span>.<span class="fu">in</span>);
  1988. key = sc.<span class="fu">nextLine</span>();
  1989. <span class="kw">if</span>(key.<span class="fu">equals</span>(<span class="st">&quot;Quit&quot;</span>)){
  1990. <span class="fu">setQuit</span>();
  1991. }
  1992. }
  1993. <span class="kw">protected</span> <span class="dt">void</span> <span class="fu">exit</span>() {
  1994. <span class="bu">System</span>.<span class="fu">out</span>.<span class="fu">println</span>(<span class="st">&quot;Quit Library&quot;</span>);
  1995. }
  1996. }</code></pre></div>
  1997. <p>然而,如书中所说<code>这是一个很好的用于示范TEMPLATE METHOD模式的例子,却不是一个合适的例子。</code></p>
  1998. <h2 id="pipe-and-filters">Pipe and Filters</h2>
  1999. <p>继续码点关于架构设计的一些小心得。架构是什么东西并没有那么重要,重要的是知道它存在过。我会面对不同的架构,有一些不同的想法。一个好的项目通常是存在一定的结构,就好像人们在建造房子的时候也都会有结构有一样。</p>
  2000. <p>我们看不到的架构,并不意味着这个架构不存在。</p>
  2001. <h3 id="unix-shell">Unix Shell</h3>
  2002. <p>最出名的Pipe便是Unix中的Shell</p>
  2003. <p><strong>管道(英语:Pipeline)是原始的软件管道:即是一个由标准输入输出链接起来的进程集合,所以每一个进程的输出(stdout)被直接作为下一个进程的输入(stdin)。 每一个链接都由未命名管道实现。过滤程序经常被用于这种设置。</strong></p>
  2004. <p>所以对于这样一个很好的操作便是,统计某种类型的文件的个数:</p>
  2005. <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">ls</span> -alh dot <span class="kw">|</span> <span class="fu">grep</span> .dot <span class="kw">|</span> <span class="fu">wc</span> -l</code></pre></div>
  2006. <p>在执行</p>
  2007. <div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="fu">ls</span> -alh dot</code></pre></div>
  2008. <p>的输出便是下一个的输入,直至最后一个输出。</p>
  2009. <p>这个过程有点类似于工厂处理废水,</p>
  2010. <p>[pipe and filter][1]</p>
  2011. <p>上图是一个理想模型~~。</p>
  2012. <p>一个明显地步骤是,水中的杂质越来越少。</p>
  2013. <h3 id="pipe-and-filter模式">Pipe and Filter模式</h3>
  2014. <p><strong>Pipe and Filter</strong>适合于处理数据流的系统。每个步骤都封装在一个过滤器组件中,数据通过相邻过滤器之间的管道传输。</p>
  2015. <ul>
  2016. <li><strong>pipe</strong>: 传输、缓冲数据。</li>
  2017. <li><strong>filter</strong>: 输入、处理、输出数据。</li>
  2018. </ul>
  2019. <p>这个处理过程有点类似于我们对数据库中数据的处理,不过可不会有这么多步骤。</p>
  2020. <h3 id="fluent-api">Fluent API</h3>
  2021. <p>这个过程也有点类似于Fluent API、链式调用,只是这些都是DSL的一种方式。</p>
  2022. <p>流畅接口的初衷是构建可读的API,毕竟代码是写给人看的。</p>
  2023. <p>类似的,简单的看一下早先我们是通过方法级联来操作DOM</p>
  2024. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="kw">var</span> btn <span class="op">=</span> <span class="va">document</span>.<span class="at">createElement</span>(<span class="st">&quot;BUTTON&quot;</span>)<span class="op">;</span> <span class="co">// Create a &lt;button&gt; element</span>
  2025. <span class="kw">var</span> t <span class="op">=</span> <span class="va">document</span>.<span class="at">createTextNode</span>(<span class="st">&quot;CLICK ME&quot;</span>)<span class="op">;</span> <span class="co">// Create a text node</span>
  2026. <span class="va">btn</span>.<span class="at">appendChild</span>(t)<span class="op">;</span> <span class="co">// Append the text to &lt;button&gt;</span>
  2027. <span class="va">document</span>.<span class="va">body</span>.<span class="at">appendChild</span>(btn)<span class="op">;</span> <span class="co">// Append &lt;button&gt; to &lt;body&gt;</span></code></pre></div>
  2028. <p>而用jQuery写的话,便是这样子</p>
  2029. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">$</span>(<span class="st">&#39;&lt;span&gt;&#39;</span>).<span class="at">append</span>(<span class="st">&quot;CLICK ME&quot;</span>)<span class="op">;</span></code></pre></div>
  2030. <p>等等</p>
  2031. <p>于是回我们便可以创建一个简单的示例来展示这个最简单的DSL</p>
  2032. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript">Func <span class="op">=</span> (<span class="kw">function</span>() <span class="op">{</span>
  2033. <span class="kw">this</span>.<span class="at">add</span> <span class="op">=</span> <span class="kw">function</span>()<span class="op">{</span>
  2034. <span class="va">console</span>.<span class="at">log</span>(<span class="st">&#39;1&#39;</span>)<span class="op">;</span>
  2035. <span class="cf">return</span> <span class="kw">this</span><span class="op">;</span>
  2036. <span class="op">};</span>
  2037. <span class="kw">this</span>.<span class="at">result</span> <span class="op">=</span> <span class="kw">function</span>()<span class="op">{</span>
  2038. <span class="va">console</span>.<span class="at">log</span>(<span class="st">&#39;2&#39;</span>)<span class="op">;</span>
  2039. <span class="cf">return</span> <span class="kw">this</span><span class="op">;</span>
  2040. <span class="op">};</span>
  2041. <span class="cf">return</span> <span class="kw">this</span><span class="op">;</span>
  2042. <span class="op">}</span>)<span class="op">;</span>
  2043. <span class="kw">var</span> func <span class="op">=</span> <span class="kw">new</span> <span class="at">Func</span>()<span class="op">;</span>
  2044. <span class="va">func</span>.<span class="at">add</span>().<span class="at">result</span>()<span class="op">;</span></code></pre></div>
  2045. <p>然而这看上去像是表达式生成器。</p>
  2046. <h3 id="dsl-表达式生成器">DSL 表达式生成器</h3>
  2047. <blockquote>
  2048. <p>表达式生成器对象提供一组连贯接口,之后将连贯接口调用转换为对底层命令-查询API的调用。</p>
  2049. </blockquote>
  2050. <p>这样的API,我们可以在一些关于数据库的API中看到:</p>
  2051. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="kw">var</span> query <span class="op">=</span>
  2052. <span class="at">SQL</span>(<span class="st">&#39;select name, desc from widgets&#39;</span>)
  2053. .<span class="at">WHERE</span>(<span class="st">&#39;price &lt; &#39;</span><span class="op">,</span> <span class="at">$</span>(<span class="va">params</span>.<span class="at">max_price</span>)<span class="op">,</span> AND<span class="op">,</span>
  2054. <span class="st">&#39;clearance = &#39;</span><span class="op">,</span> <span class="at">$</span>(<span class="va">params</span>.<span class="at">clearance</span>))
  2055. .<span class="at">ORDERBY</span>(<span class="st">&#39;name asc&#39;</span>)<span class="op">;</span></code></pre></div>
  2056. <p>链式调用有一个问题就是收尾,同上的代码里面我们没有收尾,这让人很迷惑。。加上一个query和end似乎是一个不错的结果。</p>
  2057. <h3 id="pipe-and-filter模式实战">Pipe and Filter模式实战</h3>
  2058. <p>所以,这个模式实际上更适合处理数据,如用Hadoop处理数据的时候,我们会用类似于如下的方法来处理我们的数据:</p>
  2059. <pre class="pig"><code>A = FOREACH LOGS_BASE GENERATE ToDate(timestamp, &#39;dd/MMM/yyyy:HH:mm:ss Z&#39;) as date, ip, url,(int)status,(int)bytes,referrer,useragent;
  2060. B = GROUP A BY (timestamp);
  2061. C = FOREACH B GENERATE FLATTEN(group) as (timestamp), COUNT(A) as count;
  2062. D = ORDER C BY timestamp,count desc;</code></pre>
  2063. <p>每一次都是在上一次处理完的结果后,再处理的。</p>
  2064. <p>参考书目</p>
  2065. <ul>
  2066. <li>《Head First 设计模式》</li>
  2067. <li>《设计模式》</li>
  2068. <li>《敏捷软件开发 原则、模式与实践》</li>
  2069. <li>《 面向模式的软件架构:模式系统》</li>
  2070. <li>《Java应用架构设计》</li>
  2071. </ul>
  2072. <h1 id="数据与模型篇">数据与模型篇</h1>
  2073. <p>无论是MVC、MVP或者MVVP,都离不开这些基本的要素:数据、表现、领域。</p>
  2074. <h2 id="数据">数据</h2>
  2075. <p>信息源于数据,我们在网站上看到的内容都应该是属于信息的范畴。这些信息是应用从数据库中根据业务需求查找、过滤出来的数据。</p>
  2076. <p>数据通常以文件的形式存储,毕竟文件是存储信息的基本单位。只是由于业务本身对于Create、Update、Query、Index等有不同的组合需求就引发了不同的数据存储软件。</p>
  2077. <p>如上章所说,View层直接从Model层取数据,无遗也会暴露数据的模型。作为一个前端开发人员,我们对数据的操作有三种类型:</p>
  2078. <ol type="1">
  2079. <li>数据库。由于Node.js在最近几年里发展迅猛,越来越多的开发者选择使用Node.js作为后台语言。这与传统的Model层并无多大不同,要么直接操作数据库,要么间接操作数据库。即使在NoSQL数据库中也是如此。</li>
  2080. <li>搜索引擎。对于以查询为主的领域来说,搜索引擎是一个更好的选择,而搜索引擎又不好直接向View层暴露接口。这和招聘信息一样,都在暴露公司的技术栈。</li>
  2081. <li>RESTful。RESTful相当于是CRUD的衍生,只是传输介质变了。</li>
  2082. <li>LocalStorage。LocalStorage算是另外一种方式的CRUD。</li>
  2083. </ol>
  2084. <p>说了这么多都是废话,他们都是可以用类CRUD的方式操作。</p>
  2085. <h3 id="数据库">数据库</h3>
  2086. <p>数据库里存储着大量的数据,在我们对系统建模的时候,也在决定系统的基础模型。</p>
  2087. <p>在传统SQL数据库中,我们可能会依赖于ORM,也可能会自己写SQL。在那之间,我们需要先定义Model,如下是Node.js的ORM框架Sequelize的一个示例:</p>
  2088. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="kw">var</span> User <span class="op">=</span> <span class="va">sequelize</span>.<span class="at">define</span>(<span class="st">&#39;user&#39;</span><span class="op">,</span> <span class="op">{</span>
  2089. <span class="dt">firstName</span><span class="op">:</span> <span class="op">{</span>
  2090. <span class="dt">type</span><span class="op">:</span> <span class="va">Sequelize</span>.<span class="at">STRING</span><span class="op">,</span>
  2091. <span class="dt">field</span><span class="op">:</span> <span class="st">&#39;first_name&#39;</span> <span class="co">// Will result in an attribute that is firstName when user facing but first_name in the database</span>
  2092. <span class="op">},</span>
  2093. <span class="dt">lastName</span><span class="op">:</span> <span class="op">{</span>
  2094. <span class="dt">type</span><span class="op">:</span> <span class="va">Sequelize</span>.<span class="at">STRING</span>
  2095. <span class="op">}</span>
  2096. <span class="op">},</span> <span class="op">{</span>
  2097. <span class="dt">freezeTableName</span><span class="op">:</span> <span class="kw">true</span> <span class="co">// Model tableName will be the same as the model name</span>
  2098. <span class="op">}</span>)<span class="op">;</span>
  2099. <span class="va">User</span>.<span class="at">sync</span>(<span class="op">{</span><span class="dt">force</span><span class="op">:</span> <span class="kw">true</span><span class="op">}</span>).<span class="at">then</span>(<span class="kw">function</span> () <span class="op">{</span>
  2100. <span class="co">// Table created</span>
  2101. <span class="cf">return</span> <span class="va">User</span>.<span class="at">create</span>(<span class="op">{</span>
  2102. <span class="dt">firstName</span><span class="op">:</span> <span class="st">&#39;John&#39;</span><span class="op">,</span>
  2103. <span class="dt">lastName</span><span class="op">:</span> <span class="st">&#39;Hancock&#39;</span>
  2104. <span class="op">}</span>)<span class="op">;</span>
  2105. <span class="op">}</span>)<span class="op">;</span></code></pre></div>
  2106. <p>像如MongoDB这类的数据库,也是存在数据模型,但说的却是嵌入子文档。在业务量大的情况下,数据库在考验公司的技术能力,想想便觉得Amazon RDS挺好的。</p>
  2107. <h3 id="建模">建模</h3>
  2108. <h1 id="领域篇">领域篇</h1>
  2109. <h2 id="ddd">DDD</h2>
  2110. <h2 id="dsl">DSL</h2>
  2111. <p>DSL(domain-specific languages)即领域特定语言,唯一能够确定DSL边界的方法是考虑“一门语言的一种特定用法”和“该语言的设计者或使用者的意图。在试图设计一个DSL的时候,发现了一些有意思的简单的示例。</p>
  2112. <h3 id="dsl示例">DSL示例</h3>
  2113. <h4 id="jquery-最流行的dsl">jQuery 最流行的DSL</h4>
  2114. <p>jQuery是一个Internal DSL的典型的例子。它是在一门现成语言内实现针对领域问题的描述。</p>
  2115. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">$</span>(<span class="st">&#39;.mydiv&#39;</span>).<span class="at">addClass</span>(<span class="st">&#39;flash&#39;</span>).<span class="at">draggable</span>().<span class="at">css</span>(<span class="st">&#39;color&#39;</span><span class="op">,</span> <span class="st">&#39;blue&#39;</span>)</code></pre></div>
  2116. <p>这也就是其最出名的<strong>链式方法调用</strong>。</p>
  2117. <h4 id="cucumber.js">Cucumber.js</h4>
  2118. <p>Cucumber, the popular Behaviour-Driven Development tool, brought to your JavaScript stack。它是使用通用语言描述该领域的问题。</p>
  2119. <pre class="cucumber"><code>Feature: Example feature
  2120. As a user of cucumber.js
  2121. I want to have documentation on cucumber
  2122. So that I can concentrate on building awesome applications
  2123. Scenario: Reading documentation
  2124. Given I am on the Cucumber.js GitHub repository
  2125. When I go to the README file
  2126. Then I should see &quot;Usage&quot; as the page title</code></pre>
  2127. <h4 id="coffeescript">CoffeeScript</h4>
  2128. <p>发明一门全新的语言描述该领域的问题。</p>
  2129. <div class="sourceCode"><pre class="sourceCode coffee"><code class="sourceCode coffee">math <span class="kw">=</span>
  2130. root<span class="kw">:</span> <span class="ot">Math</span><span class="kw">.</span>sqrt
  2131. square<span class="kw">:</span> square
  2132. cube<span class="kw">:</span> <span class="fu">(x) -&gt;</span> x <span class="kw">*</span> square x</code></pre></div>
  2133. <h4 id="javascript-dsl-示例">JavaScript DSL 示例</h4>
  2134. <p>所以由上面的结论我们可以知道的是,难度等级应该是</p>
  2135. <p>内部DSL &lt; 外部DSL &lt; 语言工作台(这是怎么翻译的)</p>
  2136. <p>接着在网上找到了一个高级一点的内部DSL示例,如果我们要做jQuery式的链式方法调用也是简单的,但是似乎没有足够的理由去说服其他人。</p>
  2137. <p>原文在: <a href="http://alexyoung.org/2009/10/22/javascript-dsl/" class="uri">http://alexyoung.org/2009/10/22/javascript-dsl/</a>,相当于是一个微测试框架。</p>
  2138. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="kw">var</span> DSLRunner <span class="op">=</span> <span class="op">{</span>
  2139. <span class="dt">run</span><span class="op">:</span> <span class="kw">function</span>(methods) <span class="op">{</span>
  2140. <span class="kw">this</span>.<span class="at">ingredients</span> <span class="op">=</span> []<span class="op">;</span>
  2141. <span class="kw">this</span>.<span class="at">methods</span> <span class="op">=</span> methods<span class="op">;</span>
  2142. <span class="kw">this</span>.<span class="at">executeAndRemove</span>(<span class="st">&#39;first&#39;</span>)<span class="op">;</span>
  2143. <span class="cf">for</span> (<span class="kw">var</span> key <span class="kw">in</span> <span class="kw">this</span>.<span class="at">methods</span>) <span class="op">{</span>
  2144. <span class="cf">if</span> (key <span class="op">!==</span> <span class="st">&#39;last&#39;</span> <span class="op">&amp;&amp;</span> <span class="va">key</span>.<span class="at">match</span>(<span class="ss">/</span><span class="sc">^</span><span class="ss">bake/</span>)) <span class="op">{</span>
  2145. <span class="kw">this</span>.<span class="at">executeAndRemove</span>(key)<span class="op">;</span>
  2146. <span class="op">}</span>
  2147. <span class="op">}</span>
  2148. <span class="kw">this</span>.<span class="at">executeAndRemove</span>(<span class="st">&#39;last&#39;</span>)<span class="op">;</span>
  2149. <span class="op">},</span>
  2150. <span class="dt">addIngredient</span><span class="op">:</span> <span class="kw">function</span>(ingredient) <span class="op">{</span>
  2151. <span class="kw">this</span>.<span class="va">ingredients</span>.<span class="at">push</span>(ingredient)<span class="op">;</span>
  2152. <span class="op">},</span>
  2153. <span class="dt">executeAndRemove</span><span class="op">:</span> <span class="kw">function</span>(methodName) <span class="op">{</span>
  2154. <span class="kw">var</span> output <span class="op">=</span> <span class="kw">this</span>.<span class="at">methods</span>[methodName]()<span class="op">;</span>
  2155. <span class="kw">delete</span>(<span class="kw">this</span>.<span class="at">methods</span>[methodName])<span class="op">;</span>
  2156. <span class="cf">return</span> output<span class="op">;</span>
  2157. <span class="op">}</span>
  2158. <span class="op">};</span>
  2159. <span class="va">DSLRunner</span>.<span class="at">run</span>(<span class="op">{</span>
  2160. <span class="dt">first</span><span class="op">:</span> <span class="kw">function</span>() <span class="op">{</span>
  2161. <span class="va">console</span>.<span class="at">log</span>(<span class="st">&quot;I happen first&quot;</span>)<span class="op">;</span>
  2162. <span class="op">},</span>
  2163. <span class="dt">bakeCake</span><span class="op">:</span> <span class="kw">function</span>() <span class="op">{</span>
  2164. <span class="va">console</span>.<span class="at">log</span>(<span class="st">&quot;Commencing cake baking&quot;</span>)<span class="op">;</span>
  2165. <span class="op">},</span>
  2166. <span class="dt">bakeBread</span><span class="op">:</span> <span class="kw">function</span>() <span class="op">{</span>
  2167. <span class="va">console</span>.<span class="at">log</span>(<span class="st">&quot;Baking bread&quot;</span>)<span class="op">;</span>
  2168. <span class="op">},</span>
  2169. <span class="dt">last</span><span class="op">:</span> <span class="kw">function</span>() <span class="op">{</span>
  2170. <span class="va">console</span>.<span class="at">log</span>(<span class="st">&quot;last&quot;</span>)<span class="op">;</span>
  2171. <span class="op">}</span>
  2172. <span class="op">}</span>)<span class="op">;</span></code></pre></div>
  2173. <p>这个想法,看上去就是定义了一些map,然后执行。</p>
  2174. <p>接着,又看到了一个有意思的DSL,作者是在解决表单验证的问题<a href="http://byatool.com/ui/javascript-dsl-because-im-tired-of-writing-if-if-if/">《JavaScript DSL Because I’m Tired of Writing If.. If…If…》</a>:</p>
  2175. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> <span class="kw">var</span> rules <span class="op">=</span>
  2176. [<span class="st">&#39;Username&#39;</span><span class="op">,</span>
  2177. [<span class="st">&#39;is not empty&#39;</span><span class="op">,</span> <span class="st">&#39;Username is required.&#39;</span>]<span class="op">,</span>
  2178. [<span class="st">&#39;is not longer than&#39;</span><span class="op">,</span> <span class="dv">7</span><span class="op">,</span> <span class="st">&#39;Username is too long.&#39;</span>]]<span class="op">,</span>
  2179. [<span class="st">&#39;Name&#39;</span><span class="op">,</span>
  2180. [<span class="st">&#39;is not empty&#39;</span><span class="op">,</span> <span class="st">&#39;Name is required.&#39;</span>]]<span class="op">,</span>
  2181. [<span class="st">&#39;Password&#39;</span><span class="op">,</span>
  2182. [<span class="st">&#39;length is between&#39;</span><span class="op">,</span> <span class="dv">4</span><span class="op">,</span> <span class="dv">6</span><span class="op">,</span> <span class="st">&#39;Password is not acceptable.&#39;</span>]]]<span class="op">;</span></code></pre></div>
  2183. <p>有一个map对应了上面的方法</p>
  2184. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> <span class="kw">var</span> methods <span class="op">=</span> [
  2185. [<span class="st">&#39;is not empty&#39;</span><span class="op">,</span> isNotEmpty]<span class="op">,</span>
  2186. [<span class="st">&#39;is not longer than&#39;</span><span class="op">,</span> isNotLongerThan]<span class="op">,</span>
  2187. [<span class="st">&#39;length is between&#39;</span><span class="op">,</span> isBetween]]<span class="op">;</span></code></pre></div>
  2188. <p>原文只给了一部分代码</p>
  2189. <div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="kw">var</span> methodPair <span class="op">=</span> <span class="at">find</span>(methods<span class="op">,</span> <span class="kw">function</span>(method) <span class="op">{</span>
  2190. <span class="cf">return</span> <span class="at">car</span>(method) <span class="op">===</span> <span class="at">car</span>(innerRule)<span class="op">;</span>
  2191. <span class="op">}</span>)<span class="op">;</span>
  2192. <span class="kw">var</span> methodToUse <span class="op">=</span> <span class="at">peek</span>(methodPair)<span class="op">;</span>
  2193. <span class="cf">return</span> <span class="kw">function</span>(obj) <span class="op">{</span>
  2194. <span class="kw">var</span> error <span class="op">=</span> <span class="at">peek</span>(innerRule)<span class="op">;</span> <span class="co">//error is the last index</span>
  2195. <span class="kw">var</span> values <span class="op">=</span> <span class="at">sink</span>(<span class="at">cdr</span>(innerRule))<span class="op">;</span> <span class="co">//get everything but the error</span>
  2196. <span class="cf">return</span> <span class="at">methodToUse</span>(obj<span class="op">,</span> propertyName<span class="op">,</span> error<span class="op">,</span> values)<span class="op">;</span> <span class="co">//construct the validation call</span>
  2197. <span class="op">};</span></code></pre></div>
  2198. </body>
  2199. </html>