微服务架构大成以来,业界持续有声音在质疑微服务架构的弊病,时不时的有曝出某某大厂回归了单体架构。今年(即2023年)5月份,又曝出亚马逊一团队抛弃微服务架构回归单体架构后,成本降低90%,引发业内热议。正于此时Spring Modulith更新到了0.6
版本。
这个孵化于Moduliths项目并且现已纳入Spring Boot 体系的框架的愿景是要将单体架构的框架水平拉升到微服务架构Spring Cloud的层次,让架构师们在做架构选择的时候,能基于真正需求去考量,而不是基于框架先进性。
抛开微服务的优点先不说,我所在的公司也在成本上苦微服务架构久矣……因此,我抽空通读了官方的简介、文档和相关示例代码,以期能盼来解药,结果只能说还差点意思……
大家怀念单体架构主要是因为单体架构开发方便,部署方便,然后性能高,但是带来的缺点是事实上的紧耦合,无论再好的规范和设计,也都会随着成员的增多、时间的流逝、紧急需求的响应,导致代码纠缠不清,一团乱麻;而微服务架构带来的是松耦合,独立发布,水平扩展,但是会导致部署架构复杂,基础设施成本、运维成本飙升……
Spring Modulith的解法是用单体架构,通过规范、显式化包与包之间的调用和依赖关系,从而降低单体架构的耦合程度。提供的方法是两种:
package-info.java
,并使用@ApplicationModule
指明能被谁调用"order" ) package example.inventory; .springframework.modulith.ApplicationModule( allowedDependencies = |
ApplicationEventPublisher
组件,子包与子包之间通过发布事件、响应事件来完成一次系统。这个过程种可以通过数据库去做事件日志的持久化,再配合事件状态检查和重传机制确保事务最终一致性。// 这是发布事件的核心代码,位于Order Package中 |
在一个企业组织里,科技部门不免需要回答科技投入到底产出了什么,科技投产效能是高是低,要回答这个问题不是一个简单的事情。特别是当CEO有这个疑问的时候,通常意味着他对科技的投入产出已经产生了怀疑,定性的产出描述,“形成了XXXX技术能力,提升了XXXX技术水平”,都无法消除他的怀疑。
在一家科技企业里,科技投入通常是在做三方面工作:(一)科技产品的研发;(二)核心技术能力的建设;(三)基础的IT支撑。
科技产品研发投入带来的直接产出是可供销售的软件系统或者支撑业务运营的软件平台。这两部分可直接与市场收入挂钩。但当市场销售不佳,收入较少时,是否就意味着科技产品的研发产出较差呢?我觉得在投资人的角度来看,这个结论是正确的,但是在公司治理中去看却不竟然。因为只要研发部门按照产品设计部门的设计和时点完成了产品研发,那就代表着科技部门履行了职责,后续市场表现不佳,通常是产品设计或销售环节出了问题。
因此我觉得衡量科技产品的研发产出应复合收入和需求实现情况来评价。收入要考虑滞后性,将后续一定时段内的收入框算进来。
产品研发效能 = (研发相关收入+需求实现率 X 系数)/产品研发投入
当然,在现实中,产品、研发、销售总是会互相甩锅,销售会认为卖的不好是产品没有竞争力,产品会说是研发出来的产品质量差、体验不好。因为科技部门需要在版本交付时,让产品部门做好验收,功能、性能、用户体验,并形成一份验收确认单留存。
为了让企业或产品更有技术竞争力,根据企业所处的行业,通常要去投入一定的研发成本去研发尖端技术,形成企业的核心技术能力。核心技术是可以在多项产品中得到应用,但由于核心技术的挑战性,可能会失败,也可能不是立即能赋能产品或业务,就像隐私计算技术之于数据要素产业。
核心技术的投入是企业的战略决策。但在决策时,科技部门为了获得高层的认可,通常是着重强调了可能的收益,弱化了风险和周期。这也就加大了论述核心技术能力投产效能的难度。
核心技术能力的产出有2个方面,一个是对业务/产品的支撑,一个是专利/论文。对产品的支撑可以使用关联收入规模或者业务规模来体现。
核心技术的研发效能=(收入规模 X 系数+业务规模 X 系数+专利论文数量 X 系数)/核心技术研发投入
鉴于核心技术投入的不确定性,科技条线应该推动企业建立核心技术项目的投入管理或退出机制,这样能使得核心技术的投入是受控状态,也能说明核心技术投入的合理性。一旦缺乏这个机制,就很难说明持续投入的合理性,比较容易引发疑虑。
企业信息化、运维、安全等投入都是为了企业的正常运行,是最基础的IT支撑。这些投入前期是为了实现业务流程的稳定线上化运行,后期是为了不断的提升企业经营效率,降低企业风险。这三方面投入均可以分摊给公司全员,因此可以用人均投入来衡量IT支撑的效能。
IT支撑效能=IT支撑投入/公司人数
安全是个无底洞,可以投入很小,也可以投入很大,一定要适可而止。
通过上面的方式,将三类效能量化以后,问题的关键就来到如何去选择基准值比较,从而得出效能是高还是低的结论。自己与自己的历史去做比较,能说明效能的变化,但关键还是得找到一个同业去做参照。从公开的数据中,能找到上市企业的研发费用、人数等信息,但很难得到打开的更多的信息。只能通过研发费用率(研发费用/营业收入),去说明一些笼统的情况。
科技投入对于科技公司来说,就跟一个人去学本领一样,是必不可少的,但这个人最终能在劳动市场上换来多少报酬,从统计学上来说,肯定跟学本领的投入有关联,但具体的个体,这个投入产出的效能,其实是无法追溯衡量的,因为涉及因素太多。
一个科技部门当被要求量化评价研发效能的时候,就已经是处于比较被动的局面了。因此,还是要在日常的科技制度上做好决策机制和后评估机制,对于每个项目的投入和最终的产出都做到有结论有共识。
另外,在组织架构上,需要匹配企业的阶段去定位科技,是作为能力还是资源。在业务模式探索期,科技应该作为资源,与前端深度绑定;在业务模式稳定期,一部分科技才能作为能力,去追求规模化的效率。
公司网络不支持IPv6,Drive Client使用6690 TCP端口,没有IPv4地址,通过QC连接速度太慢。刚好有台有公网IPv4的云主机,简单的使用NPS实现了TCP隧道转发,记录一下步骤,供参考。
docker pull ffdfgdfg/nps |
nps.conf
文件http_proxy_port = 7001 域名代理https代理监听端口,不用可以随便填一个 |
服务器地址:服务端客户端通信端口
,填入唯一验证密钥服务端地址:第六步配置的服务端端口
即可最近看几家HIT厂商在说自己遵循了openEHR标准规范去做了一些应用,包括个人健康档案、集成平台、临床数据仓库、电子病历等等,不一而足。那些厂商到底是应用openEHR解决了一些现实问题呢,还是在整宣传噱头呢?我基于自己的理解,谈一谈openEHR到底适合干什么。
EHR是电子健康档案的英文缩写,openEHR原本就是一套开源的电子健康档案规范,它是发源于欧洲的GEHR(Good Europe Health Record),最开始的目的是支撑欧洲健康远程信息通讯。上个世纪末,有人基于GEHR的研究成果,提出了openEHR的概念,期望建立一个开源的电子健康档案规范。后来专门的openEHR机构成立了,openEHR机构的主要是目标是通过研究临床需求、建立标准与实施软件,实现开放的、支持互操作的健康处理平台,以加快促进高质量的EHR的发展。经过了十几年的发展,到了2010年后openEHR被引入了中国,
openEHR最核心的概念就是RM
\ AM
,以及EHR
、COMPOSITION
、SECTION
、ENTRY
四类对象。
先讲EHR
、COMPOSITION
、SECTION
、ENTRY
。这些概念是源于GEHR项目的。COMPOSITION
可以理解为document,多个document组成一份EHR
。COMPOSITION
本身可以包含若干个content,这些content可以是SECTION
也是是ENTRY
。SECTION
可以包含若干个ENTRY
或者SECTION
。
RM
是参考模型,是技术层面的定义,比如基础包的定义,支持的数据类型的定义,支持的数据结构的定义,安全信息定义,EHR
、COMPOSITION
、SECTION
、ENTRY
四类对象的关系等等。ENTRY
是最小的实体,ENTRY
下面包含的信息大体分为两类:诊疗类和管理类。诊疗类还可以细分为观察、评估、只是、执行四类业务活动信息。所以RM
是纯技术的,不带具体医疗业务的。
AM
是原型模型,是业务层面的定义,可以直接理解成一个个领域模型。每个openEHR的原型定义了一种临床概念,比如处方、血压、诊断等等,原型模型定义该原型需要存储的数据内容和形式(也就是前面RM
支持的)。原型是使用ADL定义的。目前openEHR已经积累了大概500个左右的原型库可直接引用。
这种RM
和AM
的设计,就是openEHR机构一直强调的将知识与技术剥离的分层方法。让业务专家去设计原型,让技术人员根据参考模型去开发软件工具。然后最终通过选取不同的原型来组合起来(openEHR称之为模板)支撑应用。
正如在前面阐述的一样,openEHR它本质上是用领域模型的方法去表达医疗业务。按照业务领域的信息聚合必然使得其对医疗业务人员友好。我理解“处方”,不用去查看,就知道处方这个原型下,必然有什么。如果我对整个医疗业务都熟悉,那么我很容易在整个体系内找到我需要的信息。因为整个过程只取决于业务理解,而非技术设计或工程实现。
当openEHR作为一个公开的标准时,那么它就成为一个大家都可以理解的信息定义,在交换传输中就省略了定义声明这一步,简化了对接的步骤。但是,我们得看到目前openEHR的原型大多来自于西方,而欧洲、澳大利亚等的医疗体系与我国还是有很大差别的,所以原型库中的原型肯定有不适用的地方,因此想做到不声明即互知这一步,还需要形成中国的原型库。
openEHR的持久化存储有很多种方式,一种是按照参考模型对象关系来存储数据,另一种是按照属性/路径来存储数据(这种是openEHR机构推荐的),还可以按照原型关系来存储数据。但无论这三种方法的哪一种,在面临数据量级稍微大一点的复杂条件关联的批量查询,必然性能较低——这是受限于openEHR的特性——当它将技术实现与业务知识分离时,当它按照一个个领域实体固化了对象时,就已经埋下了因。至于国内还有人建议通过模板定义以周期性的频率来执行ETL
过程在openEHR存储之上生成多维数据集市,只能算时治标,可能在某些场景下可行,但改变不了这个现实。
所以总的来说,openEHR适合的场景有:
openEHR不适合的场景有:
最近一年工作发生很大变化,从技术、研发管理领域转战到了更偏向技术管理、产品管理领域,视角发生了很大变化,也有了很多感悟,正好很久没写文章了,于是总结总结,聊聊在软件企业里如何去做好研发管理、技术管理、产品管理。
产品管理不同于产品经理做的事。产品经理的工作是致力于打造出一款好的软件产品。产品管理是更高层次的事情,是在企业层面去对所有的产品进行管理,是同时对这家企业的所有产品负责。
做好产品的管理最本质的是要参透在产品到底是什么,产品管理到底要去管什么。有意思的是从企业视角去看产品,与产品经理眼中看到的产品并不一致。之前有产品经理过来诉苦,说我的产品多么好多么牛,结果公司高层却不认可,最后还被砍了,CEO真是SB。其实出现这种现象的原因就是因为他不理解在企业看来产品是什么。
产品首先是商品。产品被创造出来的目的就是为了销售。所以产品的好坏比较关键的点在于他是否卖的好,而并不在于他是否优美、先进。不可否认的是现实中,特别在 ToB行业很多卖的好的产品并不先进。
产品是以为公司取得收入为目的。产品既然是商品,那么它生来就背负这个使命。产品一定要取得收入,可以是短期收入,也可以是未来收入,可以是直接收入,也可以是间接收入。无论如何,产品得有清晰得产生收入的设计和论证。
产品是标准化的。在一定时间范围内,产品是可以重复销售、重复交付的。一款产品在交付中需要定制开发的东西越多,产品化程度就越低。当产品化程度低到一定值的时候,产品的销售交付成本与定制开发的成本(主要是人力)相当,此时产品就失去了规模化的优势,利润率就有限了。很多软件企业挣的就是在合同交付中复用成品带来的成本差值。如果没有标准化的优势,除非能有其他优势形成溢价,否则大概率不是一门好生意了。
产品是有智力投入的。通过渠道优势,采购软件硬件然后转手再卖,这种情形下软件硬件就不能称之为产品,因为其中没有凝结着公司自有的技术成果,此时不需要以产品的方式去做管理,更多的是以商品的形式去管理,这个时候考虑的是怎么拿到更低的价格,怎么降低库存,怎么提升周转率。
软件企业的产品不一定是软件。产品可以是软件,可以是服务,可以是咨询报告。一切满足上面四个要素的都可以称之为产品。
因此,在我看来:产品是以满足客户需求、取得收入为目的,可重复的标准化的交付的,凝结着公司技术成果的一种有形或无形的商品。
除此之外,还要看到企业战略。先有企业再有产品,产品是需要符合企业战略规划的。
太多的产品经理一心扑在精益产品上,看不到产品是在企业战略布局下的一款商品这个基本事实,太多着眼在用户体验,用户痛点,用户价值等等,而忘了这是一门生意。
那么企业级的产品管理应该管什么呢?
从企业的视角看,产品管理实质上是投资管理。在产品研发的过程中,企业的资产形式通常从货币资产转化成了软件资产。如果理解了产品管理就是投资管理,那么产品管理该怎么管就很明显了。
投资前,要去看这个机会是否是一个好机会,要不要投这笔钱。那么就要去问:
投资后,就要去看这笔钱花的怎么样,钱花了多少对应这些钱的效果出来了吗?定期的去做后评估,产品投产符合预期吗?外部环境因素是否发生了较大的改变,要不要追加投资,还是要及时止损。
在针对产品的管理之外,还要不断的调整公司的产品体系,打通部门协作流程,以使得公司的制度和治理能满足产品商业模式的要求。
另外还有一个重点就是产品规划,承接企业战略。
一是明确企业应该在各个发展阶段形成怎样的产品,要在公司层面形成什么样的产品组合;
二是明确每个产品的定位,比如是立足现在承担收入的,还是着眼未来打造核心竞争力的,是负责引流获客的,还是主要的利润来源。
全公司的产品应该形成一张精心设计的、逻辑严密的整体布局。产品打造的过程就是在完成一个个填空题。这种规划是总是处于信息不足状态,是在与未知的勇敢博弈,是不断的滚动试错,但也是不可或缺的,因为它对具体产品的发展给与宏观上的引领。
产品管理可深可浅,这取决于当前所处的局面,在不同的局面下处理方式不同,管理的关注点也不同。比如说是需要快速打开局面找到产品方向,还是要对产品进行收敛聚焦资源。如果是前者,那么就要专注于控制风险,避免重投资,在这个前提下,给予灵活度,要更快速的放行,更频繁的检视,鼓励向外“探索”。如果是后者,那么就要专注于价值收益,避免资源分散,果断关停并转,要更多的关注建立核心竞争力,鼓励向内“创新”。
小部分的产品是设计出来的,大部分的产品是孵化出来的。产品管理要注重如何去建立产品孵化机制,定制化的交付内容在何种情形下需要沉淀为产品。
产品管理还需要注重产品经理的培养,一方面做好至少而下的宣贯,让产品经理在大方向上不偏离,另一方面做好横向拉通,让分散在各个团队或部门的产品,能实现模块复用或业务协同,这需要产品经理互相了解。
Domain-driven design并不是一个新鲜的东西,从 2004 年Eric Evans提出这个理念以来,十几年间时不时就有一股DDD的热潮,近几年随着微服务的大行其道,DDD又成了宠儿。但终究逃不过雷声大雨点小。
领域驱动设计最根本的思路是一套自上而下的设计方法,即从要解决的问题领域或系统目标出发,由业务和架构专家一起,对复杂的业务场景和关联性进行分组归类,形成一个一个相对单一职责的子领域。子领域和子领域之间要有明确的界限。在子领域内部,再通过业务上的模型去归类对应的属性和行为。
从设计思想上看,我非常赞同自上而下的架构设计理念,也在不断践行。但今天在技术圈里流传的DDD早已超出了上述的定义,而变成了一套以充血模型为基础,围绕聚合根,并通过门面代理、分离查询、事件传播等手段的软件架构方法。通过这种构建,想达成三种目的:
service
层的复杂度我所不采纳的即是这样一种“术”,有三个主要原因。
SSE,全称Server-Sent Events,作为一种半双工的前后端通信方式,由于实现方式简单、轻量,在后端向前端的主动推送场景中具备很好的应用效果,笔者最近在一个项目中多有使用,过程中也是查阅了不少资料和文档,也有所感悟,在此将整体做一次综述。这是一篇系列文章,共分三篇,这是第三篇。
Spring WebFlux framework 5.2.0基于Reactive Streams api,使用事件循环计算模型来实现异步Java Web应用程序。这样的应用程序可以运行在非阻塞的web服务器上,如Netty 4.1和Undertow 1.4,以及Servlet 3.1+容器,如Tomcat8.5和Jetty 9.3。
在WebFlux中实现发送事件,需要以下几步:
创建一个controller
类并用@RestController
注释标记它
创建一个接受Http GET
请求的方法,该方法返回一个Flux对象,并配置produces=text/event-stream
|
如果只发送data
,直接返回Flux(Object)
就可以了,如果是发送带有id
,event
,retry
等字段,那么就要使用构造器来构建一个ServerSentEvent
对象,并放到Flux
里。比如:Flux<ServerSentEvent<T>>
短暂的去发送事件是比较简单的,只需要使用Flux.just()
将消息列表里的消息一条条发送出去即可。
|
长期的发送事件在发送本身上是没有区别,主要是需要一个周期性线程定期处理发送事务,这里直接使用Flux.inteval()
来轮询。
|
非周期性事件可以通过Spring的事件监听接口来实现,关键点在于要把监听消息的处理器和Flux的构造结合起来。
这里面核心的方法是Flux.create()
。他接受两个参数,一个是FluxSink
对象,一个是Flux的溢出策略枚举值。下面是Flux.create()
的一个简单例子,便于理解。
Flux.create(sink -> { |
Flux.create()
:
我们需要做的就是不断的将监听到的事件填充到sink.next()
中去。示例代码如下:
|
上面的代码中使用了org.springframework.messagin
包,他可以通过spring-boot-starter-integration
引入。关于这个包的一些基础知识,可以参阅这里。
只适合发送文本消息;尽管可以使用Base64编码和gzip压缩来发送二进制消息,但效率可能很低。
早期的一些浏览器,如Internet Explorer不支持。
Internet Explorer/Edge和许多移动浏览器不支持SSE;尽管可以使用polyfills,但它们可能效率低下
SSE,全称Server-Sent Events,作为一种半双工的前后端通信方式,由于实现方式简单、轻量,在后端向前端的主动推送场景中具备很好的应用效果,笔者最近在一个项目中多有使用,过程中也是查阅了不少资料和文档,也有所感悟,在此将整体做一次综述。这是一篇系列文章,共分三篇,这是第二篇。
Spring WebMVC Framework 5.2.0基于Servlet 3.1,需要中间件能支持Servlet 3.1 API,比如**Tomcat 8.5 **和 Jetty 9.3。
在WebMVC中实现发送事件,需要以下几步:
创建一个controller
类并用@RestController
注释标记它
创建一个接受Http GET
请求的方法,该方法返回一个SseEmitter对象
在另一个线程中,获取这个SseEmitter实例,并调用SseEmitter.send()
发送事件。
调用SseEmitter.complete()
正常关闭一个SSE连接,调用SseEmitter.completeWithError()
关闭连接的同时向前台抛出一个错误。
|
如果只发送data
,调用SseEmitter.send()
即可,如果是向发送带有id
,event
,retry
等字段,那么就要使用构造器来构建一个事件对象: SseEmitter.send(SseEmitter.SseEventBuilder builder)
如果要实现广播,可以将emitter
对象保存到一个线程安全的列表中,然后遍历发送。
class SseEmitters { |
短暂的去发送事件是比较简单的,只需要去启动一个线程执行发送即可,发完关闭连接。
|
长期的发送事件在发送本身上是没有区别,主要是需要一个周期性线程定期处理发送事务。
|
非周期性事件可以通过Spring的事件监听接口来实现,即在事件监听器里调用**send()**方法。下面是简化写法,事件监听器可以参阅这里。
|
SSE,全称Server-Sent Events,作为一种半双工的前后端通信方式,由于实现方式简单、轻量,在后端向前端的主动推送场景中具备很好的应用效果,笔者最近在一个项目中多有使用,过程中也是查阅了不少资料和文档,也有所感悟,在此将整体做一次综述。这是一篇系列文章,共分三篇,这是第一篇。
在性能可接受的web应用程序中,没有简单、通用的方法来实现服务端到客户端的异步通信。
HTTP是B/S中的一种请求-响应协议。客户端(通常是浏览器)向服务端提交请求,服务端向客户端返回一个响应。服务端只能向发出请求的客户端发送响应。在HTTP协议中,客户端是消息交换的发起者。
在某些情况下,需要服务端成为消息交换的发起方。实现这一点的方法之一是允许服务端将消息推送到支持发布/订阅模式的客户端中。客户端从服务端订阅消息,服务端向许多订阅的客户端发送消息(一旦消息可用)。要停止交换,客户端需要取消订阅。
SSE(服务器发送事件)就是一种实现上述场景的技术。
有几种技术允许客户端从服务端接收有关异步更新的消息。它们可以分为两类:客户端拉取和服务端推送。
在客户端拉取技术中,客户端通过短轮询或者长轮询定期请求服务器进行更新。
客户端定期向服务端发送请求。如果服务端有更新,它会向客户端发送响应并关闭连接。如果服务端没有更新,它会向客户端发送一个特殊响应,并关闭连接。
客户端向服务端发送请求。如果服务端有更新,它会向客户端发送响应并关闭连接。如果服务端没有更新,它会保持连接,直到更新可用为止。当更新可用时,服务端向客户端发送响应并关闭连接。如果更新在一段时间内不可用,服务端将向客户端发送一个特殊响应,并关闭连接。
在服务端推送技术中,服务端在消息可用后立即主动地向客户端发送消息。其中,有两种类型的服务端推送:SSE和WebSocket。
SSE是一种仅发送文本消息的技术。SSE基于HTTP协议中的持久连接。SSE是HTML5标准协议中的一部分。
WebSocket是一种在web应用中实现同步、双向、实时通信的技术。WebSocket基于HTTP以外的协议(ws协议),因此它可能需要额外的网络基础设施设置(代理服务器、NAT、防火墙等)。然而,WebSocket可以提供使用基于HTTP的技术难以实现的性能。
去订阅一个服务端推送事件,客户端需要发起一个GET
请求,并设置如下的请求头:
Accept: text/event-stream
指明MediaType是事件流Cache-Control: no-cache
不要对事件进行缓存Connection: keep-alive
长连接GET /sse |
服务端需要提供包含以下响应头的response
:
Content-Type: text/event-stream;charset=UTF-8
告诉客户端响应是一个事件流Transfer-Encoding: chunked
告诉客户端内容大小未知,为流传输200 |
建立连接之后,服务端会在消息可用时立即发送消息。事件是UTF-8编码的文本消息。事件与事件之间由两个换行符分隔\n \n
。每个事件由一个或多个name:value
字段组成,用一个换行符分隔\n
。数据主要在data
字段中传输,一个标准的事件格式如下:
id:1 |
在一次事件消息发送过程中,可以有多个data行,比如:
data:第一个事件的第一部分信息 |
id
event
data
是比较常见的字段,除此之外,还常用到的是retry
字段。在retry
字段中,服务端告知客户端超时重连的时间间隔(单位是毫秒)。如果连接断开,浏览器会在达到超时时间后重新连接。如果未指定此字段,默认超时重连的时间是3秒。
另外一个常见的字段是:value
,即只有value
没有name
。它可以用来服务端向客户端发送注释,也可以用来保持连接,以防长连接超时断开。
: heartbeat |
为了建立一个SSE
连接,客户端应该创建一个EventSource
对象。
var eventSource = new EventSource('/sse'); |
也可以在建立连接时传递参数给服务端。
var eventSource = new EventSource('/sse?event=type1'); |
关闭一个SSE
连接
eventSource.close(); |
连接有三种状态:
EventSource.CONNECTING = 0
正在连接EventSource.OPEN = 1
连接已建立EventSource.CLOSED = 2
连接已关闭eventSource
的常用事件有以下几种:
//连接建立事件 |
对于消息的处理,一般会通过增加事件监听来进行。可以针对不同的event.type
设置不同的处理逻辑。
eventSource.addEventListener('DELETE_SONG', function (event) { |
EventSource
对象已经被绝大部分现代浏览器支持,具体支持的浏览器,可以翻阅Can I Use网站。
Spring 5支持的WebFlux对于全局异常的捕捉与以前的WebMVC框架有了不同,不能靠@ControllerAdvice
或@RestControllerAdvice
打天下了。目前国内的资料比较少,而且语焉不详,下面我将处理方式分享出来,文末有我的项目代码供参考。
核心要点是要继承AbstractErrorWebExceptionHandler
这个WebFlux下的全局异常处理类,然后重写getRoutingFunction
。
需要注意的是AbstractErrorWebExceptionHandler
中的构造方法中未对messageWriters
进行初始化,源码如下:
private List<HttpMessageWriter<?>> messageWriters = Collections.emptyList(); |
messageWriters
是用于最终生成response body
的,所以我们必须在实现类中对它进行初始化:
public GlobalErrorWebExceptionHandler(ErrorAttributes errorAttributes, |
getRoutingFunction
中可以通过RouterFunctions.route()
设置Response的内容。
|
RouterFunctions.route()
接受两个参数,第一个参数是RequestPredicate
类,第二个参数是一个方法。我们通过第一个参数去匹配要处理的Request
,通过第二个参数去实现异常处理逻辑,比如重写Response
。这个方法的返回值必须是ServerResponse
对象。
另外AbstractErrorWebExceptionHandler
类中还定义了一个getErrorAttributes
方法,通过这个方法我们能获得异常的一些详细信息,比如异常类型,异常的消息描述,异常的TRACK信息等。这样有助于我们对异常做针对性的处理。
Map<String, Object> errorPropertiesMap = getErrorAttributes(request, |
最后还需要注意对这个自定义的全局异常处理类需要定义它的优先级,至少要小于*-1,因为Spring 5中默认实现的DefaultErrorWebExceptionHandle
的优先级就是-1*。在自定义全局异常处理类的时候,大可以借鉴默认处理类的写法。
以上。具体实例可以参考我的GitHub上的完整代码:点击访问。
]]>在基于WebFlux和Netty的应用框架中,惯常通过http://ip:port/h2-console
访问的H2控制台无法打开。这个时候,可以通过显式的去启动H2 Server来解决此问题。参考代码如下:
|
参考文献:Here
]]>GitHub和CodingNet上已经创建好空仓库tools,拟将本机的项目代码同时提交到两个仓库
git init |
经过一番折腾之后,还是觉得老老实实的写Dockerfile更香,Spring Boot 2.3.1对layer jars
的支持与2.3.0有所变化,网上的资料大部分都是过时了的,正确步骤如下:
pom里开启layer jars
支持
<build> |
编写Dockerfile,下面是一个简单的例子
FROM adoptopenjdk:8-jre-hotspot as builder |
最后编译即可。
docker build . -t tomu:v0.0.1 |
Spring Boot 2.3发布后带来了新特性之一就是对构建镜像的便捷支持,声称不用写dockerfile
就能方便的构建docker image
,最近刚好在写一个项目于是折腾了一下,只能说还不太适合国内用户,最终还是老老实实的写了需要挂上代理才能勉强可用,下文记录折腾的过程。dockerfile
尝试了诸如gcr.io
的国内镜像、docker tag
更改image的名字为原版gcr.io/
等其他几种办法之后,发现只有挂全局代理才可以走通。
全局代理设置好,一切都OK了,如果走代理下载镜像比较慢,可以先把镜像从国内镜像源里拉到本地。然后再改Tag的名字,这样能跳过下载镜像的环节。
docker pull registry.cn-hangzhou.aliyuncs.com/lefer/paketo-buildpacks:0.3 |
build-image现在的机制是即使本地已经有了对应的image,每次它还要访问一次gcr.io
,这样就要求每次构建的时候都要挂全局代理……好吧,只能等着以后的更新解决这些在中国大陆的可用性问题吧。
按照官方文档的指引,Spring Boot V2.3.0 M默认就包含了自动构建相关的插件,只需要在项目根目录执行下面一行命令即可:
./mvnw spring-boot:build-image |
第一次执行这个命令有点久,应该是在下载一些必要资源。满怀期待的等待了一会,结果编译失败,发现日志报错无法拉取一个builder image
Pulling builder image gcr.io/paketo-buildpacks/builder:base-platform-api-0.3 |
尝试了一下,这个gcr.io
下的镜像的确无法拉取到,即使给maven和docker以及spring-boot-maven-plugin都挂上代理也不成。
maven proxy config
<proxies> |
docker proxy config
因为我是docker desktop,只需要在docker desktop的GUI里配置proxy即可。
spring-boot-maven-plugin proxy config
<project> |
没辙,然后想了一个取巧的办法,就是通过阿里云的镜像仓库支持海外构建的特性将base-platform-api-0.3
拉到阿里云镜像仓库里去,这样就能绕过网络不通的问题。
于是在GitHub里建了一个仓库,里面只需要放一个Dockerfile,直接引用gcr.io
的image即可。
FROM gcr.io/paketo-buildpacks/builder:base-platform-api-0.3 |
然后在阿里云的镜像仓库里配置从这个代码仓库构建镜像。构建完成的镜像可以通过命令拉取。
docker pull registry.cn-hangzhou.aliyuncs.com/lefer/paketo-buildpacks:0.3 |
然后告诉spring-boot-maven-plugin插件,这个builder镜像要从阿里云里拉取,配置如下:
<build> |
再重新执行./mvnw spring-boot:build-image
果然顺畅的通过了这一步,还没待高兴,又报出无法拉取另一个镜像:gcr.io/paketo-buildpacks/run:base-cnb
,研究了一番发现这个镜像还没办法通过配置项指明使用一个第三方的镜像替代,只好洗洗睡吧。
有谁解决了这个问题,麻烦留言告诉我…(lll¬ω¬)
]]>ToMu 是 Together Music 的缩写。ToMu是基于网络开放的音乐资源基础上,让一对听众一起听歌的平台。ToMu的初衷是找回在随身听时代,两个人共用一副耳机一边散步一边听歌的感觉。
GitHub:**ToMu**
频道页
这本书是我看过的架构领域的书中最好的一本,“神书”,作者比较系统的阐述了自己对架构的理解、对好的架构的理解、以及对如何做出好的架构设计的理解。作者本人在软件领域从业几十年,从他的口中听到的一些架构原则更透彻,能让你看到当时的来龙去脉。
依赖倒置(DIP)本质上是一种解耦,解耦的目的是实现组件化的独立部署和独立开发能力。PS:顺便说一句,最早提出DIP原则的就是这本书的作者罗伯特老爷子。
所有的竞争问题、死锁问题、并发更新问题都是由可变变量导致的。如果变量永远不会被更改,那就不可能产生竞争或并发更新问题。如果锁状态是不可变的,那就永远不会产生死锁问题。所以架构师要做好可变性的隔离,区分出可变组件和不可变组件,尽可能的将处理逻辑归于不可变组件。
软件构建中层结构的主要目标是:第一、使软件可容忍被改动;第二、使软件更容易被理解;第三、构建可在多个系统中复用的组件。
SRP
是康威定律的一个推论,即软件应与组织息息相关,每一个软件模块应该只对某一类行为者负责。单一职责原则放到组件层面就是共同闭包,即我们应该将会同时修改,会以同样目的而被修改的类整合成一个组件。
OCP
是指当新增功能时可以通过增加新代码而非修改旧代码来实现。
软件复用的最小粒度应该等同于软件发布的最小粒度。PS:有些研发组织以系统的形式发布软件,却期望各个系统去共享使用通用的功能,最后只能是一团糟。
不要依赖不需要用到的东西,换言之,在设计组件时,应考虑这些类是被共同复用的吗?PS:难
不要出现依赖环,否则会影响开发效率。依赖环的消除可以使用依赖倒置来解决。
组件是不可能从上到下被设计出来,因为组件体现的不仅是业务功能而是构建性、维护性、依赖关系的地图。PS:很反直觉,有待体验
不稳地的组件可以依赖稳定的组件,而不能让稳定的组件依赖不稳定的组件。一个组件的抽象化程度应该与其稳定性保持一致,就是说稳定的应该抽象,否则无法满足开闭原则。不稳定的应该包含实现代码,这样才方便修改。
软件架构的终极目标是最大化程序员的生产力,同时最小化系统的总运营成本。
一个系统在水平分层上比较常见的方式是:UI界面、应用独有的业务逻辑、领域普适的业务逻辑、数据库。
划分边界的目的是方便我们尽量将一些决策延后进行。
计算机程序从本质上说就是一组仔细描述如何将输入转化为输出的策略语句的集合。软件架构设计的工作重点之一就是将这些策略彼此分里,然后将他们按照变更的方式进行重新分组。变更原因、时间和层次相同的策略应该被分到同一个组件中。
一种策略距离系统的I/O越远,它所属的层次就越高。高层次的策略不应该依赖于低层次的策略。PS:有道理
业务逻辑是与技术无关的,是现实中某一项业务的一部分。业务实体就是业务逻辑与业务数据的组合。
架构设计的核心目标:满足用例,并能将它们与周边的因素隔离。
整洁架构:越内层层次越高。外层圆代表的是机制,内层圆代表的是策略。源码中的依赖关系必须只指向同心圆的内层,即由低层机制指向高层策略。PS:这就是整本书的核心了,也是罗伯特老爷子的核心主张。
在这个框架中,业务实体应该是最不容易发生变动的。
每个系统架构的边界处,都应该多使用谦卑对象模式,即只发挥自己的桥梁和通信作用,并不从中干预信息的传输。
中文书名:架构整洁之道 |
烂书一本,整本书就是云徙科技的广告吧。书的内容组织混乱,欠缺开创性的见解,东拼西凑,不值得一读。
中文书名:中台战略:中台建设与数字商业 |
为了自己听歌方便,我一直在自己的网站上用aplayer
挂着自己喜爱的音乐,但因为涉及音乐资源版权,心里始终有点不踏实,所以想着能不能直接把网易云歌单集成进来。网易云提供了iframe的方式内嵌歌单,但是太丑了,还是得自己写一个。在搜索网易云的API的时候,发现竟然有人已经写好了:MetingJS,用得也是aplayer
,给了他一个大大的Star之后,记录下用法。
引入js
<!-- require APlayer --> |
播放器代码
<div> |
server
:指明是网易云音乐还是QQ音乐type
:指明是歌单还是一首歌id
:歌单的ID或者歌的IDautopaly
:自动播放list-max-height
:playlist的最大高度MetingJS是显示歌词的,如果不喜欢歌词,可以注释掉JS的第81行。
// lrc: this.meta.lrc || this.meta.lyric || '', |
整本书有点标题党,是想蹭《修改代码的艺术》的热度吧。整本书实质上是作者在敏捷和TDD实践过程中的一些心得。不少观点也很有启发意义,还是值得一读的。
许多bug都是只在集成阶段才会出现,所以越早集成就越能发现风险。(敏捷的理由)
软件与建筑的区别是盖房子不会想着将来会怎么再加装一层,而软件必须要面向扩展去构建。
过期的注释比没有注释更加糟糕,这会使得代码成为谎言。
注释应该去描述代码的缘由而非解释代码的行为,代码的行为应该是自解释的。
繁复的流程会让各个环节对立,各个环节的对立会引来不信任,不信任会导致更多的流程。复杂的流程并不能帮助软件项目成功。
软件开发过程本质上是一个创造过程,流程无法支配创造力。
掌握一门编程语言并不能使你成为软件开发者,正如掌握一门自然语言并不能使你成为作家。
PS:这个数据作者没有注明来源,但符合我的经验。
PS:这7个指标让人大开眼界
PS:看的我大汗淋漓
PS:值得尝试
PS:作者的意思是先去实现,然后回过头来再来考虑设计,然后再来重构。有一定道理
中文书名:修改软件的艺术 |
在 Win10上的 docker desktop上去启用K8s,阿里云团队给出了详细的指南:Docker Desktop for Mac/Windows 开启 Kubernetes。在实操中,有两点需要做调整。
load_images.ps1
脚本时,因为安全策略被阻塞时,需要将安全策略调整到Set-ExecutionPolicy Unrestricted
,脚本执行完毕后,记得将安全策略调整回Default
级别。kubectl apply -f xxxx.yaml
时,需要把yaml下载到本地,在执行。可能是因为raw.githubusercontent.com
域名访问问题导致。