程序员过关斩将--从未停止过的系统架构设计步伐

首先,这篇文章肯定会得罪一些人

其次,此文只代表我个人的意见,仅供参考

从分层说起

谈到系统架构的分层和系统领域边界的划分,每个架构师,每个技术经理,甚至每个程序员都有自己的一套想法。无论是怎么样的划分方案,总体的目标始终是一致的,打造一个高性能,高可用,高可扩展,高安全性的系统,甚至会附加上一大堆的专业名词,例如:高度一致性,可重用性,幂等性,兼容性 等等。对于最终用户来说,无论系统怎么样架构设计,稳定性是第一位的。假如系统三天两头打不开,报500服务器错误,程序员岂不是天天要被祭天?

从很久之前的面向过程编程模式,到现在的面向对象设计,微服务架构方案,都体现着架构设计一直在追求更加极致的设计之美。而这种美要归功于系统的分层设计,小到类的职责划分,大到系统的分布式部署。

系统为什么一定要做分层呢?至于系统领域的划分,本质上也是一种分层设计的体现思想。

分层是软件工程中一种常见的设计方式,它根据整个系统的职责链把系统逻辑上拆分为多个层,每个层都有相对明确的独立的职责,多个层通过协调提供完整的功能。至于每层负责什么职责,软件工程学并没有明确的定义,系统的设计者可以根据系统特点来具体划分,比如:最常见的三层架构设计,把整个系统划分为:

  • UI层,主要负责用户UI的职责

  • 业务层,主要负责业务逻辑相关职责

  • 数据持久化层,主要负责数据的持久化,落盘操作

image

还有我们耳熟能详的OSI网络模型,它把整个网络划分为了七层,每层都有相对明确的职责,但是还有另外一种划分方式把网络模型划分为四层,这是根据不同职责来划分网络的典型案例。

软件设计采用分层设计算是一种工程学,它把整体系统划分为不同的层,之后采用不同的依赖方式来组织功能,带来了很多优势

  • 每层的职责明确,而且依赖关系明确

  • 每层都可以复用,减少了代码重复率

  • 每层都可以相对独立的做修改,扩展等,不会影响其他层

看到不少技术经理乃至架构师一直鄙视使用三层架构的程序员,我觉得你需要反思一下。最简单的三层架构模式并非优势全无,据我所知,在快速应对中小系统开发的时候,三层架构仍然是首选。不要整天拿着所谓的DDD说事,DDD也不是银弹,简单的三层架构,甚至贫血模型开发模式也有自己的优势,更何况一些人高举DDD的“聚合根”,“值类型”等概念,其实并未真正理解其含义和设计理念,自以为看了几篇DDD的文章,就可以妄自吹嘘自己精通DDD,领域模型开发确实是好的开发理念,但是它也有自己的劣势,不是任何系统用DDD开发都是最优的,更何况那些没有实际DDD开发经验的“高层”。

系统拆分

虽然分层设计优势很明显,但是随着系统业务越来越复杂,就面临着层次划分越来越多的窘态。这也是系统发展的一个必然结果,也是单体应用的必然瓶颈。所以系统按照业务拆分是业务发展到一定阶段的结果,而并非架构师主观意愿的结果。随着子系统越来越多,部署和运维工作也随着越来越多,所以自动化的部署需求也随之出现,为了更好的实现每个系统的可扩展性,稳定性,可用性等软性需求,kubernetes也越来越受到追捧。

其实系统拆分是一个很泛的概念,业界并没有实际的拆分原则,只是大部分人喜欢以业务为维度来进行拆分,实际证明按照业务拆分也是正确的。被拆分的不止是业务逻辑的代码,包括业务的数据库等也会被彻底物理隔离,因为只有这样才可以做到真正的高内聚,低耦合。

image

架构设计

当每个服务都可以根据自身业务量来进行横向扩展或者纵向扩展的时候,就可以体现出微服务的优势。而具体的系统架构设计决定着这个服务是否可以灵活的应对多变的业务需求,说到底,我们又回到怎样设计单体系统的话题上来了。其实设计好单体架构并不比分布式系统容易,一个好的单体系统同样也需要设计模式,数据结构,算法和抽象。前面所说的三层架构是其中一种选择。

系统的设计离不开业务,任何脱离业务的系统架构设计都是耍流氓。设计一个灵活的系统需要对业务的变化点进行正确的识别,然后进行抽象,比如:注册新用户的时候,需要给用户发送短信欢迎语,其实这是一个业务的变化点,因为产品不知道哪天会有一个发送邮件的欢迎语,甚至如果关注了公众号,会发送公众号消息。

三层架构在应对大型系统的时候之所以力不从心,是因为他并不是按照业务的对象进行分层抽象,而现在流行的DDD更加贴近现实世界中的抽象层次,所以DDD在大型系统中更加游刃有余。其中有一种六边形的架构理论值得我们学习。

六边形架构设计

image

六边形架构设计本质上还是一种分层架构设计方案,但是它不同于传统的三层架构,传统的三层架构自上而下逐层依赖,而六边形架构设计采用了内部外部的分层思想,它把业务的领域模型最为最核心的概念,然后扩展出外层的应用层,其实领域模型和应用层就是系统的业务层。

内部通过端口和外部系统通信,端口代表了一定协议,以API呈现。一个端口可能对应多个外部系统,不同的外部系统需要使用不同的适配器,适配器负责对协议进行转换。这样就使得应用程序能够以一致的方式被用户、程序、自动化测试、批处理脚本所驱动,并且,可以在与实际运行的设备和数据库相隔离的情况下开发和测试。

  • 领域层:领域层是业务最核心的概念,包括领域对象的状态,规则,行为等。

  • 应用层:定义了系统可以完成的功能,它通过协调多个领域对象来完成业务逻辑,这一层会包含事务的管理。

  • 输入端口:用于接收外部系统的输入,可以认为它是暴露在外边的接口

  • 输出端口:为系统获取外部服务提供支持,如获取持久化状态、对结果进行持久化,或者发布领域状态的变更通知(如领域事件)。系统作为服务的消费者获取服务是对外的接口(数据库、缓存、消息队列、RPC调用)等都可以看成是输入端口。

六边形理论从一开始就强调把中心放在业务逻辑上,外部的端口存在可变性,可替换性,这是依赖倒置规则的体现。下面就以用户注册业务来模拟一下

首先为核心的用户领域模型,以及模型的行为,其中持久化数据的行为依赖于接口

//用户领域对象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);}}

然后是用户的应用,其中给用户发送欢迎语的行为依赖于接口

 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,接下来只要把各种适配器实现,然后注入(安装)到系统,整个系统就可以运行起来了。

//外部的adapter的实现public class UserDomainRepositoryAdpater: IUserDomainRepositoryAdpater{public int UpdateUserName(int userId, string name){//具体的sql执行过程}}
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的一种,它负责请求业务提供的接口来实现系统业务。

程序员过关斩将--真的可以用版本号的方式来保证MQ消费消息的幂等性?


程序员过关斩将--搞定秒杀,只需要这几步!!


程序员修神之路--分布式系统使用网关到底是好还是坏?


程序员修神之路--它可能是分布式系统中最重要的枢纽

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/306987.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BCVP第2期:项目已完成升级.NET5.0

(是时候拿出来这种图了)1开心的锣鼓想必这两天最热闹的几个词语,就是c#9.0、.net5.0还有conf大会了吧,当然还有大一统。其实,早在2019年年中,就已经引入了.NET5.0了,然后从2020-03-16开始,就一直在说.NET5.…

如何在ASP.NetCore增加文件上传大小

关注架构师高级俱乐部开启架构之路不定期福利发放哦~架构师高级俱乐部读完需要7分钟速读仅需 3 分钟/ 如何在核心中增加文件 ASP.NET 大小 /从ASP.NET 2.0开始最大请求正文大小限制为30MB (28.6 MiB)。在正常情况下,无需增加 HTTP 请求 body …

java完全二叉树最小堆_Java实现最小堆一

Java实现最小堆一堆是一种经过排序的完全二叉树,其中任一非终端节点的数据值均不大于(或不小于)其左孩子和右孩子节点的值。最大堆和最小堆是二叉堆的两种形式。最大堆:根结点的键值是所有堆结点键值中最大者。最小堆:根结点的键值是所有堆结…

一个 Task 不够,又来一个 ValueTask ,真的学懵了!

一:背景 1. 讲故事前几天在项目中用 MemoryStream 的时候意外发现 ReadAsync 方法多了一个返回 ValueTask 的重载,真是日了狗了,一个 Task 已经够学了,又来一个 ValueTask,晕,方法签名如下:publ…

Magicodes.IE 3.0重磅设计畅谈

Magicodes.IE 3.0重磅设计畅谈总体设计图Magicodes.IE导入导出通用库,支持Dto导入导出、模板导出、花式导出以及动态导出,支持Excel、Csv、Word、Pdf和Html。IE在去年年底重构一次之后,经过这么长时间的迭代,又迎来了瓶颈。根据本…

php引用类,thinkphp引用类的使用

比如发送邮件类phpmailer1.将核心文件放入ORG目录下2.在使用的地方,引入这个类文件如何引入呢?import(.ORG.phpmailer);这个表示引入当前项目中的ORG中的phpmailer.class.php文件3.引入之后就可以使用文件中的类了public function sendEmail() {import(.…

Net5 已经来临,让我来送你一个成功

没错,那就是“下载成功”。现在,已经可以急速下载.Net5 docker 镜像 .Net 5 进行今天已经正式发布,想必各位已经通过各种渠道了解到了此次发布的所有内容。并且也都体会到了这次凑成三连的金 scott 是什么效果(啊哈,三…

推荐几款强大流行的BI系统

高级架构师俱乐部 读完需要2分钟速读仅需 1 分钟企业在日常运营过程中,需要根据公司实时经营数据来做未来决测或者发现经营中的问题,在此过程中离不开对数据的分析,而平常利用 excel 等方式极大的提高了领导层快速做出决测的成本&#xff0c…

php 4位数字不足补零,php实现数字不足补0的方法

php实现数字不足补0的方法发布时间:2020-08-28 09:51:06来源:亿速云阅读:100作者:小新这篇文章将为大家详细讲解有关php实现数字不足补0的方法,小编觉得挺实用的,因此分享给大家做个参考,希望大…

起点低,怎么破?

职场&认知洞察 丨 作者 / findyi这是findyi公众号分享的第91篇原创文章洋友问:“洋哥,我北漂多年,专科毕业从农村出来,感觉做什么都不顺,我该怎么办”。和他聊了聊,他毕业后就来北京打工,尝…

C# Span 源码解读和应用实践

一:背景 1. 讲故事这两天工作上太忙没有及时持续的文章产出,和大家说声抱歉,前几天群里一个朋友在问什么时候可以产出 Span 的下一篇,哈哈,这就来啦!读过上一篇的朋友应该都知道 Span 统一了 .NET 程序 栈 …

[C#.NET 拾遗补漏]12:死锁和活锁的发生及避免

多线程编程时,如果涉及同时读写共享数据,就要格外小心。如果共享数据是独占资源,则要对共享数据的读写进行排它访问,最简单的方式就是加锁。锁也不能随便用,否则可能会造成死锁和活锁。本文将通过示例详细讲解死锁和活…

64岁Python之父加入微软 | 谁说大龄程序员无出路

喜欢就关注我们吧!现年 64 岁的 Python 创始人 Guido van Rossum 退休一年后再度复出,今天宣布已加入微软开发者部门 (Developer Division).我觉得退休生活乏味又无趣,因此已加入微软开发者部门。做什么工作?选择太多了&#xff0…

JAVA中的GridView每一个赋值,在ASP.NET 2.0中操作数据之六十二:GridView批量更新数据...

导言:在前面的教程,我们对数据访问层进行扩展以支持数据库事务.数据库事务确保一系列的操作要么都成功,要么都失败。本文我们将注意力转到创建一个批更新数据界面.在本文,我们将创建一个GridView控件,里面的每一行记录…

微软发布VS Code Jupyter插件!不止Python!多语言的Jupyter Notebook支持来了!

北京时间 2020 年 11 月 12 日,微软发布了全新的 VS Code Jupyter 插件!Jupyter 插件将 Jupyter Notebook 的功能引入 VS Code,并且将会支持更多语言和使用场景。Jupyter Notebook 支持创建和共享包含代码、方程式、文本和可视化内容的文档&a…

windows安全模式_鲁大师正式挂牌上市,使用鲁大师如何开启笔记本电脑全面节能模式...

10月10日消息,今天360旗下的鲁大师正式挂牌上市。上市之后,鲁大师的盘中涨幅一度扩大至100%,鲁大师的市值也一度达到了14亿港元。过去三个财年,鲁大师的营业收入分别为6981.2万、1.23亿和3.20亿人民币。简单介绍360,36…

跟我一起学Redis之Redis事务简单了解一下

前言关系数据库中的事务,小伙伴们应该是不陌生了,不管是在开发还是在面试过程中,总有两个问题逃不掉:•说说事务的特性;•事务隔离级别是怎么一回事?事务处理不好,数据就可能不准确,…

groovy 字符串截取最后一个_Python入门高级教程--Python 字符串

Python 字符串字符串是 Python 中最常用的数据类型。我们可以使用引号(或")来创建字符串。创建字符串很简单,只要为变量分配一个值即可。例如:var1 Hello World!var2 "Python Runoob"Python 访问字符串中的值Python 不支持单字符类型&a…

java面试题_阿里大厂流出的数百道 Java 经典面试题

BAT 常问的 Java基础39道常见面试题1.八种基本数据类型的大小,以及他们的封装类2.引用数据类型3.Switch能否用string做参数4.equals与的区别5.自动装箱,常量池6.Object有哪些公用方法7.Java的四种引用,强弱软虚,用到的场景8.Hashc…

​被冷落的运算符重载

基本类型可以使用运算符进行运算、比较、取反等操作。如果想使用运算符操作两个对象,我们就需要用到运算符重载。我们先看个例子,假如有个房子类,有长和宽两个属性。代码如下:接下来我们使用House类实例化两个对象:hou…