如何设计一个高逼格的博客系统

3,305 阅读9分钟
点击上方“Phodal”可订阅哦!

节选自《Growth: 全栈增长工程师指南》

1MVC

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


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

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

2Django MTV

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


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

对比如下图如示:

ModelModel(Data Access Logic)
ViewTemplate(Presentation Logic)
ViewView(Business Logic)
ControllerDjango itself

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

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


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

3异步与MVC

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

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

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

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

4微服务与Reactive

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


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

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


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

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

5CQS

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

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

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


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

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

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


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

6编程-发布分离

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


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


我们在本地生成我们的项目,然后可以创建一个新的博客、开始编写内容等等。接着,我们可以在本地运行起这个服务,除了查看博客的内容,还可以修改样式等等。完成上面的工作后,我们就可以生成静态内容,然后部署我们的应用到GitHub Page上。

这一切看上去都完美,我们有两个不同的数据源——一个是md格式的文本,一个是最后生成的html。它们已经实现了读写/分离:


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

7编程-开发-发布分离

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


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

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


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

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


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

但是如果仅仅只是如何,我们还存在一些问题:搜索功能、AutoComplete 等等的这些服务是没有用静态API来实现的。

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

9Nginx As Dispatcher

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


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

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

9CQRS结合微服务

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

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

CQRS结合微服务

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

理论写完了,可以实践了。

更多精彩内容请关注