-
Notifications
You must be signed in to change notification settings - Fork 138
/
Copy pathindex.html
2291 lines (2199 loc) · 203 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="generator" content="pandoc">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>RePractise – </title>
<style type="text/css">code{white-space: pre;}</style>
<style type="text/css">
div.sourceCode { overflow-x: auto; }
table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
margin: 0; padding: 0; vertical-align: baseline; border: none; }
table.sourceCode { width: 100%; line-height: 100%; }
td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
td.sourceCode { padding-left: 5px; }
code > span.kw { color: #007020; font-weight: bold; } /* Keyword */
code > span.dt { color: #902000; } /* DataType */
code > span.dv { color: #40a070; } /* DecVal */
code > span.bn { color: #40a070; } /* BaseN */
code > span.fl { color: #40a070; } /* Float */
code > span.ch { color: #4070a0; } /* Char */
code > span.st { color: #4070a0; } /* String */
code > span.co { color: #60a0b0; font-style: italic; } /* Comment */
code > span.ot { color: #007020; } /* Other */
code > span.al { color: #ff0000; font-weight: bold; } /* Alert */
code > span.fu { color: #06287e; } /* Function */
code > span.er { color: #ff0000; font-weight: bold; } /* Error */
code > span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
code > span.cn { color: #880000; } /* Constant */
code > span.sc { color: #4070a0; } /* SpecialChar */
code > span.vs { color: #4070a0; } /* VerbatimString */
code > span.ss { color: #bb6688; } /* SpecialString */
code > span.im { } /* Import */
code > span.va { color: #19177c; } /* Variable */
code > span.cf { color: #007020; font-weight: bold; } /* ControlFlow */
code > span.op { color: #666666; } /* Operator */
code > span.bu { } /* BuiltIn */
code > span.ex { } /* Extension */
code > span.pp { color: #bc7a00; } /* Preprocessor */
code > span.at { color: #7d9029; } /* Attribute */
code > span.do { color: #ba2121; font-style: italic; } /* Documentation */
code > span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
code > span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
code > span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
</style>
<link rel="stylesheet" href="style.css">
<!--[if lt IE 9]>
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv-printshiv.min.js"></script>
<![endif]-->
<meta name="viewport" content="width=device-width">
</head>
<body>
<p>
<h1>RePractise</h1>
<h3>By Phodal Huang(<a href="http://www.phodal.com">Geek's Life</a>)</h3>
</p>
<div>
<iframe src="http://ghbtns.com/github-btn.html?user=phodal&repo=repractise&type=watch&count=true"
allowtransparency="true" frameborder="0" scrolling="0" width="110px" height="20px"></iframe>
<div>
<nav id="TOC">
<ul>
<li><a href="#repractise">RePractise</a><ul>
<li><a href="#关于作者">关于作者</a></li>
</ul></li>
<li><a href="#引言">引言</a><ul>
<li><a href="#re-practise">Re-Practise</a></li>
<li><a href="#技术与业务">技术与业务</a></li>
<li><a href="#资讯爆炸">资讯爆炸</a></li>
<li><a href="#lost">Lost</a></li>
</ul></li>
<li><a href="#前端篇-前端演进史">前端篇: 前端演进史</a><ul>
<li><a href="#什么是前端">什么是前端?</a></li>
<li><a href="#前端演进史">前端演进史</a><ul>
<li><a href="#数据-模板-样式混合">数据-模板-样式混合</a></li>
<li><a href="#model-view-controller">Model-View-Controller</a></li>
<li><a href="#从桌面版到移动版">从桌面版到移动版</a></li>
<li><a href="#app与过渡期api">APP与过渡期API</a></li>
<li><a href="#过渡期spa">过渡期SPA</a></li>
<li><a href="#hybird与viewmodel">Hybird与ViewModel</a></li>
<li><a href="#一次构建跨平台运行">一次构建,跨平台运行</a></li>
</ul></li>
<li><a href="#repractise-1">RePractise</a></li>
</ul></li>
<li><a href="#后台与服务篇">后台与服务篇</a><ul>
<li><a href="#restful与服务化">RESTful与服务化</a><ul>
<li><a href="#设计restful-api">设计RESTful API</a></li>
<li><a href="#资源">资源</a></li>
</ul></li>
<li><a href="#微服务">微服务</a><ul>
<li><a href="#微内核">微内核</a></li>
</ul></li>
<li><a href="#混合微服务">混合微服务</a></li>
<li><a href="#其他">其他</a></li>
</ul></li>
<li><a href="#前后端篇">前后端篇</a><ul>
<li><a href="#前后端分离">前后端分离</a></li>
<li><a href="#单页面应用后台渲染">单页面应用后台渲染</a><ul>
<li><a href="#前后台渲染同一模板">前后台渲染同一模板</a></li>
<li><a href="#prerender方式">PreRender方式</a></li>
<li><a href="#react">React</a></li>
</ul></li>
</ul></li>
<li><a href="#从真实世界到前后端">从真实世界到前后端</a><ul>
<li><a href="#从真实世界到前后端-1">从真实世界到前后端</a><ul>
<li><a href="#便利店与售货员">便利店与售货员</a></li>
<li><a href="#模型领域抽象">模型、领域、抽象</a></li>
</ul></li>
<li><a href="#前后台分离后台">前后台分离:后台</a></li>
<li><a href="#前后台分离前端">前后台分离:前端</a></li>
<li><a href="#repractise-2">RePractise</a></li>
</ul></li>
<li><a href="#重构篇">重构篇</a><ul>
<li><a href="#网站重构">网站重构</a><ul>
<li><a href="#网站重构目的">网站重构目的</a></li>
</ul></li>
<li><a href="#代码重构">代码重构</a></li>
<li><a href="#使用工具重构">使用工具重构</a></li>
<li><a href="#借助工具重构">借助工具重构</a><ul>
<li><a href="#code-climate">Code Climate</a></li>
</ul></li>
<li><a href="#测试驱动开发">测试驱动开发</a><ul>
<li><a href="#一次测试驱动开发的故事">一次测试驱动开发的故事</a></li>
<li><a href="#说说测试驱动开发">说说测试驱动开发</a></li>
<li><a href="#思考">思考</a></li>
</ul></li>
</ul></li>
<li><a href="#架构篇-cms的重构与演进">架构篇: CMS的重构与演进</a><ul>
<li><a href="#动态cms">动态CMS</a><ul>
<li><a href="#cms简介">CMS简介</a></li>
<li><a href="#cms架构与django">CMS架构与Django</a></li>
<li><a href="#编辑-发布分离">编辑-发布分离</a></li>
<li><a href="#基于github的编辑-发布-开发分离">基于Github的编辑-发布-开发分离</a></li>
<li><a href="#repractise-3">Repractise</a></li>
</ul></li>
<li><a href="#构建基于git为数据中心的cms">构建基于Git为数据中心的CMS</a><ul>
<li><a href="#用户场景">用户场景</a></li>
</ul></li>
<li><a href="#code-生成静态页面">Code: 生成静态页面</a></li>
<li><a href="#builder-构建生成工具">Builder: 构建生成工具</a></li>
<li><a href="#contentjson格式">Content:JSON格式</a><ul>
<li><a href="#从schema到数据库">从Schema到数据库</a></li>
<li><a href="#git作为nosql数据库">git作为NoSQL数据库</a></li>
</ul></li>
<li><a href="#一键发布编辑器">一键发布:编辑器</a></li>
<li><a href="#移动应用">移动应用</a><ul>
<li><a href="#小结">小结</a></li>
<li><a href="#其他-2">其他</a></li>
</ul></li>
</ul></li>
<li><a href="#模式篇设计与架构">模式篇:设计与架构</a><ul>
<li><a href="#观察者模式">观察者模式</a><ul>
<li><a href="#ruby观察者模式">Ruby观察者模式</a></li>
<li><a href="#pubsub">PUB/SUB</a></li>
</ul></li>
<li><a href="#模板方法">模板方法</a><ul>
<li><a href="#从基本的app说起">从基本的App说起</a></li>
<li><a href="#template-method">Template Method</a></li>
<li><a href="#template-method实战">Template Method实战</a></li>
</ul></li>
<li><a href="#pipe-and-filters">Pipe and Filters</a><ul>
<li><a href="#unix-shell">Unix Shell</a></li>
<li><a href="#pipe-and-filter模式">Pipe and Filter模式</a></li>
<li><a href="#fluent-api">Fluent API</a></li>
<li><a href="#dsl-表达式生成器">DSL 表达式生成器</a></li>
<li><a href="#pipe-and-filter模式实战">Pipe and Filter模式实战</a></li>
</ul></li>
</ul></li>
<li><a href="#数据与模型篇">数据与模型篇</a><ul>
<li><a href="#数据">数据</a><ul>
<li><a href="#数据库">数据库</a></li>
<li><a href="#建模">建模</a></li>
</ul></li>
</ul></li>
<li><a href="#领域篇">领域篇</a><ul>
<li><a href="#ddd">DDD</a></li>
<li><a href="#dsl">DSL</a><ul>
<li><a href="#dsl示例">DSL示例</a></li>
</ul></li>
</ul></li>
</ul>
</nav>
<h1 id="repractise">RePractise</h1>
<h2 id="关于作者">关于作者</h2>
<p>黄峰达(Phodal Huang)是一个创客、工程师、咨询师和作家。他毕业于西安文理学院电子信息工程专业,现作为一个咨询师就职于 ThoughtWorks 深圳。长期活跃于开源软件社区 GitHub,目前专注于物联网和前端领域。</p>
<p>作为一个开源软件作者,著有 Growth、Stepping、Lan、Echoesworks 等软件。其中开源学习应用 Growth,广受读者和用户好评,可在 APP Store 及各大 Android 应用商店下载。</p>
<p>作为一个技术作者,著有《自己动手设计物联网》(电子工业出版社)、《全栈应用开发:精益实践》(电子工业出版社,正在出版)。并在 GitHub 上开源有《Growth: 全栈增长工程师指南》、《GitHub 漫游指南》等七本电子书。</p>
<p>作为技术专家,他为英国 Packt 出版社审阅有物联网书籍《Learning IoT》、《Smart IoT》,前端书籍《Angular 2 Serices》、《Getting started with Angular》等技术书籍。</p>
<p>他热爱编程、写作、设计、旅行、hacking,你可以从他的个人网站:<a href="https://www.phodal.com/" class="uri">https://www.phodal.com/</a> 了解到更多的内容。</p>
<p>其它相关信息:</p>
<ul>
<li>微博:<a href="http://weibo.com/phodal" class="uri">http://weibo.com/phodal</a></li>
<li>GitHub: <a href="https://github.com/phodal" class="uri">https://github.com/phodal</a></li>
<li>知乎:<a href="https://www.zhihu.com/people/phodal" class="uri">https://www.zhihu.com/people/phodal</a></li>
<li>SegmentFault:<a href="https://segmentfault.com/u/phodal" class="uri">https://segmentfault.com/u/phodal</a></li>
</ul>
<p>当前为预览版,在使用的过程中遇到任何问题请及时与我联系。阅读过程中的问题,不妨在GitHub上提出来: <a href="https://github.com/phodal/fe/issues">Issues</a></p>
<p>阅读过程中遇到语法错误、拼写错误、技术错误等等,不妨来个Pull Request,这样可以帮助到其他阅读这本电子书的童鞋。</p>
<p>我的电子书:</p>
<ul>
<li>《<a href="https://github.com/phodal/github-roam">GitHub 漫游指南</a>》</li>
<li>《<a href="https://github.com/phodal/fe">我的职业是前端工程师</a>》</li>
<li>《<a href="https://github.com/phodal/serverless">Serverless 架构应用开发指南</a>》</li>
<li>《<a href="https://github.com/phodal/growth-ebook">Growth: 全栈增长工程师指南</a>》</li>
<li>《<a href="https://github.com/phodal/ideabook">Phodal’s Idea实战指南</a>》</li>
<li>《<a href="https://github.com/phodal/designiot">一步步搭建物联网系统</a>》</li>
<li>《<a href="https://github.com/phodal/repractise">RePractise</a>》</li>
<li>《<a href="https://github.com/phodal/growth-in-action">Growth: 全栈增长工程师实战</a>》</li>
</ul>
<p>我的微信公众号:</p>
<figure>
<img src="./img/wechat.jpg" alt="作者微信公众号:phodal-weixin" /><figcaption>作者微信公众号:phodal-weixin</figcaption>
</figure>
<p>支持作者,可以加入作者的小密圈:</p>
<figure>
<img src="./img/xiaomiquan.jpg" alt="小密圈" /><figcaption>小密圈</figcaption>
</figure>
<p>或者转账:</p>
<p><img src="./img/alipay.png" alt="支付宝" /> <img src="./img/wechat-pay.png" alt="微信" /></p>
<h1 id="引言">引言</h1>
<p>回到一年前的今天(2014.09.29),一边在准备着去沙漠之旅,一边在准备国庆后的印度培训。</p>
<p>当时我还在用我的Lumia 920,上面没有各式各样的软件,除了我最需要的地图、相机。所以,我需要为我的手机写一个应用,用于在地图上显示图片信息及照片。</p>
<p>今天Github已经可以支持geojson了,于是你可以看到我在之前生成的geojson在地图上的效果<a href="https://github.com/phodal-archive/onmap/blob/master/gps.geojson">gps.geojson</a>。</p>
<h2 id="re-practise">Re-Practise</h2>
<p>在过去的近一年时期里,花费了很多时间在提高代码质量与构建架构知识。试着学习某一方面的架构知识,应用到某个熟悉领域。</p>
<ol type="1">
<li><p>所谓的一万小时天才理论一直在说明练习的重要性,你需要不断地去练习。但是并不是说你练习了一万小时之后就可以让你成为一个专家,而练习是必须的。</p></li>
<li><p>让我想起了在大学时代学的PID算法,虽然我没有掌握好控制领域的相关理论及算法,但是我对各种调节还算有点印象。简单地来说,我们需要不断调整自己的方向。</p></li>
</ol>
<p>现在还存在的那些互联网公司或者说开源项目,我们会发现两个不算有趣的规律:</p>
<ol type="1">
<li>一个一直在运行的软件。</li>
<li>尝试了几个产品,最后找到了一个合适的方向。</li>
</ol>
<p>我发现我属于不断尝试地类型。一直想构建一个开源软件,但是似乎一直没有找对合理的用户?但是,我们会发现上述地两者都在不断地retry,不断地retry归根于那些人在不断的repractise。与之成为反例的便是:</p>
<ol type="1">
<li>一个成功发布几次的软件,但是最后失败了</li>
<li>尝试了不同的几个产品,但是失败了</li>
</ol>
<p>所谓的失败,就是你离开人世了。所以,在我们还活着的时候,我们总会有机会去尝试。在那之前,我们都是在不断地re-practise。</p>
<p>这让我想到了Linux,这算是一个不错地软件,从一开始就存活到了现在。但是有多少开源软件就没有这么幸运,时间在淘汰越来越多的过去想法。人们创造事物的能力也越来越强,但是那只是因为创造变得越来越简单。</p>
<p>在我们看到的那些走上人生巅峰的CEO,还都在不断地re-practise。</p>
<h2 id="技术与业务">技术与业务</h2>
<p>于是,我又再次回到了这样一个现实的问题。技术可以不断地练习,不断地调整方向。但是技术地成本在不断地降低,代码的长度在不断地降低。整个技术的门槛越来越低,新出现的技术总会让新生代的程序员获利。但是不可避免地,业务地复杂度并没有因此而降低。这就是一个复杂的话题,难道业务真的很复杂吗?</p>
<p>人们总会提及写好CSS很难,但是写好Java就是一件容易的事。因为每天我们都在用Java、JavaScript去写代码,但是我们并没有花费时间去学。</p>
<p>因为我们一直将我们的时候花费的所谓的业务上,我们可以不断地将一些重复的代码抽象成一个库。但是我们并没有花费过多的时间去整理我们的业务,作为程序员,我们切换工作很容易只是因为相同的技术栈。作为一些营销人员,他们从一个领域到一个新的领域,不需要过多的学习,因为本身是相通的。</p>
<p>技术本身是如此,业务本身也是如此。</p>
<p>从技术到技术-领域是一条难走通的路?</p>
<h2 id="资讯爆炸">资讯爆炸</h2>
<p>回顾到近几年出现的各种资讯程序——开发者头条、极客头条、掘金、博乐头条等等,他们帮助我们的是丰富我们的信息,而不是简化我们的信息。</p>
<p>作为一个开发人员,过去我们并不需要关注那么多的内容。如果我们没有关注那么多的点,那么我们就可以集中于我们的想法里。实现上,我们需要的是一个更智能的时代。</p>
<p>业务本身是一种重复,技术本身也是重复的。只是在某个特定的时刻,一个好的技术可以帮助我们更好地Re-Practise。如推荐算法本身依赖于人为对信息进行分类,但是我们需要去区分大量地信息。而人本身的经历是足够有险的,这时候就需要机器来帮我们做很多事。</p>
<p>今天我在用MX5,但是发现不及Lumia 1020来得安静。功能越强大的同时,意味着我在上面花费的时间会更多。事情有好的一面总会有不好的一面,不好的一面也就意味着有机会寻找好的一面。</p>
<p>我们需要摒弃一些东西,以重新纠正我们的方向。于是,我需要再次回到Lumia 1020上。</p>
<h2 id="lost">Lost</h2>
<blockquote>
<p>一开始就输在起跑线上</p>
</blockquote>
<p>这是一个很有意思的话题,尽管试图将本章中从书中删除,但是我还是忍了下来。如果你学得比别人晚,在很长的一段时间里(可能直到进棺材)输给别人是必然的——落后就要挨打。就好像我等毕业于一所二本垫底的学校里,如果在过去我一直保持着和别人(各种重点)一样的学习速度,那么我只能一直是Loser。</p>
<p>需要注意的是,对你来说考上二本很难,并不是因为你比别人笨。教育资源分配不均的问题,在某种程度上导致了新的阶级制度的出现。如我的首页说的那样: THE ONLY FAIR IS NOT FAIR——唯一公平的是它是不公平的。我们可以做的还有很多——CREATE & SHARE。真正的不幸是,因为营养不良导致的教育问题。如果你还有机会正常地思想,那说明这个世界对你还是公平的。</p>
<h1 id="前端篇-前端演进史">前端篇: 前端演进史</h1>
<p>细细整理了过去接触过的那些前端技术,发现前端演进是段特别有意思的历史。人们总是在过去就做出未来需要的框架,而现在流行的是过去的过去发明过的。如,响应式设计不得不提到的一个缺点是:<strong>他只是将原本在模板层做的事,放到了样式(CSS)层来完成</strong>。</p>
<p>复杂度同力一样不会消失,也不会凭空产生,它总是从一个物体转移到另一个物体或一种形式转为另一种形式。</p>
<p>如果六、七年前的移动网络速度和今天一样快,那么直接上的技术就是响应式设计,APP、SPA就不会流行得这么快。尽管我们可以预见未来这些领域会变得更好,但是更需要的是改变现状。改变现状的同时也需要预见未来的需求。</p>
<h3 id="什么是前端">什么是前端?</h3>
<p>维基百科是这样说的:前端Front-end和后端back-end是描述进程开始和结束的通用词汇。前端作用于采集输入信息,后端进行处理。计算机程序的界面样式,视觉呈现属于前端。</p>
<p>这种说法给人一种很模糊的感觉,但是他说得又很对,它负责视觉展示。在MVC结构或者MVP中,负责视觉显示的部分只有View层,而今天大多数所谓的View层已经超越了View层。前端是一个很神奇的概念,但是而今的前端已经发生了很大的变化。</p>
<p>你引入了Backbone、Angluar,你的架构变成了MVP、MVVM。尽管发生了一些架构上的变化,但是项目的开发并没有因此而发生变化。这其中涉及到了一些职责的问题,如果某一个层级中有太多的职责,那么它是不是加重了一些人的负担?</p>
<h2 id="前端演进史">前端演进史</h2>
<p>过去一直想整理一篇文章来说说前端发展的历史,但是想着这些历史已经被人们所熟知。后来发现并非如此,大抵是幸存者偏见——关注到的都知道这些历史。</p>
<h3 id="数据-模板-样式混合">数据-模板-样式混合</h3>
<p>在有限的前端经验里,我还是经历了那段用Table来作样式的年代。大学期间曾经有偿帮一些公司或者个人开发、维护一些CMS,而Table是当时帮某个网站更新样式接触到的——ASP.Net(maybe)。当时,我们启动这个CMS用的是一个名为<code>aspweb.exe</code>的程序。于是,在我的移动硬盘里找到了下面的代码。</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><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">></span>
<span class="kw"><TBODY></span>
<span class="kw"><TR></span>
<span class="kw"><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">><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">></span>
<span class="kw"><TBODY></span>
<span class="kw"><TR></span>
<span class="kw"><TD><IMG</span><span class="ot"> src=</span><span class="st">"Images/xxx.gif"</span><span class="ot"> width=</span><span class="st">184</span><span class="kw">></TD></TR></span>
<span class="kw"><TR></span>
<span class="kw"><TD></span>
<span class="kw"><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"> background=</span><span class="st">Images/xxx.gif</span><span class="ot"> border=</span><span class="st">0</span><span class="kw">></span></code></pre></div>
<p>虽然,我也已经在HEAD里找到了现代的雏形——DIV + CSS,然而这仍然是一个Table的年代。</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><LINK</span><span class="ot"> href=</span><span class="st">"img/xxx.css"</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">></span></code></pre></div>
<p><strong>人们一直在说前端很难,问题是你学过么???</strong></p>
<p><strong>人们一直在说前端很难,问题是你学过么???</strong></p>
<p><strong>人们一直在说前端很难,问题是你学过么???</strong></p>
<p>也许,你也一直在说CSS不好写,但是CSS真的不好写么?人们总在说JS很难用,但是你学过么?只在需要的时候才去学,那肯定很难。<strong>你不曾花时间去学习一门语言,但是却能直接写出可以work的代码,说明他们容易上手</strong>。如果你看过一些有经验的Ruby、Scala、Emacs Lisp开发者写出来的代码,我想会得到相同的结论。有一些语言可以让写程序的人Happy,但是看的人可能就不Happy了。做事的方法不止一种,但是不是所有的人都要用那种方法去做。</p>
<p>过去的那些程序员都是<strong>真正的全栈程序员</strong>,这些程序员不仅仅做了前端的活,还做了数据库的工作。</p>
<div class="sourceCode"><pre class="sourceCode sql"><code class="sourceCode sql"><span class="kw">Set</span> rs = Server.CreateObject(<span class="ot">"ADODB.Recordset"</span>)
sql = <span class="ot">"select id,title,username,email,qq,adddate,content,Re_content,home,face,sex from Fl_Book where ispassed=1 order by id desc"</span>
rs.open sql, Conn, <span class="dv">1</span>, <span class="dv">1</span>
fl.SqlQueryNum = fl.SqlQueryNum + <span class="dv">1</span></code></pre></div>
<p>在这个ASP文件里,它从数据库里查找出了数据,然后Render出HTML。如果可以看到历史版本,那么我想我会看到有一个作者将style=“”的代码一个个放到css文件中。</p>
<p>在这里的代码里也免不了有动态生成JavaScript代码的方法:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript">show_other <span class="op">=</span> <span class="st">"<SCRIPT language=javascript>"</span>
show_other <span class="op">=</span> show_other <span class="op">&</span> <span class="st">"function checkform()"</span>
show_other <span class="op">=</span> show_other <span class="op">&</span> <span class="st">"{"</span>
show_other <span class="op">=</span> show_other <span class="op">&</span> <span class="st">"if (document.add.title.value=='')"</span>
show_other <span class="op">=</span> show_other <span class="op">&</span> <span class="st">"{"</span></code></pre></div>
<p>请尽情嘲笑,然后再看一段代码:</p>
<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">"react"</span><span class="op">;</span>
<span class="im">import</span> <span class="op">{</span> getData <span class="op">}</span> <span class="im">from</span> <span class="st">"../../common/request"</span><span class="op">;</span>
<span class="im">import</span> styles <span class="im">from</span> <span class="st">"./style.css"</span><span class="op">;</span>
<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>
<span class="at">componentWillMount</span>() <span class="op">{</span>
<span class="va">console</span>.<span class="at">log</span>(<span class="st">"[HomePage] will mount with server response: "</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>
<span class="op">}</span>
<span class="at">render</span>() <span class="op">{</span>
<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>
<span class="cf">return</span> (
<span class="op"><</span>div className<span class="op">={</span><span class="va">styles</span>.<span class="at">content</span><span class="op">}></span>
<span class="op"><</span>h1<span class="op">>{</span>title<span class="op">}<</span><span class="ss">/h1></span>
<span class="ss"> <p className={styles.welcomeText}>Thanks for joining!</p</span><span class="op">></span>
<span class="op"><</span><span class="ss">/div></span>
<span class="ss"> </span><span class="sc">)</span><span class="ss">;</span>
<span class="ss"> }</span>
<span class="ss"> static fetchData = function</span><span class="sc">(</span><span class="ss">params</span><span class="sc">)</span><span class="ss"> {</span>
<span class="ss"> return getData</span><span class="sc">(</span><span class="ss">"/home</span><span class="st">");</span>
<span class="op">}</span>
<span class="op">}</span></code></pre></div>
<p>10年前和10年后的代码,似乎没有太多的变化。有所不同的是数据层已经被独立出去了,如果你的component也混合了数据层,即直接查询数据库而不是调用数据层接口,那么你就需要好好思考下这个问题。你只是在追随潮流,还是在改变。用一个View层更换一个View层,用一个Router换一个Router的意义在哪?</p>
<h3 id="model-view-controller">Model-View-Controller</h3>
<p>人们在不断地反思这其中复杂的过程,整理了一些好的架构模式,其中不得不提到的是我司Martin Folwer的《企业应用架构模式》。该书中文译版出版的时候是2004年,那时对于系统的分层是</p>
<table>
<thead>
<tr class="header">
<th>层次</th>
<th>职责</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>表现层</td>
<td>提供服务、显示信息、用户请求、HTTP请求和命令行调用。</td>
</tr>
<tr class="even">
<td>领域层</td>
<td>逻辑处理,系统中真正的核心。</td>
</tr>
<tr class="odd">
<td>数据层</td>
<td>与数据库、消息系统、事物管理器和其他软件包通讯。</td>
</tr>
</tbody>
</table>
<p>化身于当时最流行的Spring,就是MVC。人们有了iBatis这样的数据持久层框架,即ORM,对象关系映射。于是,你的package就会有这样的几个文件夹:</p>
<pre><code>|____mappers
|____model
|____service
|____utils
|____controller</code></pre>
<p>在mappers这一层,我们所做的莫过于如下所示的数据库相关查询:</p>
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="at">@Insert</span>(
<span class="st">"INSERT INTO users(username, password, enabled) "</span> +
<span class="st">"VALUES (#{userName}, #{passwordHash}, #{enabled})"</span>
)
<span class="at">@Options</span>(keyProperty = <span class="st">"id"</span>, keyColumn = <span class="st">"id"</span>, useGeneratedKeys = <span class="kw">true</span>)
<span class="dt">void</span> <span class="fu">insert</span>(User user);</code></pre></div>
<p>model文件夹和mappers文件夹都是数据层的一部分,只是两者间的职责不同,如:</p>
<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>() {
<span class="kw">return</span> userName;
}
<span class="kw">public</span> <span class="dt">void</span> <span class="fu">setUserName</span>(<span class="bu">String</span> userName) {
<span class="kw">this</span>.<span class="fu">userName</span> = userName;
}</code></pre></div>
<p>而他们最后都需要在Controller,又或者称为ModelAndView中处理:</p>
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="at">@RequestMapping</span>(value = {<span class="st">"/disableUser"</span>}, method = RequestMethod.<span class="fu">POST</span>)
<span class="kw">public</span> ModelAndView <span class="fu">processUserDisable</span>(HttpServletRequest request, ModelMap model) {
<span class="bu">String</span> userName = request.<span class="fu">getParameter</span>(<span class="st">"userName"</span>);
User user = userService.<span class="fu">getByUsername</span>(userName);
userService.<span class="fu">disable</span>(user);
<span class="bu">Map</span><<span class="bu">String</span>,User> map = <span class="kw">new</span> <span class="bu">HashMap</span><<span class="bu">String</span>,User>();
<span class="bu">Map</span> <User,<span class="bu">String</span>> usersWithRoles= userService.<span class="fu">getAllUsersWithRole</span>();
model.<span class="fu">put</span>(<span class="st">"usersWithRoles"</span>,usersWithRoles);
<span class="kw">return</span> <span class="kw">new</span> <span class="fu">ModelAndView</span>(<span class="st">"redirect:users"</span>,map);
}</code></pre></div>
<p>在多数时候,Controller不应该直接与数据层的一部分,而将业务逻辑放在Controller层又是一种错误,这时就有了Service层,如下图:</p>
<figure>
<img src="./img/frontend/service-mvc.png" alt="Service MVC" /><figcaption>Service MVC</figcaption>
</figure>
<p>然而对于Domain相关的Service应该放在哪一层,总会有不同的意见:</p>
<figure>
<img src="./img/frontend/mvcplayer.gif" alt="MVC Player" /><figcaption>MVC Player</figcaption>
</figure>
<figure>
<img src="./img/frontend/ms-mvc.png" alt="MS MVC" /><figcaption>MS MVC</figcaption>
</figure>
<p>Domain(业务)是一个相当复杂的层级,这里是业务的核心。一个合理的Controller只应该做自己应该做的事,它不应该处理业务相关的代码:</p>
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">if</span> (isNewnameEmpty == <span class="kw">false</span> && newuser == <span class="kw">null</span>){
user.<span class="fu">setUserName</span>(newUsername);
<span class="bu">List</span><Post> myPosts = postService.<span class="fu">findMainPostByAuthorNameSortedByCreateTime</span>(principal.<span class="fu">getName</span>());
<span class="kw">for</span> (<span class="dt">int</span> k = <span class="dv">0</span>;k < myPosts.<span class="fu">size</span>();k++){
Post post = myPosts.<span class="fu">get</span>(k);
post.<span class="fu">setAuthorName</span>(newUsername);
postService.<span class="fu">save</span>(post);
}
userService.<span class="fu">update</span>(user);
Authentication oldAuthentication = SecurityContextHolder.<span class="fu">getContext</span>().<span class="fu">getAuthentication</span>();
Authentication authentication = <span class="kw">null</span>;
<span class="kw">if</span>(oldAuthentication == <span class="kw">null</span>){
authentication = <span class="kw">new</span> <span class="fu">UsernamePasswordAuthenticationToken</span>(newUsername,user.<span class="fu">getPasswordHash</span>());
}<span class="kw">else</span>{
authentication = <span class="kw">new</span> <span class="fu">UsernamePasswordAuthenticationToken</span>(newUsername,user.<span class="fu">getPasswordHash</span>(),oldAuthentication.<span class="fu">getAuthorities</span>());
}
SecurityContextHolder.<span class="fu">getContext</span>().<span class="fu">setAuthentication</span>(authentication);
map.<span class="fu">clear</span>();
map.<span class="fu">put</span>(<span class="st">"user"</span>,user);
model.<span class="fu">addAttribute</span>(<span class="st">"myPosts"</span>, myPosts);
model.<span class="fu">addAttribute</span>(<span class="st">"namesuccess"</span>, <span class="st">"User Profile updated successfully"</span>);
<span class="kw">return</span> <span class="kw">new</span> <span class="fu">ModelAndView</span>(<span class="st">"user/profile"</span>, map);
}</code></pre></div>
<p>我们在Controller层应该做的事是:</p>
<ol type="1">
<li>处理请求的参数</li>
<li>渲染和重定向</li>
<li>选择Model和Service</li>
<li>处理Session和Cookies</li>
</ol>
<p>业务是善变的,昨天我们可能还在和对手竞争谁先推出新功能,但是今天可能已经合并了。我们很难预见业务变化,但是我们应该能预见Controller是不容易变化的。在一些设计里面,这种模式就是Command模式。</p>
<p>View层是一直在变化的层级,人们的品味一直在更新,有时甚至可能因为竞争对手而产生变化。在已经取得一定市场的情况下,Model-Service-Controller通常都不太会变动,甚至不敢变动。企业意识到创新的两面性,要么带来死亡,要么占领更大的市场。但是对手通常都比你想象中的更聪明一些,所以这时<strong>开创新的业务是一个更好的选择</strong>。</p>
<p>高速发展期的企业和发展初期的企业相比,更需要前端开发人员。在用户基数不够、业务待定的情形中,View只要可用并美观就行了,这时可能就会有大量的业务代码放在View层:</p>
<div class="sourceCode"><pre class="sourceCode jsp"><code class="sourceCode jsp"><span class="kw"><c:choose></span>
<span class="kw"><c:when</span><span class="ot"> test</span>=<span class="dt">"</span>${ hasError }<span class="dt">"</span><span class="kw">></span>
<p<span class="ot"> class</span>=<span class="dt">"prompt-error"</span>>
${errors.username} ${errors.password}
</p>
<span class="kw"></c:when></span>
<span class="kw"><c:otherwise></span>
<p<span class="ot"> class</span>=<span class="dt">"prompt"</span>>
Woohoo, User <span<span class="ot"> class</span>=<span class="dt">"username"</span>>${user.userName}</span> has been created successfully!
</p>
<span class="kw"></c:otherwise></span>
<span class="kw"></c:choose></span> </code></pre></div>
<p>不同的情形下,人们都会对此有所争议,但只要符合当前的业务便是最好的选择。作为一个前端开发人员,在过去我需要修改JSP、PHP文件,这期间我需要去了解这些Template:</p>
<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>}
<li itemprop=<span class="st">"breadcrumb"</span>><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">'addtime'</span><span class="ot">],</span><span class="dv">24</span><span class="ot">))</span>} style=<span class="st">"color:red"</span>{/<span class="kw">if</span>}><span class="ot">[</span>{fun <span class="fu">date</span><span class="ot">(</span><span class="st">'Y-m-d'</span><span class="ot">,</span><span class="kw">$v</span><span class="ot">[</span><span class="st">'addtime'</span><span class="ot">])</span>}<span class="ot">]</span></span><a href=<span class="st">"</span><span class="kw">{$v['url']}</span><span class="st">"</span> style=<span class="st">"</span><span class="kw">{$v['style']}</span><span class="st">"</span> target=<span class="st">"_blank"</span>>{<span class="kw">$v</span><span class="ot">[</span><span class="st">'title'</span><span class="ot">]</span>}</a></li>
{/<span class="kw">foreach</span>}</code></pre></div>
<p>有时像Django这一类,自称为Model-Template-View的框架,更容易让人理解其意图:</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html">{% for blog_post in blog_posts.object_list %}
{% block blog_post_list_post_title %}
<span class="kw"><section</span><span class="ot"> class=</span><span class="st">"section--center mdl-grid mdl-grid--no-spacing mdl-shadow--2dp mdl-cell--11-col blog-list"</span><span class="kw">></span>
{% editable blog_post.title %}
<span class="kw"><div</span><span class="ot"> class=</span><span class="st">"mdl-card__title mdl-card--border mdl-card--expand"</span><span class="kw">></span>
<span class="kw"><h2</span><span class="ot"> class=</span><span class="st">"mdl-card__title-text"</span><span class="kw">></span>
<span class="kw"><a</span><span class="ot"> href=</span><span class="st">"{{ blog_post.get_absolute_url }}"</span><span class="ot"> itemprop=</span><span class="st">"headline"</span><span class="kw">></span>{{ blog_post.title }} › <span class="kw"></a></span>
<span class="kw"></h2></span>
<span class="kw"></div></span>
{% endeditable %}
{% endblock %}</code></pre></div>
<p>作为一个前端人员,我们真正在接触的是View层和Template层,但是MVC并没有说明这些。</p>
<h3 id="从桌面版到移动版">从桌面版到移动版</h3>
<p>Wap出现了,并带来了更多的挑战。随后,分辨率从1024x768变成了176×208,开发人员不得不面临这些挑战。当时所需要做的仅仅是修改View层,而View层随着iPhone的出现又发生了变化。</p>
<figure>
<img src="./img/frontend/wap.gif" alt="WAP 网站" /><figcaption>WAP 网站</figcaption>
</figure>
<p>这是一个短暂的历史,PO还需要为手机用户制作一个怎样的网站?于是他们把桌面版的网站搬了过去变成了移动版。由于网络的原因,每次都需要重新加载页面,这带来了不佳的用户体验。</p>
<p>幸运的是,人们很快意识到了这个问题,于是就有了SPA。<strong>如果当时的移动网络速度可以更快的话,我想很多SPA框架就不存在了</strong>。</p>
<p>先说说jQuery Mobile,在那之前,先让我们来看看两个不同版本的代码,下面是一个手机版本的blog详情页:</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html"><span class="kw"><ul</span><span class="ot"> data-role=</span><span class="st">"listview"</span><span class="ot"> data-inset=</span><span class="st">"true"</span><span class="ot"> data-splittheme=</span><span class="st">"a"</span><span class="kw">></span>
{% for blog_post in blog_posts.object_list %}
<span class="kw"><li></span>
{% editable blog_post.title blog_post.publish_date %}
<span class="kw"><h2</span><span class="ot"> class=</span><span class="st">"blog-post-title"</span><span class="kw">><a</span><span class="ot"> href=</span><span class="st">"{% url "</span><span class="er">blog_post_detail"</span><span class="ot"> blog_post.slug</span> <span class="er">%}"</span><span class="kw">></span>{{ blog_post.title }}<span class="kw"></a></h2></span>
<span class="kw"><em</span><span class="ot"> class=</span><span class="st">"since"</span><span class="kw">></span>{% blocktrans with sometime=blog_post.publish_date|timesince %}{{ sometime }} ago{% endblocktrans %}<span class="kw"></em></span>
{% endeditable %}
<span class="kw"></li></span>
{% endfor %}
<span class="kw"></ul></span></code></pre></div>
<p>而下面是桌面版本的片段:</p>
<div class="sourceCode"><pre class="sourceCode html"><code class="sourceCode html">{% for blog_post in blog_posts.object_list %}
{% block blog_post_list_post_title %}
{% editable blog_post.title %}
<span class="kw"><h2></span>
<span class="kw"><a</span><span class="ot"> href=</span><span class="st">"{{ blog_post.get_absolute_url }}"</span><span class="kw">></span>{{ blog_post.title }}<span class="kw"></a></span>
<span class="kw"></h2></span>
{% endeditable %}
{% endblock %}
{% block blog_post_list_post_metainfo %}
{% editable blog_post.publish_date %}
<span class="kw"><h6</span><span class="ot"> class=</span><span class="st">"post-meta"</span><span class="kw">></span>
{% trans "Posted by" %}:
{% with blog_post.user as author %}
<span class="kw"><a</span><span class="ot"> href=</span><span class="st">"{% url "</span><span class="er">blog_post_list_author"</span><span class="ot"> author</span> <span class="er">%}"</span><span class="kw">></span>{{ author.get_full_name|default:author.username }}<span class="kw"></a></span>
{% endwith %}
{% with blog_post.categories.all as categories %}
{% if categories %}
{% trans "in" %}
{% for category in categories %}
<span class="kw"><a</span><span class="ot"> href=</span><span class="st">"{% url "</span><span class="er">blog_post_list_category"</span><span class="ot"> category.slug</span> <span class="er">%}"</span><span class="kw">></span>{{ category }}<span class="kw"></a></span>{% if not forloop.last %}, {% endif %}
{% endfor %}
{% endif %}
{% endwith %}
{% blocktrans with sometime=blog_post.publish_date|timesince %}{{ sometime }} ago{% endblocktrans %}
<span class="kw"></h6></span>
{% endeditable %}
{% endblock %}</code></pre></div>
<p>人们所做的只是<strong>重载View层</strong>。这也是一个有效的SEO策略,上面这些代码是我博客过去的代码。对于桌面版和移动版都是不同的模板和不同的JS、CSS。</p>
<figure>
<img src="./img/frontend/mobile-web.png" alt="移动版网页" /><figcaption>移动版网页</figcaption>
</figure>
<p>在这一时期,桌面版和移动版的代码可能在同一个代码库中。他们使用相同的代码,调用相同的逻辑,只是View层不同了。但是,每次改动我们都要维护两份代码。</p>
<p>随后,人们发现了一种更友好的移动版应用——APP。</p>
<h3 id="app与过渡期api">APP与过渡期API</h3>
<p>这是一个艰难的时刻,过去我们的很多API都是在原来的代码库中构建的,即桌面版和移动版一起。我们已经在这个代码库中开发了越来越多的功能,系统开发变得臃肿。如《Linux/Unix设计思想》中所说,这是一个伟大的系统,但是它臃肿而又缓慢。</p>
<p>我们是选择重新开发一个结合第一和第二系统的最佳特性的第三个系统,还是继续臃肿下去。我想你已经有答案了。随后我们就有了APP API,构建出了博客的APP。</p>
<figure>
<img src="./img/frontend/mobile-app.jpg" alt="应用" /><figcaption>应用</figcaption>
</figure>
<p>最开始,人们越来越喜欢用APP,因为与移动版网页相比,其响应速度更快,而且更流畅。对于服务器来说,也是一件好事,因为请求变少了。</p>
<p>但是并非所有的人都会下载APP——<strong>有时只想看看上面有没有需要的东西</strong>。对于刚需不强的应用,人们并不会下载,只会访问网站。</p>
<p>有了APP API之后,我们可以向网页提供API,我们就开始设想要有一个好好的移动版。</p>
<h3 id="过渡期spa">过渡期SPA</h3>
<p>Backbone诞生于2010年,和响应式设计出现在同一个年代里,但他们似乎在同一个时代里火了起来。如果CSS3早点流行开来,似乎就没有Backbone啥事了。不过移动网络还是限制了响应式的流行,只是在今天这些都有所变化。</p>
<p>我们用Ajax向后台请求API,然后Mustache Render出来。因为JavaScript在模块化上的缺陷,所以我们就用Require.JS来进行模块化。</p>
<p>下面的代码就是我在尝试对我的博客进行SPA设计时的代码:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"><span class="at">define</span>([
<span class="st">'zepto'</span><span class="op">,</span>
<span class="st">'underscore'</span><span class="op">,</span>
<span class="st">'mustache'</span><span class="op">,</span>
<span class="st">'js/ProductsView'</span><span class="op">,</span>
<span class="st">'json!/configure.json'</span><span class="op">,</span>
<span class="st">'text!/templates/blog_details.html'</span><span class="op">,</span>
<span class="st">'js/renderBlog'</span>
]<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>
<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>
<span class="dt">el</span><span class="op">:</span> <span class="at">$</span>(<span class="st">"#content"</span>)<span class="op">,</span>
<span class="dt">initialize</span><span class="op">:</span> <span class="kw">function</span> () <span class="op">{</span>
<span class="kw">this</span>.<span class="at">params</span> <span class="op">=</span> <span class="st">'#content'</span><span class="op">;</span>
<span class="op">},</span>
<span class="dt">getBlog</span><span class="op">:</span> <span class="kw">function</span>(slug) <span class="op">{</span>
<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">'blogPostUrl'</span>] <span class="op">+</span> slug<span class="op">,</span> blogDetailsTemplate)<span class="op">;</span>
<span class="va">getblog</span>.<span class="at">renderBlog</span>()<span class="op">;</span>
<span class="op">}</span>
<span class="op">}</span>)<span class="op">;</span>
<span class="cf">return</span> BlogDetailsView<span class="op">;</span>
<span class="op">}</span>)<span class="op">;</span></code></pre></div>
<p>从API获取数据,结合Template来Render出Page。但是这无法改变我们需要Client Side Render和Server Side Render的两种Render方式,除非我们可以像淘宝一样不需要考虑SEO——因为它不那么依靠搜索引擎带来流量。</p>
<p>这时,我们还是基于类MVC模式。只是数据的获取方式变成了Ajax,我们就犯了一个错误——将大量的业务逻辑放在前端。这时候我们已经不能再从View层直接访问Model层,从安全的角度来说有点危险。</p>
<p>如果你的View层还可以直接访问Model层,那么说明你的架构还是MVC模式。之前我在Github上构建一个Side Project的时候直接用View层访问了Model层,由于Model层是一个ElasticSearch的搜索引擎,它提供了JSON API,这使得我要在View层处理数据——即业务逻辑。将上述的JSON API放入Controller,尽管会加重这一层的复杂度,但是业务逻辑就不再放置于View层。</p>
<p>如果你在你的View层和Model层总有一层接口,那么你采用的就是MVP模式——MVC模式的衍生(PS:为了区别别的事情,总会有人取个表意的名称)。</p>
<p>一夜之前,我们又回到了过去。我们离开了JSP,将View层变成了Template与Controller。而原有的Services层并不是只承担其原来的责任,这些Services开始向ViewModel改变。</p>
<p>一些团队便将Services抽成多个Services,美其名为微服务。传统架构下的API从下图</p>
<figure>
<img src="./img/frontend/api-gateway.png" alt="API Gateway" /><figcaption>API Gateway</figcaption>
</figure>
<p>变成了直接调用的微服务:</p>
<figure>
<img src="./img/frontend/microservices.png" alt="Micro Services" /><figcaption>Micro Services</figcaption>
</figure>
<p>对于后台开发者来说,这是一件大快人心的大好事,但是对于应用端/前端来说并非如此。调用的服务变多了,在应用程序端进行功能测试变得更复杂,需要Mock的API变多了。</p>
<h3 id="hybird与viewmodel">Hybird与ViewModel</h3>
<p>这时候遇到问题的不仅仅只在前端,而在App端,小的团队已经无法承受开发成本。人们更多的注意力放到了Hybird应用上。Hybird应用解决了一些小团队在开发初期遇到的问题,这部分应用便交给了前端开发者。</p>
<p>前端开发人员先熟悉了单纯的JS + CSS + HTML,又熟悉了Router + PageView + API的结构,现在他们又需要做手机APP。这时候只好用熟悉的jQuer Mobile + Cordova。</p>
<p>随后,人们先从Cordova + jQuery Mobile,变成了Cordova + Angular的 Ionic。在那之前,一些团队可能已经用Angular代换了Backbone。他们需要更好的交互,需要data binding。</p>
<p>接着,我们可以直接将我们的Angular代码从前端移到APP,比如下面这种博客APP的代码:</p>
<div class="sourceCode"><pre class="sourceCode javascript"><code class="sourceCode javascript"> .<span class="at">controller</span>(<span class="st">'BlogCtrl'</span><span class="op">,</span> <span class="kw">function</span> ($scope<span class="op">,</span> Blog) <span class="op">{</span>
<span class="va">$scope</span>.<span class="at">blogs</span> <span class="op">=</span> <span class="kw">null</span><span class="op">;</span>
<span class="va">$scope</span>.<span class="at">blogOffset</span> <span class="op">=</span> <span class="dv">0</span><span class="op">;</span>
<span class="co">//</span>
<span class="va">$scope</span>.<span class="at">doRefresh</span> <span class="op">=</span> <span class="kw">function</span> () <span class="op">{</span>
<span class="va">Blog</span>.<span class="at">async</span>(<span class="st">'https://www.phodal.com/api/v1/app/?format=json'</span>).<span class="at">then</span>(<span class="kw">function</span> (results) <span class="op">{</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>
<span class="op">}</span>)<span class="op">;</span>
<span class="va">$scope</span>.<span class="at">$broadcast</span>(<span class="st">'scroll.refreshComplete'</span>)<span class="op">;</span>
<span class="va">$scope</span>.<span class="at">$apply</span>()
<span class="op">};</span>
<span class="va">Blog</span>.<span class="at">async</span>(<span class="st">'https://www.phodal.com/api/v1/app/?format=json'</span>).<span class="at">then</span>(<span class="kw">function</span> (results) <span class="op">{</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>
<span class="op">}</span>)<span class="op">;</span>
<span class="va">$scope</span>.<span class="at">loadMore</span> <span class="op">=</span> <span class="kw">function</span>() <span class="op">{</span>
<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>
<span class="va">Blog</span>.<span class="at">async</span>(<span class="st">'https://www.phodal.com/api/v1/app/?limit=10&offset='</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">'&format=json'</span>).<span class="at">then</span>(<span class="kw">function</span> (results) <span class="op">{</span>
<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>
<span class="va">$scope</span>.<span class="at">$broadcast</span>(<span class="st">'scroll.infiniteScrollComplete'</span>)<span class="op">;</span>
<span class="op">}</span>)
<span class="op">};</span>
<span class="op">}</span>)</code></pre></div>
<p>结果<strong>时间轴又错了</strong>,人们总是<strong>超前一个时期做错了一个在未来是正确的决定</strong>。人们遇到了网页版的用户授权问题,于是发明了JWT——Json Web Token。</p>
<p>然而,由于WebView在一些早期的Android手机上出现了性能问题,人们开始考虑替换方案。接着出现了两个不同的解决方案:</p>
<ol type="1">
<li>React Native</li>
<li>新的WebView——Crosswalk</li>
</ol>
<p>开发人员开始欢呼React Native这样的框架。但是,他们并没有预见到<strong>人们正在厌恶APP</strong>,APP在我们的迭代里更新着,可能是一星期,可能是两星期,又或者是一个月。谁说APP内自更新不是一件坏事,但是APP的提醒无时无刻不在干扰着人们的生活,噪声越来越多。<strong>不要和用户争夺他们手机的使用权</strong></p>
<h3 id="一次构建跨平台运行">一次构建,跨平台运行</h3>
<p>在我们需要学习C语言的时候,GCC就有了这样的跨平台编译。</p>
<p>在我们开发桌面应用的时候,QT就有了这样的跨平台能力。</p>
<p>在我们构建Web应用的时候,Java就有了这样的跨平台能力。</p>
<p>在我们需要开发跨平台应用的时候,Cordova就有了这样的跨平台能力。</p>
<p>现在,React这样的跨平台框架又出现了,而响应式设计也是跨平台式的设计。</p>
<p>响应式设计不得不提到的一个缺点是:<strong>他只是将原本在模板层做的事,放到了样式(CSS)层</strong>。你还是在针对着不同的设备进行设计,两种没有什么多大的不同。复杂度不会消失,也不会凭空产生,它只会从一个物体转移到另一个物体或一种形式转为另一种形式。</p>
<p>React,将一小部分复杂度交由人来消化,将另外一部分交给了React自己来消化。在用Spring MVC之前,也许我们还在用CGI编程,而Spring降低了这部分复杂度,但是这和React一样降低的只是新手的复杂度。在我们不能以某种语言的方式写某相关的代码时,这会带来诸多麻烦。</p>
<h2 id="repractise-1">RePractise</h2>
<p>如果你是一只辛勤的蜜蜂,那么我想你应该都玩过上面那些技术。你是在练习前端的技术,还是在RePractise?如果你不花点时间整理一下过去,顺便预测一下未来,那么你就是在白搭。</p>
<p>前端的演进在这一年特别快,Ruby On Rails也在一个合适的年代里出现,在那个年代里也流行得特别快。RoR开发效率高的优势已然不再突显,语法灵活性的副作用就是运行效率降低,同时后期维护难——每个人元编程了自己。</p>
<p>如果不能把Controller、Model Mapper变成ViewModel,又或者是Micro Services来解耦,那么ES6 + React只是在现在带来更高的开发效率。而所谓的高效率,只是相比较而意淫出来的,因为他只是一层View层。将Model和Controller再加回View层,以后再拆分出来?</p>
<p>现有的结构只是将View层做了View层应该做的事。</p>
<p>首先,你应该考虑的是一种可以让View层解耦于Domain或者Service层。今天,桌面、平板、手机并不是唯一用户设备,虽然你可能在明年统一了这三个平台,现在新的设备的出现又将设备分成两种类型——桌面版和手机版。一开始桌面版和手机版是不同的版本,后来你又需要合并这两个设备。</p>
<p>其次,你可以考虑用混合Micro Services优势的Monolithic Service来分解业务。如果可以举一个成功的例子,那么就是Linux,一个混合内核的“Service”。</p>
<p>最后,Keep Learning。我们总需要在适当的时候做出改变,尽管我们觉得一个Web应用代码库中含桌面版和移动版代码会很不错,但是在那个时候需要做出改变。</p>
<p>对于复杂的应用来说,其架构肯定不是只有纯MVP或者纯MVVM这么简单的。如果一个应用混合了MVVM、MVP和MVC,那么他也变成了MVC——因为他直接访问了Model层。但是如果细分来看,只有访问了Model层的那一部分才是MVC模式。</p>
<p>模式,是人们对于某个解决方案的描述。在一段代码中可能有各种各样的设计模式,更何况是架构。</p>
<h1 id="后台与服务篇">后台与服务篇</h1>
<p>尽管在最初我也想去写一篇文章来说说后台的发展史,后来想了想还是让我们把它划分成不同的几部分。以便于我们可以更好的说说这些内容,不过相信这是一个好的开始。</p>
<h2 id="restful与服务化">RESTful与服务化</h2>
<h3 id="设计restful-api">设计RESTful API</h3>
<blockquote>
<p>REST从资源的角度来观察整个网络,分布在各处的资源由URI确定,而客户端的应用通过URI来获取资源的表征。获得这些表征致使这些应用程序转变了其状态。随着不断获取资源的表征,客户端应用不断地在转变着其状态,所谓表征状态转移。</p>
</blockquote>
<p>因为我们需要的是一个Machine到Machine沟通的平台,需要设计一个API。而设计一个API来说,RESTful是很不错的一种选择,也是主流的选择。而设计一个RESTful服务,的首要步骤便是设计资源模型。</p>
<h3 id="资源">资源</h3>
<p>互联网上的一切信息都可以看作是一种资源。</p>
<table>
<thead>
<tr class="header">
<th>HTTP Method</th>
<th>Operation Performed</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>GET</td>
<td>Get a resource (Read a resource)</td>
</tr>
<tr class="even">
<td>POST</td>
<td>Create a resource</td>
</tr>
<tr class="odd">
<td>PUT</td>
<td>Update a resource</td>
</tr>
<tr class="even">
<td>DELETE</td>
<td>Delete Resource</td>
</tr>
</tbody>
</table>
<p>设计RESTful API是一个有意思的话题。下面是一些常用的RESTful设计原则:</p>
<ul>
<li>组件间交互的可伸缩性</li>
<li>接口的通用性</li>
<li>组件的独立部署</li>
<li>通过中间组件来减少延迟、实施安全策略和封装已有系统</li>
</ul>
<p>判断是否是 RESTful的约束条件</p>
<ul>
<li>客户端-服务器分离</li>
<li>无状态</li>
<li>可缓存</li>
<li>多层系统</li>
<li>统一接口</li>
<li>随需代码(可选)</li>
</ul>
<h2 id="微服务">微服务</h2>
<h3 id="微内核">微内核</h3>
<p>这只是由微服务与传统架构之间对比而引发的一个思考,让我引一些资料来当参考吧.</p>
<blockquote>
<p>单内核:也称为宏内核。将内核从整体上作为一个大过程实现,并同时运行在一个单独的地址空间。所有的内核服务都在一个地址空间运行,相互之间直接调用函数,简单高效。微内核:功能被划分成独立的过程,过程间通过IPC进行通信。模块化程度高,一个服务失效不会影响另外一个服务。Linux是一个单内核结构,同时又吸收了微内核的优点:模块化设计,支持动态装载内核模块。Linux还避免了微内核设计上的缺陷,让一切都运行在内核态,直接调用函数,无需消息传递。</p>
</blockquote>
<p>对就的微内核便是:</p>
<blockquote>
<p>微内核――在微内核中,大部分内核都作为单独的进程在特权状态下运行,他们通过消息传递进行通讯。在典型情况下,每个概念模块都有一个进程。因此,假如在设计中有一个系统调用模块,那么就必然有一个相应的进程来接收系统调用,并和能够执行系统调用的其他进程(或模块)通讯以完成所需任务。</p>
</blockquote>
<p>如果读过《操作系统原理》及其相关书籍的人应该很了解这些,对就的我们就可以一目了然地解决我们当前是的微服务的问题。</p>
<p>文章的来源是James Lewis与Martin Fowler写的<a href="http://martinfowler.com/articles/microservices.html">Microservices</a>。对就于上面的</p>
<ul>
<li>monolithic kernel</li>
<li>microkernel</li>
</ul>
<p>与文中的</p>
<ul>
<li>monolithic services</li>
<li>microservices</li>
</ul>
<p>我们还是将其翻译成<code>微服务</code>与<code>宏服务</code>。</p>
<p>引起原文中对于微服务的解释:</p>
<blockquote>
<p>简短地说,微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,通过轻量的通讯机制联系,经常是基于HTTP资源API,这些服务基于业务能力构建,能够通过自动化部署方式独立部署,这些服务自己有一些小型集中化管理,可以是使用不同的编程语言编写,正如不同的数据存储技术一样。</p>
</blockquote>
<p>原文是:</p>
<blockquote>
<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>
</blockquote>
<p>而关于微服务的提出是早在2011年的5月份</p>
<blockquote>
<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>
</blockquote>
<p>简单地与微内核作一些对比。微内核,<strong>微内核部分经常只但是是个消息转发站</strong>,而微服务从某种意义上也是如此,他们都有着下面的优点。</p>
<ul>
<li>有助于实现模块间的隔离</li>
<li>在不影响系统其他部分的情况下,用更高效的实现代替现有文档系统模块的工作将会更加容易。</li>
</ul>
<p>对于微服务来说</p>
<ul>
<li>每个服务本身都是很简单的</li>
<li>对于每个服务,我们可以选择最好和最合适的工具来开发</li>
<li>系统本质上是松耦合的</li>
<li>不同的团队可以工作在不同的服务中</li>
<li>可以持续发布,而其他部分还是稳定的</li>
</ul>
<p>从某种意义上来说微服务更适合于大型企业架构,而不是一般的应用,对于一般的应用来说他们的都在同一台主机上。无力于支付更多的系统开销,于是如<strong>微服务不是免费的午餐</strong>一文所说</p>
<ul>
<li>微服务带来很多的开销操作</li>
<li>大量的DevOps技能要求</li>
<li>隐式接口</li>
<li>重复努力</li>
<li>分布式系统的复杂性</li>
<li>异步性是困难的!</li>
<li>可测试性挑战</li>
</ul>
<p>因而不得不再后面补充一些所知的额外的东西。</p>
<p>针对于同样的话题,开始了解其中的一些问题。当敏捷的思想贯穿于开发过程时,我们不得不面对持续集成与发布这样的问题。我们确实可以在不同的服务下工作,然而当我们需要修改API时,就对我们的集成带来很多的问题。我们需要同时修改两个API!我们也需要同时部署他们!</p>
<h2 id="混合微服务">混合微服务</h2>
<p>在设计所谓的“Next-Generation CMS”,即Echoes CMS的时候,对于我这种懒得自己写Django App的人来说,通过我会去复制别人的代码,于是我继续在Github上漫游。接着找到了DjangoProject.com的源码,又看了看Mezzanine(ps: 我博客用的就是这个CMS)。于是从DjangoProject复制了Blog的代码,从Mezzanine复制了conf的代码,然后就有了Echoes的codebase。然后,继之前的文章(《微服务的小思考》我想了想, 这不就是我想要的模型么?</p>
<p>微服务与Django</p>
<p>Django 应用架构 Django MVC结构如下如示:</p>
<figure>
<img src="./img/backend/django_mvc.png" alt="Django MVC" /><figcaption>Django MVC</figcaption>
</figure>
<p>然后,记住这张图,忘记上面的MVC,Django实际上是一个MTV</p>
<ul>
<li>Model</li>
<li>Template</li>
<li>View</li>
</ul>
<p>主要是Django中的views.py通常是在做Controller的事。</p>
<p>然而对于一个Django的应用来说,他的架构如下所示:</p>
<figure>
<img src="./img/backend/django-app.jpg" alt="Django apps architecture" /><figcaption>Django apps architecture</figcaption>
</figure>
<p>Django的每个App就代表着程序的一个功能。每个App有自己的models、views、urls、templates所以对于一个app来说他的结构如下:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">.</span>
<span class="kw">|</span><span class="ex">______init__.py</span>
<span class="kw">|</span><span class="ex">____models.py</span>
<span class="kw">|</span><span class="ex">____tests.py</span>
<span class="kw">|</span><span class="ex">____views.py</span></code></pre></div>
<p>如果是新版的Django那么它的结构如下:</p>
<div class="sourceCode"><pre class="sourceCode bash"><code class="sourceCode bash"><span class="ex">.</span>
<span class="kw">|</span><span class="ex">______init__.py</span>
<span class="kw">|</span><span class="ex">____admin.py</span>
<span class="kw">|</span><span class="ex">____migrations</span>
<span class="kw">|</span> <span class="kw">|</span><span class="ex">______init__.py</span>
<span class="kw">|</span><span class="ex">____models.py</span>
<span class="kw">|</span><span class="ex">____tests.py</span>
<span class="kw">|</span><span class="ex">____views.py</span></code></pre></div>
<p>上面少了templates,最后会有一个总的URL,即第一张图的URL Dispatcher。接着,让我们看看微服务是怎样的。</p>
<p>一个典型的微服务如下所示:</p>
<figure>
<img src="./img/backend/microservices_a.jpg" alt="microservices architecture" /><figcaption>microservices architecture</figcaption>
</figure>
<p>有不同的技术栈python、spring、scala,但是他们看上去和Django应用的图差不多,除了数据库不一样。</p>
<p>与其将复杂的测试、逻辑部分变得不可测,不如把这些部分放置于系统内部。</p>
<figure>
<img src="./img/backend/linux_os.jpg" alt="Linux OS Hybrid" /><figcaption>Linux OS Hybrid</figcaption>
</figure>
<p>当我们在我们的服务器上部署微服务的时候,也就意味着实现所以的服务都是在我们系统的内部,我们有一个Kernel以及他们的Kernel Moduels,即微服务群们。他们调用DB,或者某些第三方服务。</p>
<p>System Libraries相当于我们的URL Dispatcher。而我们的URL Dispatcher实际上所做的便是将各自调用的服务指向各自的app。</p>
<p>这样我们即可以解决部署的问题,又可以减少内部耦合。</p>
<h2 id="其他">其他</h2>
<blockquote>
<p>我猜,微服务的流行是因为程序员可以欢乐地使用自己的语言,哪怕是Logo。</p>
</blockquote>
<p>参考</p>
<p><a href="http://highscalability.com/blog/2014/4/8/microservices-not-a-free-lunch.html">Microservices - Not A Free Lunch!</a></p>
<p><a href="http://martinfowler.com/articles/microservices.html">Microservices</a></p>
<h1 id="前后端篇">前后端篇</h1>
<h2 id="前后端分离">前后端分离</h2>
<p>这是一个很古老的话题,对于大公司来说就是部门大了,需要拆分。因此开始之前,先提一下“康威定律”:</p>
<blockquote>
<p>Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations.</p>
</blockquote>
<p>换成中文,即:<strong>设计系统的组织,其产生的设计和架构等价于组织间的沟通结构</strong>。上图</p>
<figure>
<img src="./img/front-back-end/conway.jpg" alt="Conway" /><figcaption>Conway</figcaption>
</figure>
<p>这张图可以解释相当多的软件开发过程中的问题,而我们知道软件开发的主要问题是沟通问题。组织结构影响了我们的沟通结构,进而影响了我们的软件系统结构。好吧,我承认可能离题有点远。不过,我想说的是组织结构可能不允许我们做出一些好的系统架构。</p>
<p>如我们在《<a href="http://mp.weixin.qq.com/s?src=3&timestamp=1467515714&ver=1&signature=z1onJvKn4TSrUmXm384CQUF1IZBVsLShsQ4DpmumN6xY0Gm5RR9XKdbf6ELzdRqg-mxdtxceTg-4-KrhYHZQC6wiSEWsP64vh0sl2Je4G16hnS6MsuZaD-u01HAENCSKuw3mQL-F2Gc5WYvti9SQlw==">RePractise前端篇: 前端演进史</a>》中提到的那样:我们已经有了一个桌面版网页,然后我们打造了一个APP。然而,总有些客户想在手机上浏览但是又不想下APP,我们就需要一个移动版。为什么会这样?因为用户已经被养成了这样的习惯,大部分的网站提到了桌面版、移动版、APP。要维护这样的三个不同的系统,对于大部分的业务公司来说成本太高了。</p>
<p>于是,大部分公司来说解决方案就是 后台 + 大前端 (桌面前端、移动Web、手机APP)。Angular和React就是为了解决这样的问题,而出现了不同的解决方案——基于Angular.js的混合应用框架Ionic、以及React Native。不过在当前,我对React Native的共用UI还是持观望态度。有人可能会提到Vue和Weex,但是我觉得并没有那么好用。或许是因为我接触React比较早,我觉得Vue的语法四不像。</p>
<p>在这样的情形下,我们只需要几个后台开发人员和几个前端开发人员就可以完成系统的设计了。这种前端开发人员就是最近几年人们“最想要”的。</p>
<h2 id="单页面应用后台渲染">单页面应用后台渲染</h2>
<p>我已经想不到一个好的关于前端分享的主题了,于是联想到最近想要做的一件事,就想到了这个标题。或许这是一个好的主题,又或许这不是一个好的主题。但是至少我可以Share一下我的经验:</p>
<ul>
<li>基于Mustache模板引擎的前后台渲染。</li>
<li>基于PreRender方式的Angular.js应用的后台渲染</li>
<li>服务端渲染的React</li>
</ul>
<p>开始之前,我希望即使你们需要后台渲染,你们也应该前后端分离!由<strong>后台</strong>来提供API数据,前端用自己的后台来渲染页面。听上去有点绕,简单的来说就是不要把大量的业务逻辑放前台来,只把显示逻辑放在前台上。这样一来,即使有一天我们换了新的前端,如移动应用,那么我们的后台也是可用的。。</p>
<h3 id="前后台渲染同一模板">前后台渲染同一模板</h3>
<p>我接触的第一个SPA应用是一个基于Spring MVC和Backbone的移动网站,但是它比一般的SPA应该要复杂——由于SEO的缘故,它需要支持后台渲染。</p>
<p>当搜索引擎通过URL访问我们的网站的时候,我们就需要返回相应的HTML。这意味着我们需要在后台有对应的模板引擎来支持,而由于SPA的性质又决定了,这需要使用一个纯前端的模板引擎。因此,我们并不能使用两个模板引擎来做这件事,维护两套模板注定会是一件痛苦的事,并且当时还没有React这种模板引擎在。不过,后来我们发现维护两种不同的渲染方式也是一件痛苦的事。因此,我们就会有了类似于下图的架构:</p>
<figure>
<img src="./img/front-back-end/spring-backbone.png" alt="Spring MVC Backbone" /><figcaption>Spring MVC Backbone</figcaption>
</figure>
<p>我们在后台使用Spring MVC作为基础架构、Mustache作为模板引擎,和使用JSP作为模板引擎相比没有多大的区别——由Controller去获取对应的Model,再渲染给用户。多数时候搜索引擎都是依据Sitemap来进行索引的,所以我们的后台很容易就可以处理这些请求。同样的当用户访问相应的页面的时候,也返回同样的页面内容。当完成页面渲染的时候,就交由Backbone来处理相应的逻辑了。换句话来说,从这时候它就变成了一个单页面应用。</p>
<p>尽管这是一个三年年前开始的项目,但是在今天看来,这种做法仍然相应地有趣: 大部分的单页面应用只有一个首页,并由HTTP服务器(如Nginx)、Web框架(如Express、Koa)对路由做一些处理,可以让用户通过特定地URL访问特定地页面。而我们需要保证所有的用户访问地都是真实的页面,既然JavaScript没有加载完,用户也能看到完整的页面。</p>
<p>在这个项目里,最大的挑战就是如何保证后台渲染和前台渲染的业务逻辑是一样的。如当我们想要针对不同的产品显示不同的内容时,我们就需要在JavaScript中赋予一些逻辑,我们还需要在Java在有同样的逻辑。相比于在同一个代码里有桌面版、移动版来说,逻辑有更加复杂的趋势——因为在这种情况下,我们只需要维护两个不同的模板即可。而在SPA的情况下,我们要维护<strong>两套逻辑</strong>。后来,这个框架交由下文中的React与响应式设计重写。</p>
<p>在今天你仍然可以使用这样的方式来渲染,JDK 1.8自带了嵌入式JavaScript引擎Nashorn,完成支持ECMAScript 5.1规范以及一些扩展。</p>
<h3 id="prerender方式">PreRender方式</h3>
<p>在我们重新设计系统的时候,曾经考虑过类似的做法。将我们的所有页面渲染成静态的HTML,然后用爬虫抓取我们的所有页面,再上传到AWS即可。当时我们咨询了其他小组的做法,其中有一个小组正是采用了这种PreRender的方式——在本地运行起一个Server,由PhantomJS来渲染页面,再保存为对应的HTML。</p>
<p>PreRender就是预先渲染好HTML,并针对于爬虫返回特定的HTML。(PS:不过作为一个很有经验的SEO开发人员,我一点不喜欢这种作法。要知道Google有时候会模拟成真实的用户,不带有爬虫的那些参数和标志,去访问页面。如果你返回给Google的两个页面差异太大——可能是你忘记更新了频率,那么Google可能就会认为你在<strong>作弊</strong>。)</p>
<figure>
<img src="./img/front-back-end/angular-prerender.jpg" alt="PreRender" /><figcaption>PreRender</figcaption>
</figure>
<p>对于一般用户来说就不会返回后台渲染的结果了:</p>
<figure>
<img src="./img/front-back-end/angular-phantomjs-prereder.jpg" alt="Angular PreRender" /><figcaption>Angular PreRender</figcaption>
</figure>
<p>和上面的第一种情况相比,这种作法可以大大减少服务器地负担,并且可以直接交由CDN就可以了。这时我们只需要考虑要渲染哪些页面即可,对于数据量比较少的网站来说这是一个不错的做法,但是多了就不一样了。</p>
<p>对于我们来说,有两个问题:一个是速度的问题,他们有上万条数据就需要近一天左右的时间来生成(渲染时间长),而我们有上百万条数据。二是数据的实时问题,我们的产品数据每天都会更新一次。</p>
<h3 id="react">React</h3>
<p>对于使用React的开发人员来说,要处理后台渲染就是一种更简单的事,毕竟React中提供了一个方法叫 renderToString()。我们所要做的就是用Express或者Koa对路由进行处理,然后返回对应的内容即可:</p>
<figure>
<img src="./img/front-back-end/react-server-side-render.png" alt="React Server Side Render" /><figcaption>React Server Side Render</figcaption>
</figure>
<p>然后,剩下的事都可以交由React来解决,就是这么简单。</p>
<p>因为在这个时候我们在前后台使用的都是JavaScript,我们可以在这个地方直接实现对数据库的操作,就会出现我们在开头说到的前后台分离的问题。这样做并不合理,后台只应该返回我们所需要的数据,并且它可以随时被其他语言替换掉。</p>
<h1 id="从真实世界到前后端">从真实世界到前后端</h1>
<p>RePractise终于又迎来了新的一篇,要知道上一篇可是在半年前呢——《Repractise前端篇: 前端演进史 》。照RePractise惯例,这又是一篇超长文以及个人的扯淡过程。</p>
<p>当然这也是一个神奇的标题,因为我已经想不到一个好的名字了,不过先这样吧。这篇文章算是我最近两三个月的一篇思考。在上一个项目的打杂生涯里,我开始去学习架构方面的知识,开始去接触DDD的思想。从编码到架构,再回到实际的编码中,总会有很多的灵感闪现。</p>
<h2 id="从真实世界到前后端-1">从真实世界到前后端</h2>
<p>我们所写的代码在某种程度上都反应了真实世界的模型、行为等等。一个比较常见的模型就是:购物模型。同时, 这也是一个很好的展示前后端分离的模型。</p>
<figure>
<img src="./img/no-stacks/store-model.jpg" alt="store-model.jpg" /><figcaption>store-model.jpg</figcaption>
</figure>
<p>(PS: 原谅我的画工)</p>
<h3 id="便利店与售货员">便利店与售货员</h3>
<p>对于一般的便利店来说,只有一个销售员,ta负责整个商店的一系列事务。从某种意义上来说,ta就是整个系统的核心,负责了系统的业务和事件。</p>
<p>一般来说在一个购买流程里,会有三个主要的人或物:</p>
<ul>
<li>售货员。一般来说,ta只会在最后的结账流程中出以及顾客询问时做出响应。</li>
<li>货物。没啥可解释的,就是一堆模型。0</li>
<li>顾客 。浏览商店、对比商店、blabla等等。</li>
</ul>
<p>如果我们要构建这样一个系统,我们只需要区分出系统的各个部分,那么剩下的事情就变得很简单了。</p>
<figure>
<img src="./img/no-stacks/domain.jpg" alt="domain.jpg" /><figcaption>domain.jpg</figcaption>
</figure>
<p>由于整个系统仍然是相当复杂的,我们在这里只关注于用户购买的过程。</p>
<h3 id="模型领域抽象">模型、领域、抽象</h3>
<p>从购买过程来说,顾客所要做的事情就是:</p>
<ul>
<li>浏览、对比商品</li>
<li>加到购物车</li>
<li>结账、付钱</li>
</ul>
<p>对应的也就是有模型、领域和抽象几个概念。</p>
<h4 id="模型"><strong>模型</strong></h4>
<p>这些商品实现上就是相当于一系列的模型及数据。在用户购买之前,我们只需要一个去获取一个个的数据接口,并展示这些数据。</p>
<p>对应于这些商品要建起Schema来是一件容易的事。作为一个商品,他们都拥有着一些共同的元素:price, name, description, location, manufacturer等等的信息。其中一些属性,还会有复杂的对应关系:</p>
<figure>
<img src="./img/no-stacks/product-schema.png" alt="Product Schema" /><figcaption>Product Schema</figcaption>
</figure>
<p>这些需要在我们建立数据库的时候,尽可能地明确好这些关系。由于业务本身是难以预料的,你可能和我们之前的项目一样需要一个addtionInfo的字段,来用JSON存储一些额外的字段。当然如果你使用的是NoSQL,那就再好不过了。</p>
<p>最好你还使用了<strong>读写分离架构</strong>,一种比较常见的用法就是CMS网站,人们使用数据库来存储内容,使用静态页面来展示这些内容。比较好的实践还有CQRS(Command Query Responsibility Segregation, 命令查询职责分离模式),用于CRUD(增、删、改,当然也可以查)的Command,以及Query的查询分开。简单的来说,就是有两个不同的数据持久化中心:</p>
<figure>
<img src="./img/no-stacks/basic-cqrs.png" alt="Basic CQRS" /><figcaption>Basic CQRS</figcaption>
</figure>
<p>这一点特别适合于那些查询、搜索为主的网站,如淘宝。哈哈,我们离题有点远了,总之我们就是在这里提供了数据库的灵气,并对一些字段做一些简单的处理。听上去感觉GraphQL更适合做这样的事。</p>
<h4 id="领域"><strong>领域</strong></h4>
<p>而顾客及售货员在整个过程中做的事情就是领域(Domain,《实现领域驱动设计》)——即一个组织所做的事情以及其中所包含的一切。对于顾客和售货员来说,他们在各自的领域里做着不同的事。</p>
<p>对于顾客来说,其整个浏览过程,基本上都是在前端完成:</p>
<ul>
<li>搜索、查找商品 -> 获得商品列表</li>
<li>查找商品详细</li>
<li>切换到下一个商品</li>
</ul>
<p>这个场景下就特别适合于上面说到的读写分离架构。在浏览过程中,对用户的数据进行监控,以用于了解用户的行为,改善用户体验。这也是很常见的功能,或者说他们是无处不在的模式:</p>
<ul>
<li>结果页 / 列表页</li>
<li>详情页</li>
</ul>
<p>随后的用户收藏、添加到购物车、购买、交付等流程都需要与后台进行更紧密的交付。而这些都和售货员都有紧密的关系,而这些就不是一种简单的事。</p>
<p>从用户购买完成以后,剩下的就是一堆琐碎的事了,而这些都是由后端来完成的:</p>
<ul>
<li>订单子系统</li>
<li>物流系统</li>
<li>发票系统</li>
<li>支付系统</li>
</ul>
<p>等等。</p>
<p>对于用户来说,一种最简单的情况就是亚马逊,你只需要按一下“一键下单”即可。不需要关心后面的操作了,同样的这也适合于我们的业务场景。</p>
<h4 id="抽象"><strong>抽象</strong></h4>
<p>抽象本来不打算写的,但是后来想了想还是写。总的来说整个过程还是需要相对比较好的抽象能力,要不我就很难讲述清楚这里面的过程了。</p>
<p>抽象是很神奇的东西,也可以分为几个不同的境界——但是我也不知道有几个境界,简单的来说就是不同的人看上去就有不同的东西。如有的人看到下面的画就是一坨shit——还不如小学生画的呢,有的人就会惊呼大师。</p>
<figure>
<img src="./img/no-stacks/2012070208374547914.jpg" alt="星空" /><figcaption>星空</figcaption>
</figure>
<p>反正,我也很看不懂。这一点倒类似于最初我对设计模型的理解一样:</p>
<ul>
<li>一开始不以为然</li>
<li>然后发现很棒</li>
<li>接着使用过度</li>
<li>最后就和最好的编程器Emacs一样</li>
</ul>
<figure>
<img src="./img/no-stacks/editor-learning-curve.png" alt="Editor Learning Curve" /><figcaption>Editor Learning Curve</figcaption>
</figure>
<p>这些都在随着编程生涯的展开而发生一些变化,我们不断地抽象出一些概念,以至于到了最后刚进入这个行业的人都看不懂。但是,这些都是一点点在一层层抽象的基础上产生的。</p>
<figure>
<img src="./img/no-stacks/needs.jpg" alt="Needs" /><figcaption>Needs</figcaption>
</figure>
<p>所以,我就把这一小小节扯完了,反正我是不想说得太抽象了。接着,让我们再扯点技术性的话题。</p>
<h2 id="前后台分离后台">前后台分离:后台</h2>
<p>典型的Web应用框架就是类似于这样的架构:</p>
<figure>
<img src="./img/no-stacks/spring-web-app-architecture.png" alt="Spring Web App Architecture" /><figcaption>Spring Web App Architecture</figcaption>
</figure>
<p>又或者是MVC架构,但是这已经不重要了。我们都前后端分离了,是时候把V层去掉了。</p>
<figure>
<img src="./img/no-stacks/mvc_role_diagram.png" alt="MVC Role Diagram" /><figcaption>MVC Role Diagram</figcaption>
</figure>
<p>我们继续以上面的浏览来购买流程来做扯淡,后台除了提高上面的商品信息以外,在购买的时候还需要对用户进行授权。当然注册就是另外一个话题了,另外一个很大的话题。</p>
<p>所有的这些我们都可以向前台提供对应的API即可。理想的情况下,我们对应于不同的模块可以有不同的服务:</p>
<figure>
<img src="./img/no-stacks/microservices.png" alt="MicroServices" /><figcaption>MicroServices</figcaption>
</figure>
<p>但是现实并不总是这么美好的,而在我们当前情况下则可以——毕竟所有的用户都应该能浏览所有的商品,这时就不需要做特殊的处理了。</p>
<p>在这个过程中,我们还有一系列的操作需要在后台来完成。不过,这些都可以在内部中完成的。而复杂的过程,实际上还存在于前端的逻辑当中。</p>
<h2 id="前后台分离前端">前后台分离:前端</h2>
<p>开始时,我们需要这样做去获取一个个的商品详情。这些数据也可以在返回页面模板的时候,直接将API数据填充到HTML中——带后台渲染的React都是这样做的。然后在用户浏览的过程中,我们还需要遵循这样的数据流程:</p>
<ul>
<li>获取数据。无论是Ajax,还是新的Fetch API都可以做这样的事。</li>
<li>处理数据。依据于业务的需要对数据进行一些特殊的处理,如修改时间、价格的显示格式,描述的长度等等。</li>
<li>显示数据。只需要一个简单的模板引擎,过去的JSP、Mustache、今天的React都在做同样的事。</li>
</ul>
<p>而在进入这个页面之前,我们还需要关注几个基本的元素,虽然这些都是将由框架来解决的。</p>
<ul>
<li>路由。无论你遵不遵循REST的实践,我们只需要给对应的页面一个URL即可。相应的如果我们需要SEO,那么我们也需要在后台有一个对应的URL。理想的情况下,他们应该是由一个URL。</li>
<li>模块化。从过去Require.js的火热,到今天的各式各样的框架内建的模块化框架,他们解决都是一个问题:代码度的问题。这一点和后台采用的微服务架构的缘由好像是一样。</li>
<li>控制器。我也想不起来为什么控制器非在这里不可,但是我想只有这样,我才能快速地找到这个文件。</li>
</ul>
<p>作为前端,我们不仅仅要负责的页面的美观,还要对用户的事件做出响应,并且做好用户体验。</p>
<ul>
<li>事件</li>
</ul>
<p>这就是最近火热的关于DOM的讨论的原因,当顾客想了解一下一些不一样东西的时候,我们就需要对DOM进行操作。</p>
<p>我突然就有一个问题了,你真的有那么多DOM需要操作么?你又不是Facebook,有那么多的Timeline和事件流。还有你的用户体验是不是做得足够好了?顺便再补一刀,如果你使用了React,并且没有进行前后端分离,那就等下一个恶梦。</p>
<p>最后,当用户买下东西的时候,我们也需要这样的交互流程。</p>
<h2 id="repractise-2">RePractise</h2>
<p>因为最近我对DDD又有了一些想法,还在想着如何直接由真实世界来建模。顺便整理了这些思路到一起,但是好似这样的设计更简单。</p>
<h1 id="重构篇">重构篇</h1>
<p>什么是重构?</p>
<blockquote>
<p>重构,一言以蔽之,就是在不改变外部行为的前提下,有条不紊地改善代码。</p>
</blockquote>
<p>相似的</p>
<blockquote>
<p>代码重构(英语:Code refactoring)指对软件代码做任何更动以增加可读性或者简化结构而不影响输出结果。</p>
</blockquote>
<h2 id="网站重构">网站重构</h2>
<p>与上述相似的是:在不改变外部行为的前提下,简化结构、添加可读性,而在网站前端保持一致的行为。也就是说是在不改变UI的情况下,对网站进行优化,在扩展的同时保持一致的UI。</p>
<p>过去人们所说的<code>网站重构</code></p>
<blockquote>
<p>把“未采用CSS,大量使用HTML进行定位、布局,或者虽然已经采用CSS,但是未遵循HTML结构化标准的站点”变成“让标记回归标记的原本意义。通过在HTML文档中使用结构化的标记以及用CSS控制页面表现,使页面的实际内容与它们呈现的格式相分离的站点。”的过程就是网站重构(Website Reconstruction)</p>
</blockquote>
<p>依照我做过的一些案例,对于传统的网站来说重构通常是</p>
<ul>
<li>表格(table)布局改为DIV+CSS</li>
<li>使网站前端兼容于现代浏览器(针对于不合规范的CSS、如对IE6有效的)</li>
<li>对于移动平台的优化</li>
<li>针对于SEO进行优化</li>
</ul>
<p>过去的网站重构就是“DIV+CSS”,想法固然极度局限。但也不是另一部分的人认为是“XHTML+CSS”,因为“XHTML+CSS”只是页面重构。</p>
<p>而真正的网站重构</p>
<blockquote>
<p>应包含结构、行为、表现三层次的分离以及优化,行内分工优化,以及以技术与数据、人文为主导的交互优化等。</p>
</blockquote>
<p>深层次的网站重构应该考虑的方面</p>
<ul>
<li>减少代码间的耦合</li>
<li>让代码保持弹性</li>
<li>严格按规范编写代码</li>
<li>设计可扩展的API</li>
<li>代替旧有的框架、语言(如VB)</li>
<li>增强用户体验</li>
</ul>
<p>通常来说对于速度的优化也包含在重构中</p>
<ul>
<li>压缩JS、CSS、image等前端资源(通常是由服务器来解决)</li>
<li>程序的性能优化(如数据读写)</li>
<li>采用CDN来加速资源加载</li>
<li>对于JS DOM的优化</li>