“首先,这篇文章肯定会得罪一些人
“其次,此文只代表我个人的意见,仅供参考
从分层说起
谈到系统架构的分层和系统领域边界的划分,每个架构师,每个技术经理,甚至每个程序员都有自己的一套想法。无论是怎么样的划分方案,总体的目标始终是一致的,打造一个高性能,高可用,高可扩展,高安全性的系统,甚至会附加上一大堆的专业名词,例如:高度一致性,可重用性,幂等性,兼容性 等等。对于最终用户来说,无论系统怎么样架构设计,稳定性是第一位的。假如系统三天两头打不开,报500服务器错误,程序员岂不是天天要被祭天?
从很久之前的面向过程编程模式,到现在的面向对象设计,微服务架构方案,都体现着架构设计一直在追求更加极致的设计之美。而这种美要归功于系统的分层设计,小到类的职责划分,大到系统的分布式部署。
系统为什么一定要做分层呢?至于系统领域的划分,本质上也是一种分层设计的体现思想。
分层是软件工程中一种常见的设计方式,它根据整个系统的职责链把系统逻辑上拆分为多个层,每个层都有相对明确的独立的职责,多个层通过协调提供完整的功能。至于每层负责什么职责,软件工程学并没有明确的定义,系统的设计者可以根据系统特点来具体划分,比如:最常见的三层架构设计,把整个系统划分为:
- UI层,主要负责用户UI的职责
- 业务层,主要负责业务逻辑相关职责
- 数据持久化层,主要负责数据的持久化,落盘操作
还有我们耳熟能详的OSI网络模型,它把整个网络划分为了七层,每层都有相对明确的职责,但是还有另外一种划分方式把网络模型划分为四层,这是根据不同职责来划分网络的典型案例。
软件设计采用分层设计算是一种工程学,它把整体系统划分为不同的层,之后采用不同的依赖方式来组织功能,带来了很多优势
- 每层的职责明确,而且依赖关系明确
- 每层都可以复用,减少了代码重复率
- 每层都可以相对独立的做修改,扩展等,不会影响其他层
看到不少技术经理乃至架构师一直鄙视使用三层架构的程序员,我觉得你需要反思一下。最简单的三层架构模式并非优势全无,据我所知,在快速应对中小系统开发的时候,三层架构仍然是首选。不要整天拿着所谓的DDD说事,DDD也不是银弹,简单的三层架构,甚至贫血模型开发模式也有自己的优势,更何况一些人高举DDD的“聚合根”,“值类型”等概念,其实并未真正理解其含义和设计理念,自以为看了几篇DDD的文章,就可以妄自吹嘘自己精通DDD,领域模型开发确实是好的开发理念,但是它也有自己的劣势,不是任何系统用DDD开发都是最优的,更何况那些没有实际DDD开发经验的“高层”。
系统拆分
虽然分层设计优势很明显,但是随着系统业务越来越复杂,就面临着层次划分越来越多的窘态。这也是系统发展的一个必然结果,也是单体应用的必然瓶颈。所以系统按照业务拆分是业务发展到一定阶段的结果,而并非架构师主观意愿的结果。随着子系统越来越多,部署和运维工作也随着越来越多,所以自动化的部署需求也随之出现,为了更好的实现每个系统的可扩展性,稳定性,可用性等软性需求,kubernetes也越来越受到追捧。
其实系统拆分是一个很泛的概念,业界并没有实际的拆分原则,只是大部分人喜欢以业务为维度来进行拆分,实际证明按照业务拆分也是正确的。被拆分的不止是业务逻辑的代码,包括业务的数据库等也会被彻底物理隔离,因为只有这样才可以做到真正的高内聚,低耦合。
架构设计
当每个服务都可以根据自身业务量来进行横向扩展或者纵向扩展的时候,就可以体现出微服务的优势。而具体的系统架构设计决定着这个服务是否可以灵活的应对多变的业务需求,说到底,我们又回到怎样设计单体系统的话题上来了。其实设计好单体架构并不比分布式系统容易,一个好的单体系统同样也需要设计模式,数据结构,算法和抽象。前面所说的三层架构是其中一种选择。
系统的设计离不开业务,任何脱离业务的系统架构设计都是耍流氓。设计一个灵活的系统需要对业务的变化点进行正确的识别,然后进行抽象,比如:注册新用户的时候,需要给用户发送短信欢迎语,其实这是一个业务的变化点,因为产品不知道哪天会有一个发送邮件的欢迎语,甚至如果关注了公众号,会发送公众号消息。
三层架构在应对大型系统的时候之所以力不从心,是因为他并不是按照业务的对象进行分层抽象,而现在流行的DDD更加贴近现实世界中的抽象层次,所以DDD在大型系统中更加游刃有余。其中有一种六边形的架构理论值得我们学习。
六边形架构设计
六边形架构设计本质上还是一种分层架构设计方案,但是它不同于传统的三层架构,传统的三层架构自上而下逐层依赖,而六边形架构设计采用了内部外部的分层思想,它把业务的领域模型最为最核心的概念,然后扩展出外层的应用层,其实领域模型和应用层就是系统的业务层。
内部通过端口和外部系统通信,端口代表了一定协议,以API呈现。一个端口可能对应多个外部系统,不同的外部系统需要使用不同的适配器,适配器负责对协议进行转换。这样就使得应用程序能够以一致的方式被用户、程序、自动化测试、批处理脚本所驱动,并且,可以在与实际运行的设备和数据库相隔离的情况下开发和测试。
- 领域层:领域层是业务最核心的概念,包括领域对象的状态,规则,行为等。
- 应用层:定义了系统可以完成的功能,它通过协调多个领域对象来完成业务逻辑,这一层会包含事务的管理。
- 输入端口:用于接收外部系统的输入,可以认为它是暴露在外边的接口
- 输出端口:为系统获取外部服务提供支持,如获取持久化状态、对结果进行持久化,或者发布领域状态的变更通知(如领域事件)。系统作为服务的消费者获取服务是对外的接口(数据库、缓存、消息队列、RPC调用)等都可以看成是输入端口。
六边形理论从一开始就强调把中心放在业务逻辑上,外部的端口存在可变性,可替换性,这是依赖倒置规则的体现。下面就以用户注册业务来模拟一下
首先为核心的用户领域模型,以及模型的行为,其中持久化数据的行为依赖于接口
代码语言:javascript复制//用户领域对象
public class UserDomain
{
public int UserId { get; set; }
public string UserName { get; set; }
//注册新用户行为
public int UpdateName(string name)
{
IUserDomainRepositoryAdpater resAdapter = ioc方式获取注入的实例/或者其他渠道;
return resAdapter.UpdateUserName(this.UserId,name);
}
}
然后是用户的应用,其中给用户发送欢迎语的行为依赖于接口
代码语言:javascript复制 public class UserApplication
{
public int UpdateuserName(int userId,string name)
{
UserDomain user = new UserDomain() { UserId = userId, UserName = name };
user.UpdateName("新用户名");
ISendMessageAdpater sendMessage = ioc获取实例;
sendMessage.SendMessage(userId.ToString(),"新用户注册");
}
}
//发送消息的适配器
public interface ISendMessageAdpater
{
//给用户发送消息
void SendMessage(string user,string content);
}
到此为止,用户核心业务已经编写完毕,可以看到,其中业务的变化点都是依赖于接口,对应到六边形理论中,对应的就是各种adapter,接下来只要把各种适配器实现,然后注入(安装)到系统,整个系统就可以运行起来了。
代码语言:javascript复制//外部的adapter的实现
public class UserDomainRepositoryAdpater: IUserDomainRepositoryAdpater
{
public int UpdateUserName(int userId, string name)
{
//具体的sql执行过程
}
}
代码语言:javascript复制public class SendEmailAdpater: ISendMessageAdpater
{
//给用户发送邮件
public void SendMessage(string user, string content)
{
}
}
public class SendPhoneCodeAdpater : ISendMessageAdpater
{
//给用户发送短信
public void SendMessage(string user, string content)
{
}
}
不难看出,六边形理论其实是抽象业务模型 面型接口编程的有效组合,当然真正的项目中还有可能涉及到很多设计模式相关的设计理念。对应到平时开发中,mvc的controller层已然变成六边形的输入adapter的一种,它负责请求业务提供的接口来实现系统业务。