架构解耦
---

解耦是一件很有意思的过程，它也能反应架构的变迁。

###从MVC与微服务

在我初识架构是什么的时候，我看到了MVC模式架构。这种模式是基于分层的结构，要理解起逻辑也很简单。这个模式如下图所示：

![Spring MVC](chapters/chapter8/spring-mvc.png)

由我们的Front controller来处理由客户端（浏览器）发过来的请求，实际上这里的Front controller是DispatcherServlet。DispatcherServlet负责将请求派发到特定的handler，接着交由对应的Controller来处理这个请求。依据请求的内容，Controller将创建相应model。随后这个model将传到前端框架中渲染，最后再返回给浏览器。

但是这样的架构充满了太多的问题，如view与controller的紧密耦合、controller粒度难以把控的问题等等。

####Django MTV

我使用Django差不多有四年了，主要是用在我的博客上。与MVC模式一对比，我发现Django在分层上还是很有鲜明特性的：

![Django MTV架构](chapters/chapter8/django-mtv.png)

在Django中没有Controller的概念，Controller做的事都交由URL Dispatcher，而这是一个高级的URL Dispatcher。它使用正则表达式匹配URL，然后调用合适的Python函数。然后这个函数就交由相应的View层来处理，而这个View层则是处理业务逻辑的地方。处理完后，model将传到Template层来处理。

对比如下图如示：

传统的MVC架构 | Django 架构
----------------------|-----------
Model      | Model(Data Access Logic)
View       |Template(Presentation Logic)
View       | View(Business Logic)
Controller | Django itself

从上面的对比中，我们可以发现Django把View分层了。以Django对于MVC的解释来说，视图用来描述要展现给用户的数据。 而在ROR等其他的MVC框架中，控制器负责决定向用户展现哪些数据，而视图决定如何展现数据。

联想起我最近在学的Scala中的Play框架，我发现了其中诸多的相似之处：

![Play框架异步请求](chapters/chapter8/playarchtectureasyncrequest.png)

虽然在Play中，也有Controller的概念。但是对于URL的处理先交给了Routes来处理，随后再交给Controller中的函数来处理。

不过与一般MVC架构的最大不同之处，怕是在于Django的APP架构。Django中有一个名为APP的概念，它是实现某种功能的Web应用程序，。如果我们要设计一个博客系统的话，那么在这个项目中，Blogpost是一个APP、评论是一个APP、用户管理是一个APP等等。每个APP之中，都会有自己的Model、View和Controller。其架构如下图所示：

![Django APP架构](chapters/chapter8/django_app_arch.jpg)

当我们需要创建一个新的功能的时候，我们只需要创建一个新的APP即可——为这个APP配置新的URL、创建新的Model以及新的View。如果功能上没有与原来的代码重复的话，那么这就是一个独立的APP，并且我们可以将这个APP的代码Copy/Paste到一个新的项目中，并且不需要做修改。

与一般的MVC架构相比，我们会发现我们细化了这些业务逻辑原来的三层结构，会随着APP的数量发生变化。如果我们有三个APP的话，那么我们相当于有3*三层，但是他不是等于九层。这样做可以从代码上直接减少逻辑的思考，让我们可以更加集中注意力于业务实现，同时也利于我们后期维护。

虽是如此，后来我意识到了这样的架构并没有在意识有太多的先进之处。而这实际上是一个美好但是不现实的东西，因为我们还是使用同一个数据库。

####微服务与Reactive

在微服务架构中，它提倡将单一应用程序划分成一组小的服务，这些服务之间互相协调、互相配合。每个服务运行在其独立的进程中，服务与服务间采用轻量级的通信机制互相沟通。每个服务都应该有自己独立的数据库来存储数据。

![分散数据](chapters/chapter8/decentralised-data.png)

Django从某种意义上有点接近微服务的概念，只是实际上并没有。因为它没有实现Play框架的异步请求机制。抱句话来说，应用很容易就会在调用JDBC、Streaming API、HTTP请求等一系列的请求中发生阻塞。

这些服务都是独立的，对于服务的请求也是独立的。使用微服务来构建的应用，不会因为一个服务的瘫痪让整个系统瘫痪。最后，这一个个的微服务将合并成这个系统。

![Combined List](chapters/chapter8/combinedlist.png)

我们将我们后台的服务变成微服务的架构，在我们的前台使用Reactive编程，这样我们就可以结合两者的优势，解耦出更好的架构模式。然而，这其中还有一个让人不爽的问题，即数据库。如果我们使用多个数据库，那么维护成本也随着上升。而如果我们可以在后台使用类似于微服务的Django MTV架构，并且它可以支持异步请求的话，并在前台使用Reactive来编程，是不是就会更爽一点？

###CQRS

对于复杂的系统来说，上面的做法做确实很不错。但是对于一个简单地系统来说，这样做是不是玩过火了？如果我们要设计一个博客系统的话，那么我们是不是可以考虑将Write/Read分离就可以了？

> 命令和查询责任分离Command Query Responsibility Segregation（CQRS）是一种将系统的读写操作分离为两种独立模型的架构模式。

####CQS

对于这个架构的深入思考是起源于之前在理解DDD。据说在DDD领域中被广泛使用。理解CQRS可以用分离Model和API集合来处理读取和写入请求开始，即CQS（Command Query Separation，命令查询分离）模式。CQS模式最早由软件大师Bertrand Meyer（Eiffel语言之父，面向对象开-闭原则 OCP 提出者）提出。他认为，对象的行为仅有两种：命令和查询。

这个类型的架构如下图所示：

![CQS Basic](chapters/chapter8/cqrs-2.png)

> 除了编写优化的查询类型，它可以让我们轻松换API的一部分读一些缓存机制，甚至移动读取API的请求到另一台服务器。

对于读取和写入相差不多的应用来说，这种架构看起来还是不错的。而这种架构还存在一个瓶颈问题，使用同一个RDBMS。对于写入多、读取少的应用来说，这种架构还是存在着不合理性。

为了解决这个问题，人们自然是使用缓存来解决这个问题了。我们在我们的应用服务外有一个HTTP服务器，而在HTTP服务器之外有一个缓存服务器，用于缓存用户常驻的一些资源。如下图所示：

![带缓存的Web架构](chapters/chapter8/cache-website-blog.png)

而实际上这样的服务器可能是多余的——我们为什么不直接生成HTML就好了？

####编辑-发布分离

或许你听过Martin Folwer提出的编辑-发布分享式架构：即文章在编辑时是一个形式，而发表时是另一个形式，比如用markdown编辑，而用html发表。

![编辑-发布分离](chapters/chapter8/edit-pub.jpg)

而最典型的应用就是流行于GitHub的Hexo、Jekyll框架之类的静态网站。如下图所示的是Hexo的工作流：

![Hexo站点工作流](chapters/chapter8/hexo-workflow.png)

我们在本地生成我们的项目，然后可以创建一个新的博客、开始编写内容等等。接着，我们可以在本地运行起这个服务，除了查看博客的内容，还可以修改样式等等。完成上面的工作后，我们就可以生成静态内容，然后部署我们的应用到GitHub Page上。这一切看上去都完美，我们有两个不同的数据源——一个是md格式的文本，一个是最后生成的html。它们已经实现了读写/分离：

![CQRS进阶](chapters/chapter8/cqrs-separate-storage.png)

但是作为一个前端开发人员，没有JSON，用不了Ajax请求，我怎么把我的博客做成一个单页面应用？

####编辑-发布-开发分离

因为我们需要交我们的博客转为JSON，而不是一个hexo之类的格式。有了这些JSON文件的存在，我们就可以把Git当成一个NoSQL数据库。同时这些JSON文件也可以直接当成API来

![Git As NoSQL DB](chapters/chapter8/git-internals-commits.png)

其次，这些博客还需要hexo一样生成HTML。

并且，开发人员在开发的时候不会影响到编辑的使用，于是就有了下面的架构：

![基于git的编辑-发布分离](chapters/chapter8/travis-edit-publish-code.png)

在这其中我们有两种不同的数据形式，即存储着Markdown数据的JSON文件和最后生成的HTML。

对博客数量不是很大的网站，或者说一般的网站来说，用上面的技术都不是问题。然而有大量数据的网站怎么办？使用EventBus：

![CQRS和EventBus](chapters/chapter8/cqrs-arch.png)

在我之前玩的一个Demo中，使用Python中的Scrapy爬虫来抓取现有的动态网站，并将其变成静态网站部署到AWS S3上。

但是上面仅仅只是实现了文章的显示，我们还存在一些问题：

1. 搜索功能
2. AutoComplete

等等的这些服务是没有用静态API来实现的。

###CQRS结合微服务

既然可以有这么多分法，并且我们都已经准备好分他们了。那么分了之后，我们就可以把他们都合到一起了。

####Nginx as Dispatcher

最常见的解耦应用的方式中，就有一种是基于Nginx来分发URL请求。在这种情况下，对于API的使用者，或者最终用户来说，他们都是同一个API。只是在后台里，这个API已经是不同的几个API组成，如下图所示：

![Nginx解耦微服务](chapters/chapter8/nginx-microservices.png)

客户端的请求来到API Gateway，根据不同的请求类型，这些URL被分发到不同的Service，如Review Service、Order Service等等。

对于我们想要设计的系统来说也是如此，我们可以通过这个Dispatcher来解耦我们的服务。

####CQRS结合微服务

现在，我们想要的系统的雏形已经出现了。

从源头上来说，我们把能缓存的内容变成了静态的HTML，通过CDN来分发。并且，我们还可以将把不同的服务独立出来。

从实现上来说，我们将博客的数据变成了两部分： 一个以Git + JSON格式存在的API，它除了可以用于生成HTML，另外一部分作为API来使用。

![CQRS结合微服务](chapters/chapter8/dispatcher-services.png)

最后，我们可以通过上面说到的Nginx或者Apache来当这里的Request Dispatcher。
