文章目录

  • 为什么要做分层设计和领域建模
  • 微服务设计应对之道
  • 为什么会有网关层?
  • 各层的职能是什么?
  • 用户界面层/表示层
  • 网关层
  • 应用服务层
  • 统一权限校验
  • 业务数据网关
  • 资源控制和缓存
  • 资源聚合和加工(包括定时任务、数据转换)
  • 其他(如参数校验、异常处理等)
  • 领域服务层
  • 基础设施层
  • 各层的设计原则有哪些?
  • 高内聚(复用)低耦合(解耦)
  • 层级调用
  • 领域服务层微服务如何拆分?
  • Q&A
  • 是否每一层都可以直接访问数据库?
  • 领域服务层多个微服务之间是否可以共享数据库?
  • 怎么破?
  • 其它


为什么要做分层设计和领域建模

1、提高开发效率
2、提高应用的可扩展性及可维护性

微服务设计应对之道

推荐将Web服务架构分为五层:基础设施层、领域服务层、应用服务层、网关层和用户界面层(表示层)。领域服务层和应用服务层均可以采用微服务设计进行拆分,其中领域服务层推荐按照DDD领域建模进行领域划分,设计为一个个领域模块微服务,每个微服务高度内聚,仅关注自己的业务,领域服务间通过接口调用进行松耦合。这种设计方案可以大大简化大型复杂系统,并且在后期的维护中优势会日渐凸显,然而把大型复杂系统分而治之拆成微服务的同时也对架构师和开发人员提出了更高的要求。

微服务架构项目结分层 微服务 分层_高内聚

为什么会有网关层?

互联网公司产品的输出形式无外乎Web应用(网站、或者网络服务),并且为了更好的适配PC站和App,一般会采用前后端分离的应用设计方案,前后端分离在于应用层提供API接口,表示层调用API实现数据交互,这时候会产生一个需求——内部网络应用系统如何把自己的服务输出到互联网上,供外部系统或者浏览器网页访问。最直接的方式就是把应用层直接暴露在公网上,但我们不建议这么做,应用层服务更多的是关注业务应用,对网络级的系统安全性(防DDOS、钓鱼、跨域等)、请求监控等缺乏考虑,这些工作交给网关层统一管理会轻松很多(比如淘宝的TOP平台)。

这时候在Web应用系统中引入网关层用于衔接表示层和应用层,这样可以更好的划分各层的职能。网关层也可以看作是应用服务层的对外包装层。如果一定要把网关层做到应用服务层里理论上也是可行的,比如针对于Spring Cloud这种框架下的微服务体系,可以考虑直接暴露应用层,只需辅助一些运维手段即可。

各层的职能是什么?

用户界面层/表示层

负责向用户显示和解释用户指令,这里指的用户可以是另一个计算机系统,不一定是使用用户界面的人(比如外部应用调用对应接口)。

网关层

负责提供对外的HTTP服务或者其他应用层协议(这里是指OSI七层协议中的应用层)服务。

应用服务层

业务通用能力的处理层,定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。具体包括:

统一权限校验

如上文所说,网关层只负责网络级的安全防护,业务层的权限校验则需要应用层来完成,试想一个没有应用层的微服务体系,就意味着每一个微服务都需要加上权限校验逻辑,这不仅编码上困难(可以用过滤器,AOP),而且对于成千上万个微服务(据了解,腾讯目前微服务数量已经超过2万,大众点评有将近千个微服务)来说,这会浪费大量时间,调用链越长,浪费的时间越多。换句话说,微服务体系有一个不突出但是很重要的特征,领域间环境安全,领域间的通信应当是可信的,否则分布式的缺点(多服务意味着多次通信)会被加剧。

业务数据网关

举个例子,一个order-service提供了一个queryOrder的接口,输入起始日期查询对应的订单列表,其有2个消费者:C端网站应用服务和报表应用服务,C端网站应用服务只需要知道订单的基本信息如下单时间、商品名称、金额就可以了,而报表应用服务是给管理者看的,需要的订单数据很全,除了C端网站应用服务需要的之外,还需要看平台与商家的结算金额。根据最小权限原则,以及领域层服务设计应当是调用者无关的,我们肯定不能为调用方写定制接口(写不完的,有的要这个数据,有的要那个数据,每次新增调用方,领域服务还得找人修改)。而如果我们统一使用的全量数据,并且没有应用层(同样的也没有应用层模型DTO了),那么很可能我们吐出去的数据包含了我们与商家的结算价,这会引发很多不必要的麻烦的。所以应用层还充当了业务数据网关的作用,应用层应用服务需要保证仅吐出调用方感兴趣的数据。

资源控制和缓存

想象一下双十一高并发的情况,如果查询库存每次都查库是多么恐怖的一件事。所以一般仅在支付的时候做一次库存校验,而在商品展示时查缓存的库存即可。那么问题来了,如果没有应用层,缓存直接放在库存微服务上是否可行呢?首先这会入侵库存领域,库存微服务需要按照调用方的需求做特定时间的缓存,而不是自己想缓存多久就多久,我想库存微服务的开发者也会很不满的,他会提出,让你自己去做缓存。他的方案是科学的,因为还有一些其他服务可能需要实时的数据。这时候就需要有一层来做对其下方微服务返回的数据按照应用自身的需求进行必要的缓存,而不是把这些需求都推给资源提供方,想象一下一个资源提供方有多少需求者,每个需求方都有自己的定制需求,该多痛苦。当然这一点也不是说微服务自身不能做缓存,微服务自身的缓存一定是考虑自身域的合理性后的一个措施(比如订单查询服务会做一个500ms的缓存,因为不会有正常人500ms里点两次查询还必须要求两次都是最新的),而不是由调用方来决定的。

资源聚合和加工(包括定时任务、数据转换)

应用层应用根据自身需求来对下层返回的数据进行聚合和处理。例如:任何APP都有首页,而首页的数据可能是五花八门的,可以有用户昵称、最近下的订单简要信息、最近支出曲线、积分信息等。这4个信息可以来自4个领域微服务,他们是:用户中心、订单中心、支付中心和积分中心。那么有读者会说,直接暴露微服务让前端分别调用4个接口再做聚合不是也行吗?显然这种粗暴的方式是极其不合理的,会额外增加广域网网络调用3次不说,还传输了很多不必要的信息。

其他(如参数校验、异常处理等)

应用服务层这一层所负责的工作对业务来说意义重大,也是与其他系统的应用层进行交互的必要渠道。应用层要尽量简单,不包含业务规则或者知识,而只为下一层中的领域对象协调任务,分配工作,使他们互相协作。它没有反应业务情况的状态,但是却可以具有另外一种状态,为用户或者程序显示某个任务的进度。

领域服务层

负责表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节是由基础设施层实现的,但是反应业务情况的状态是由本层控制并且使用的。领域服务层是业务软件的核心。

基础设施层

为上面各层提供通用的技术能力,为应用层传递消息,为领域层提供持久化机制等等。互联网Web应用系统中基础设施包含了数据持久化服务,中间件服务(数据库,Redis,Memcached,ES等等)以及第三方服务等。

各层的设计原则有哪些?

高内聚(复用)低耦合(解耦)

  • 紧密关联的事物应该放在一起,每个服务是针对一个单一职责的业务能力的封装,专注做好一件事情(每次只有一个更改它的理由)。如下图:有四个服务a,b,c,d,但是每个服务职责不单一,a可能在做b的事情,b又在做c的事情,c又同时在做a的事情,通过重新调整,将相关的事物放在一起后,可以减少不必要的服务。
  • 轻量级的通信方式
  • 同步RESTful(GET/PUT/POST…),基于http,让服务间的通信变得标准化并且无状态;后端微服务之间通信可以使用RPC
  • 异步(消息队列/发布订阅)
  • 避免服务之间相互依赖
  • 避免在服务与服务之间共享数据库
  • 微服务架构项目结分层 微服务 分层_高内聚_02

层级调用

1、每一层只能依赖于它下方的层,除领域服务层外,其它层均不建议出现同层内微服务间调用的情况,此外,应尽量避免循环调用或互相调用的产生;
2、禁止跨层级调用;
3、下层向上层发起的通信只能通过中间件等间接方式进行;
4、上层和下层只能有松散耦合(各自为独立个体,通过简单引用关联)。在某些微服务框架比如kratos中,可以把api包提供给上层引用即可。而Spring Cloud的上下层耦合更为松散,通过契约约定即可。前者的优点是调用者可以直接使用提供方定义好的契约和方法。后者的优点则在于最大限度的降低了耦合,避免在上层无限制的进行下层包引入。

举例说明应用服务层和领域服务层之间的关系,有一家上市企业A公司,靠卖水果发家,其首席架构师科学合理的按照DDD搭建了一套基于微服务体系的卖水果应用,其架构图如下::

微服务架构项目结分层 微服务 分层_低耦合_03


今年水果行情一般,而房地产十分火热,A公司高层发现房地产带动的五金行业也十分火热,于是下达任务给技术部,要求其立即着手搭建五金销售系统,货源已经谈好。得益于首席架构师之前优秀的架构设计,他发现只需要做一个卖五金的网站以及另外对微服务进行微量的调整即可满足老板的需求——因为卖五金和卖水果并无本质区别,他们涉及的环节几乎一致。加入五金售卖的系统架构图如下:

微服务架构项目结分层 微服务 分层_微服务架构项目结分层_04


可见应用服务层代表是某一个业务应用,它代表的更多的是从需求出发的应用定义,而领域服务层则是业务领域按照自身的边界进行设计的一个高内聚的服务体。应用层通过协调和组合各个领域服务即可形成一个新的应用服务。《领域驱动设计》中明确指出,在设计领域服务时无需考虑表示层和持久层服务的东西。

领域服务层微服务如何拆分?

根据DDD理论,领域建模主要发生在领域服务层,各领域模块都应该是高内聚低耦合的,具有清晰的业务边界。如何切分领域模块并没有一个明确的规则,不同的场景下可能相同的业务块边界也不尽相同。这里提几点领域划分的个人建议:

  • 领域设计一定要有清晰的功能边界。一个领域服务对应了一个功能集合,这些功能一定是有一些共性的。比如,订单服务,那么创建订单、修改订单、查询订单列表,一般是订单域的功能集合。
  • 一般按垂直业务拆分,一个服务对应独立的库(也可能出现多个服务公用一个库,从设计上通常没有公用表),绝大多数情况下,一个服务和一个数据库搭配,对外提供接口。
  • 领域拆分并不是一步到位的,应当根据实际情况逐步展开。从单体应用到微服务体系的拆分过程能很好的说明这个问题。所以如果一开始不知道应该划分多细,完全可以先粗粒度划分,然后随着需要,初步拆分。比如一个电商一开始索性可以拆分为商品服务和交易服务,一个负责展示商品,一个负责购买支付。随后随着交易服务越来越复杂,就可以逐步的拆分成订单服务和支付服务。
  • 领域拆分并不是一成不变的,应当具体情况具体分析。比如大众点评,其订单服务就拆分为了order-service和order-query-service,一来为了读写分离,二来order-query-service作为单独应用可以按需水平扩容。
  • 领域可以是多个子领域的一个虚拟集合,换句话说多个微服务也可以形成一个大域,不必纠结于领域和微服务之间的数量对应关系。例如在做架构设计的时候可能就把订单域作为一个领域,代表了这个域就是关于订单的,具体该有几个微服务,这需要更细的详细设计来提供。
  • 领域层服务设计应当是调用者无关的。这一点有点像第一点,但是它强调的是领域层服务的设计不应该受调用者的影响,这个观点在《领域驱动设计:软件核心复杂性应对之道》这本书里也可以找得到[4]。领域层服务开发和设计的理念是关注自己的域,一旦边界划分清楚了,开发所需要考虑的永远都只是输入和输出,提供的服务一定是尽可能通用的,面向功能来开发的,而不是面向调用方来开发的。比如某个调用方提出了一个需求:调用方B希望A服务提供一个买汽车的接口,那么A服务设计的接口就应该是buyCar(),而不是buyCarForA()。

Q&A

是否每一层都可以直接访问数据库?

领域服务层承载了数据存储和访问的能力,它与基础设施曾进行数据交换,包括 MySQL、Oracle、Redis、MongoDB、ElasticSearch、PostgreSQL、HBase 等。在应用服务层调用时,它对底层的数据实现方式是无感知的,无论是哪种数据存储方式,以及它是远程数据,还是本地数据,都可以非常容易的调用。换句话说,我们需要将数据的查询和更改操作限制在领域服务层,并只能被应用服务层访问。

领域服务层多个微服务之间是否可以共享数据库?

微服务架构项目结分层 微服务 分层_高内聚_05


严格禁止多个微服务之间共享数据库,理由如下:

  1. 强耦合:为多个服务提供单个数据库的传统设计造成了紧密耦合,如果有多个服务访问同一个数据库,那么任何模式更改都需要在所有服务之间进行协调,这在现实世界中可能会导致部署更改的额外工作和延迟。
  2. 扩展性差:使用这种设计很难扩展单个服务,因为您只能选择扩展整个单块数据库。
  3. 独立部署困难
  4. 性能问题:提高应用程序性能成为一个挑战,使用一个共享数据库,在一段时间内,您最终会得到一个巨大的表。

怎么破?

需要重点考虑对立数据库的拆分,即需要想出一个可靠的策略,将数据库分割为多个与领域服务层微服务对齐的小型数据库。简而言之,您需要将您的领域服务层微服务从使用单一的共享数据库中拆分出来,您应该以这样一种方式设计您的微服务体系结构,即每个单独的领域服务层微服务都有自己的独立数据库和自己的领域数据。这将允许您独立部署和扩展微服务。

微服务架构项目结分层 微服务 分层_低耦合_06


微服务架构项目结分层 微服务 分层_低耦合_07


领域服务层微服务应该遵循领域驱动设计并具有有限的上下文。您需要基于领域来设计微服务,领域与微服务的功能是一致的。这就像遵循代码优先方法而不是数据优先方法一样——因此您首先设计模型。这是一种与传统的在开始处理新需求或新项目时首先设计数据库表的方法完全不同的方法。您应该始终努力保持业务模型的完整性。在设计数据库时,查看应用程序功能并确定它是否需要关系模式。

微服务架构项目结分层 微服务 分层_高内聚_08


数据库应该被视为每个领域服务层微服务的私有数据库。没有其他微服务可以直接修改存储在另一个微服务中的数据库中的数据。在上图中,订单服务不能直接更新定价数据库,只能通过微服务API访问。这有助于您实现不同服务之间的一致性。

微服务架构项目结分层 微服务 分层_低耦合_09


队列的消息可以被视为事件,并且可以遵循发布-子模型。发布者发布消息,而不知道已经订阅了事件流的使用者。体系结构中组件之间的松散耦合可以构建高度可伸缩的分布式系统。

其它

配置类的用配置中心