架构解耦
---

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

###从 MVC 到微服务

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

![Spring MVC](assets/article/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架构](assets/article/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 框架异步请求](assets/article/chapter8/playarchtectureasyncrequest.png)

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

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

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

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

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

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

####微服务与 Reactive

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

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

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

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

![Combined List](assets/article/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](assets/article/chapter8/cqrs-2.png)

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

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

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

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

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

####编辑-发布分离

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1. 搜索功能
2. AutoComplete

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

###CQRS 结合微服务

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

####Nginx as Dispatcher

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

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

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

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

####CQRS 结合微服务

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

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

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

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

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