码农戏码

新生代农民工的自我修养


  • 首页

  • 归档

  • 标签

  • 关于

  • 在线工具

  • 搜索

架构的本质是业务的正交分解

发表于 2022-12-11
字数统计: 1.3k 字数 | 阅读时长 ≈ 4 分钟

七牛CEO许式伟讲:架构的本质是业务的正交分解。

好独特的见解。

做架构到底是做什么?

在《首席架构师的打怪升级之路
》
中提到:架构师是具备架构能力的人,架构能力是指为相对复杂的场景设计并引导一个或多个研发团队,来实施结构化软件系统的能力。

关键信息:复杂场景结构化

在《软件设计之美》中总结了软件复杂性来自业务复杂性和技术复杂性。应对的办法是通过分而治之,控制规模大小;保持结构清晰与一致性来降低认知负荷。并且要有一定的前瞻性,拥有可扩展性,能应对变化。

关键信息:规模可控、结构清晰、应对变化

总结以上两篇的关键信息:做架构就是通过规模可控、结构清晰的小模块去组合成大模块,进而形成更复杂的软件系统。并且拥有足够的扩展性应对未来的变化。

架构的核心就是【组合与应对变化】;简洁点,三个字:“分、合、变”。

这些其实与“业务的正交分解”方法是一脉相承的。

正交分解

既然是业务的正交分解,自然得理解正交是什么意思?

在《应对变化》详细介绍过正交设计。

简而言之,主要是三个要点:

1、消除重复

2、分离关注点

3、管理依赖:缩小依赖的范围和向稳定的方向依赖

想要把一个复杂的系统拆解成一个一个可被理解掌控、并且又能被结构化地合并成大模块的小模块。正交设计是必须的。

这考验了架构的拆解能力,拆解的合理性就是解耦的合理性;并能在合并时每一个模块保持高内聚。

开闭原则

正交设计主要应对的是“分、合”,那么怎么应对“变”?

就得提到著名的开闭原则。开闭原则是架构治理的根本哲学。

之前也整理了下OCP原则,《SOLID之OCP》,只要我们面向接口编程,就能大概率的符合开闭原则。当时的理解回头看还是比较肤浅的。

一些人对开闭原则的错误解读,认为开闭原则不鼓励修改软件的代码来响应新需求。这显然比较极端。

一个软件产品只要在其生命周期内,就会不断发生变化。变化是一个事实,需要让软件去适应变化。我们应该在设计时尽量适应这些变化,以提高项目的稳定性和灵活性,真正实现“拥抱变化”。

其实,开闭原则的背后,是推崇模块业务的确定性。可以修改模块代码的缺陷,但不要去随意调整模块的业务范畴,增加功能或减小功能都不鼓励。这意味着模块的业务变更是需要极其谨慎的,需要经得起推敲的。

开闭原则指出尽量通过扩展软件实体的行为来应对变化,满足新的需求,而不是通过修改现有代码来完成变化,它是为软件实体的未来事件而制定的对现行开发设计进行约束的一个原则。

总结一下,开闭原则就两点:

1、模块的业务要稳定。当要修改模块业务时,不如实现一个新业务模块。而实现一个新的业务模块来完成新的业务范畴,是一件轻松的事。这个角度,鼓励写“只读”的业务模块,一经设计不可修改。需要变化不如把它归档,放弃掉。这种模块业务只读的思想,是架构治理的基础哲学。

2、模块的业务变化点,简单一点,通过回调函数或者接口开放出去,交给其他业务模块。复杂一点,通过引入插件机制把系统分解为“最小化的核心系统+多个彼此正交的周边系统”。

将开闭原则应用到业务系统。业务对外只读,意味着不可变,但不变的业务生命周期是很短暂的,所以要可扩展。要扩展还要不变,就你倒逼着要做兼容,而兼容可能导致现有的功能职责不单一,这又倒逼着要对现有的功能做再抽象,以适应更广的“单一职责”。

所以不改是不可能的,只是改的结果应当是让项目往更稳定方向发展,而这很难。无论是新的抽象还是职责范围的扩张,需要强大的分析能力和精湛的设计。

这种不变与变其实也印证了架构第一定律:一切都是权衡

DDD工程代码模型的几种包风格

发表于 2022-10-22
字数统计: 2k 字数 | 阅读时长 ≈ 7 分钟

在团队中,一直在灌输DDD的理念,最近review一些新开发的项目时,发现工程包结构每个人的理解都是不一样的,命名也是各有特色。

因此,觉得有必要把之前整理的工程结构重新梳理下。

而在梳理的过程中,恍惚间,有种看山是山、看山不是山、看山还是山的体会。特别有意思。

传统风格

之前的总结DDD分层,每一层都是明确的。

整个工程的包结构就是这样的:

  • interface
  • application
  • domain
  • infrastraction

但是在落地时遇到了很多的问题,在DDD系列文章中也提到过:

1、循环依赖:

domain是依赖于infrastraction,但如repository接口是在domain层的,DDD也是这么定义的,但具体的ORM实现是在infrastraction。因此infrastraction又需要依赖domain。形成循环依赖。

2、domain的厚度

以前都是MVC,贫血模型。所以刚开始时,domain是很薄的,以致于没有存在感。很多service都被application干完了。常有application service与domain service区别的讨论。落地时也常搞混。

依赖倒置

不知道是不是整洁架构,还是洋葱架构之后或之前吧,依赖倒置成了程序员认知的共识。

为了脱离大泥球,人们注意到整体中各个部分的需求变化速率不同,进而通过关注点分离来降低系统复杂度。这是分层架构的起源。

倒置的原因,是因为领域层被赋于最稳定层。

1、展现层

逻辑是最容易改变的,新的交互模式以及不同视觉模板。

2、应用层

随着业务流程以及功能点的变化而改变。如流程重组和优化、新功能点引入,都会改变应用层逻辑。

3、领域层

核心领域概念的提取,只要领域概念和核心逻辑不变,基本是不变的。一旦领域层出现重大改变,就意味着重大业务调整,整个系统都被推倒重来。

4、基础设施层

逻辑由所选择的技术栈决定,更改技术组件、替换所使用的框架,都会改变基础设施层的逻辑。因而基础设施层的变化频率跟所用的技术组件有很大关系。越是核心的组件,变化就越缓慢,比如待定数据库系统后,不太可能频繁更换它,不太可能频繁地更换它。而如果是缓存系统,那么变化的频率会快很多。

但基础设施层可能存在不可预知的突变。历数过往诸多思潮,NoSQL、大数据、云计算等等,都为基础设计层带来过未曾预期的突变。

此外,周围系统生态的演化与变更,也会给基础设施层带来不可预知的突变的可能。比如,所依赖的消息通知系统从短信变成微信,支付方式从网银支付变成移动支付,等等。

整个工程的包结构就是这样的:

  • infrastraction
  • interface
  • application
  • domain

整体包结构是没有变化的,虽然理论是美好的,落地时问题依旧存在。尤其infrastraction与其它三层的不可调和的关系更浓烈了。

从以往感观,其他三层是必须要依赖infrastraction的,结果现在却在最顶层。

其实在之前文章中就提到,controller是在interface还是infrastraction,角度不同,在哪一层都可以。

而像一些基础的,如mq,应用层要发消息,怎么办呢?依赖结构决定了无法使用。

因此有人提出,基础设施层不是层的结论。每一层都是要依赖基础设施的。

菱形架构

经过了一番学习,发现了菱形架构,解决了之前的很多问题。

OHS:

对外主机服务,提供一切入口服务,分为remote和local.

remote:

提供一切对外服务,来源有传统的web,还是MQ的订阅等等。

local:

本地服务,是application的演变,如果远程服务要访问domain,必须通过local才能到达。

domain:

意义不变,就是domain

acl:

是原先infrastraction,但把范围给扩大了。把所有对外部的依赖都纳入其中,甚至repository。

port是表示接口,而adapter表示具体实现。

在《DDD实践指南》中有对菱形架构更详细的介绍。

这样解决了上述两种方案的缺点,理解起来也简单。

但后来还是不太喜欢,为啥,因为传统,传统的DDD理论中,repository是领域层,这儿却在acl中,所以一直在寻找别的方式来解决。

六边形风格

  • inputadapter
  • application
  • domain
  • outputadapter

这也是有相当数量受众的架构风格,类似于菱形风格,从外形理解也简单。

facade风格

  • facade
    • query
    • entity
    • appliation
    • adapter

这是在实践中,演变来的一种风格,对外一切都是facade,受CQRS影响

分为query查询与entity单对象的创建、更新操作;

application刚是业务原语的操作,简单理解为一个业务行为,会操作多个单entity;

adapter刚是封装的infrastraction或第三方接口,提供给外部使用。

混合格斗风格

经过一系列的学习,输出一个融合风格。

  • ohs
    • controller
      • pl
    • openapi
      • pl
    • xxljob
    • subscriber
      • mq
      • event
  • application
    • service
  • domain
    • entity
    • vo
    • aggregate
    • repository
  • acl
    • port
    • adapter
  • persistent
  • foundation
  • infrastraction
    • configuration

依赖关系:

ohs -> application

ohs -> infrastraction

请求入口都在ohs,不然是api,还是队列监听。

像队列底层属于infrastraction,但只面向接口编程,由ohs层实现。

application -> domain

domain -> foundation

application是domain的facade

domain -> acl

虽然可以通过供应商模式,其他层都依赖domain,但还有是会出来一些domain的依赖。放在acl中,供所有层使用。

这样也可以把需要主动调用的内容从infrastraction中剥离开,解决掉了以往提到的循环依赖。

回归传统风格

经过以上一系列的变化,可以说是由简到繁的过程。

再回头看经历过的项目现状,想想每次项目初始化,自己内心的纠结,在团队中也需要宣贯,需要解释,需要深化。

不如来得简单明了些,就使用最经典的DDD风格,只要有一点DDD理论知识,大家都看得明白。不会去问ohs是啥。

interface:有api、dto、assembler三个包,api接受外部请求,有传统的controller,还有rpc,callback,listener来的消息。dto就是传输对象。assembler则是interface->application时,把dto转换成application的command、query。

application: 还是CQRS的思路,分成query、command;还有event,由内部及domain抛出的event。

domain:还是核心概念,entity、vo、aggregate。但没有service,为啥,当有service时,经常会与application service相互干扰,并且会慢慢回到贫血模型。通过强制没有service,可以更加OO。

infrastraction:被拆成不同部分。

基础设施层,不单单是基础设施。得分成两种,一种像是acl,封装第三方接口;另一种像是mq,email等基础设施。

1、我们常见的mq,cache,io,email等等都是基础设施层,domain不是直接依赖他们,而是通过DIP来倒置。表现形式是domain都是接口,而基础设施变成了能力供应商。

2、而依赖的第三方接口,则是直接被domain,application调用。

因此infrastraction被分成两部分,同时解除了循环依赖的困境。

在之前文章中,提到过COLA的持久操作在application,当时很反感,后来感觉好像也对,也是供应商模式的一种体现。

总结

当然,最核心的还是domain的设计,专注修炼OO,没有丰满的domain,一切都是花架子,形似无神。

springboot2.6.x与swagger3兼容问题追踪

发表于 2022-10-12
字数统计: 1.3k 字数 | 阅读时长 ≈ 6 分钟

最近项目中使用了高版本的springboot-2.6.4,以及swagger3

1
2
3
4
5
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

结果启动应用程序失败,报错:

1
Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException

这个问题,网上资料不少,主要原因是因为springboot2.6.x后,把pathMatcher默认值修改了,springfox年久失修,与springboot出现了兼容性问题。

找到一个Spring Boot下的Issue:https://github.com/spring-projects/spring-boot/issues/28794,但这个issue已经关闭了,目前这个问题的主要讨论在springfox,具体issue是这个:https://github.com/springfox/springfox/issues/3462

主要项目中还需要使用springboot-actuator,所以简单的修改一下配置spring.mvc.pathmatch.matching-strategy=ant-path-matcher还不行。可参考:Spring Boot 2.6.x 集成swagger3.0.0报错解决方案,Swagger is not working with Spring Boot 2.6.X

在此问题追踪过程中,第一个就是原先的Ant方式与当前的PathPattern有什么区别:

AntPathMatcher vs PathPattern

诞生时间

AntPathMatcher是一个早在2003年(Spring的第一个版本)就已存在的路径匹配器,

而PathPattern是Spring 5新增的,旨在用于替换掉较为“古老”的AntPathMatcher。

性能

PathPattern性能比AntPathMatcher优秀。

理论上pattern越复杂,PathPattern的优势越明显

功能

1、PathPattern只适用于web环境,AntPathMatcher可用于非web环境。

2、PathPattern去掉了Ant字样,但保持了很好的向下兼容性。

3、除了不支持将 ** 写在path中间之外(以消除歧义),其它的匹配规则从行为上均保持和AntPathMatcher一致

4、并且还新增了强大的{*pathVariable}的支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test() {
System.out.println("======={*pathVariable}语法======");
PathPattern pattern = PathPatternParser.defaultInstance.parse("/api/com/zhuxingsheng/{*pathVariable}");

// 提取匹配到的的变量值
System.out.println("是否匹配:" + pattern.matches(PathContainer.parsePath("/api/com/zhuxingsheng/a/b/c")));
PathPattern.PathMatchInfo pathMatchInfo = pattern.matchAndExtract(PathContainer.parsePath("/api/com/zhuxingsheng/a/b/c"));
System.out.println("匹配到的值情况:" + pathMatchInfo.getUriVariables());
}

======={*pathVariable}语法======
是否匹配:true
匹配到的值情况:{pathVariable=/a/b/c}

在没有PathPattern之前,虽然也可以通过/**来匹配成功,但却无法得到匹配到的值,现在可以了!

5、整体上可认为后者兼容了前者的功能

具体介绍可查看:
《Spring5新宠PathPattern,AntPathMatcher:那我走?》

在源码中也有详细说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
org.springframework.web.util.pattern.PathPattern

? matches one character
* matches zero or more characters within a path segment
** matches zero or more path segments until the end of the path
{spring} matches a path segment and captures it as a variable named "spring"
{spring:[a-z]+} matches the regexp [a-z]+ as a path variable named "spring"
{*spring} matches zero or more path segments until the end of the path and captures it as a variable named "spring"


Note: In contrast to org.springframework.util.AntPathMatcher,

** is supported only at the end of a pattern. For example /pages/{**} is valid but /pages/{**}/details is not. The same applies also to the capturing variant {*spring}.

The aim is to eliminate ambiguity when comparing patterns for specificity

在springboot 2.6后,Spring MVC处理程序映射匹配请求路径的默认策略已从AntPathMatcher更改为PathPatternParser

Actuator端点现在也使用基于 PathPattern 的 URL 匹配。需要注意的是,Actuator端点的路径匹配策略无法通过配置属性进行配置。

如果需要将默认切换回 AntPathMatcher,可以将 spring.mvc.pathmatch.matching-strategy 设置为 ant-path-matcher

1
spring.mvc.pathmatch.matching-strategy=ant-path-matcher

springboot2.6.X与swagger3兼容

为什么改变了下pathmatch方式,就会影响到swagger,没想明白,毕竟swagger的路径,PathPattern也是可以正常解析的。

debug了下代码:

不配置spring.mvc.pathmatch.matching-strategy

应用在启动时,会自动设置PatternPaser

可以看到默认值就是PATH_PATTERN_PARSER,也正是springboot2.6后的默认方式:

不配置时spring.mvc.pathmatch.matching-strategy,pathPatterns是被赋值的:

springfox.documentation.spring.web.WebMvcRequestHandler#getPatternsCondition时,就是null。

这样也就出现了文章开头的兼容问题。

在配置ant-path-matcher后,RequestMappingInfo中的pathPatterns和patterns的赋值变化,pathPatterns是无值,patterns是有值。

1
spring.mvc.pathmatch.matching-strategy=ant-path-matcher

解决方案

解决springboot2.6和swagger冲突的问题这篇文章算是列举方案比较全的。

如果只是通过BeanProcessor修改了HandleMapping,但不修改pathmatch,会访问不了swagger,会出现以下错误:

1
2
3
o.s.web.servlet.PageNotFound             : No mapping for GET /webjars/js/chunk-vendors.90e8ba20.js
o.s.web.servlet.PageNotFound : No mapping for GET /webjars/js/chunk-735c675c.be4e3cfe.js
o.s.web.servlet.PageNotFound : No mapping for GET /webjars/css/app.f802fc13.css

可以追加一下swagger资源的映射,最终出的方案:https://www.jianshu.com/p/1ea987c75073;

在整合actuator时,SpringBoot 2.6.* 整合springfox 3.0报错中也指出了,并且解释了原理。但没有理解作者表达的springfox.documentation.spring.web.WebMvcRequestHandler#getPatternsCondition时为null的过滤掉。

代码里面反而是把为null的提取出来了呀。

springdoc

既然swagger3.0更新不及时,就不用再纠结,直接使用springdoc也是很好的方案。

使用springdoc来替换swagger3.0,《从springfox迁移到springdoc》

总结

虽然解决了问题,但原理尚需追踪。不如用springdoc来得简单些。

架构师如何看待统一语言

发表于 2022-10-07
字数统计: 1.9k 字数 | 阅读时长 ≈ 6 分钟

DDD统一语言

统一语言,最早听到这个概念是在学习DDD的时候。

统一语言在DDD中,是一个很重要的概念。

DDD中的几个大词:问题域,解决域,战略,战术,统一语言,界限上下文

阅读全文 »

JDK8的map与flatmap区别

发表于 2022-09-25
字数统计: 586 字数 | 阅读时长 ≈ 3 分钟

map

map() method -> Data Transformation

map() takes Stream as input and return Stream

Stream map(Stream input){}

1
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

It’s mapper function produces single value for each input value.hence it is also called One-To-One mapping.

这个方法比较好理解,把一个事物映射为另一个事物,是一对一的关系。

在没有stream.map()时,就在使用apache和guava的类似api

apache中的ListUtils

1
public static <E> List<E> transformedList(final List<E> list,final Transformer<? super E, ? extends E> transformer)

guava中的Lists

1
public static <F, T> List<T> transform(List<F> fromList, Function<? super F, ? extends T> function)

flatMap

flatMap() -> map() + Flattering

flatMap() takes Stream<Stream> as input and return Stream

Stream map(Stream<Stream> input){}

1
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

It’s mapper function produces multiple value for each input value.hence it is also called One-To-Many mapping.

flattering

flatMap()其实是两个方法的合并,map()好理解,主要是flattering。

Before Flattening: [[t,u], [v,w,x], [y,x]]

After Flattening: [t,u,v,w,x,y,x]

其实就是把两层数组打平了。

实例

在stackoverflow上找的一个示例:

What’s the difference between map() and flatMap() methods in Java 8?

flatMap helps to flatten a Collection<Collection> into a Collection. In the same way, it will also flatten an Optional<Optional> into Optional.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class Parcel {

String name;
List<String> items;

public Parcel(final String name, final String... items) {
this.name = name;
this.items = Arrays.asList(items);
}

public List<String> getItems() {
return items;
}

public static void main(final String[] args) {
final Parcel amazon = new Parcel("amazon", "Laptop", "Phone");
final Parcel ebay = new Parcel("ebay", "Mouse", "Keyboard");
final List<Parcel> parcels = Arrays.asList(amazon, ebay);

System.out.println("-------- Without flatMap() ---------------------------");
final List<List<String>> mapReturn = parcels.stream()
.map(Parcel::getItems)
.collect(Collectors.toList());
System.out.println("\t collect() returns: " + mapReturn);

System.out.println("\n-------- With flatMap() ------------------------------");
final List<String> flatMapReturn = parcels.stream()
.map(Parcel::getItems)
.flatMap(Collection::stream)
.collect(Collectors.toList());
System.out.println("\t collect() returns: " + flatMapReturn);
}

}

结果输出:

1
2
3
4
5
-------- Without flatMap() ---------------------------
collect() returns: [[Laptop, Phone], [Mouse, Keyboard]]

-------- With flatMap() ------------------------------
collect() returns: [Laptop, Phone, Mouse, Keyboard]

As you can see, with map() only:

  • The intermediate type is Stream<List>
  • The return type is List<List>

and with flatMap():

  • The intermediate type is Stream
  • The return type is List

参考

flatMap() Method in Java 8

做技术还是做管理

发表于 2022-09-12
字数统计: 1.1k 字数 | 阅读时长 ≈ 3 分钟

新晋管理者都有一些相同的烦恼和忧愁。

“管理事太杂了,没时间写代码,越来越虚”

“如何平稳技术与管理?”

“管理事太琐碎,离技术越来越远,未来职业怎么规划?”

“做管理最大的挑战,就是舍弃技术,特别难”

这些问题的本源是因为新晋管理者正在进入一个全新的领域,离开以往的舒适区。

以往的舒适区是技术范围,而且主要是技术实现。接受一个功能需求,通过技术实现出来。

而成为管理后,不再是技术实现,而是任务分配,团队建设,资源协调等等脱离技术的事项。

走出舒适区是相当痛苦的,它会让人产生不安全感,让人容易自我质疑,以及自我否定,进而退回舒适区。

新晋管理者都有类似这种前怕狼后怕虎的焦虑。

前怕狼后怕虎,狼是啥?管理之路不好走,事很杂,心里虚。虎是啥?离技术越来越远,失去了技术优势。

从自身安身立命安全感角度讲,是青黄不接的时候,管理还没有摸出门道,不知道怎么搞定,倍感焦虑;而熟练掌握的技术,却投入的时间越来越少。

这种纠结的状态,归纳为一个词叫:患得患失。还没有得到时,担心得不到;得到后,又担心失去。

阅读全文 »

DDD聚合设计的困境

发表于 2022-08-28
字数统计: 1.9k 字数 | 阅读时长 ≈ 6 分钟

为什么学了一堆DDD理论,但就是无法落地呢?很多人认为它只是个理论。

最近又看了一遍《IDDD》第十章聚合,结合已有的理论知识,来反思下这个问题。

DDD聚合是什么?

最容易与DDD聚合混淆的就是OO聚合关系。

由上图可以看出,OO聚合表示了一种关联关系;而DDD聚合表示了一种边界。

OO聚合关系(Aggregation) 表示一个整体与部分的关系。通常在定义一个整体类后,再去分析这个整体类的组成结构,从而找出一些成员类,该整体类和成员类之间就形成了聚合关系。

如上图中Question与Answer的关系。一个问题与很多的回答构成一个完整的问答关系。

在OO中还有一种比聚合关系更强的关联关系:

组合关系(Composition)也表示类之间整体和部分的关系,但是组合关系中部分和整体具有统一的生存周期。一旦整体对象不存在,部分对象也将不存在,部分对象与整体对象之间具有相同的生命周期。

继续以上图为例,Answer都是因Question存在而存在,当Question消亡时,Answer也自然消亡。

但像知乎,就算Question没了,Answer也会留存。

OO聚合与DDD聚合是什么样的关系呢?

因为聚合有隐含的构建关系和级联生命周期,通常会把OO组合关系构建成DDD聚合,其实组合关系只是聚合的必要条件,而非充分条件。

如果是聚合,那么聚合根与实体自然是组合,存在级联生命周期,但不代表存在级连生命周期都是聚合。

特别是,混淆了数据生命周期和对象生命周期,

例如在“获取客户订单”这一业务场景下,Customer 与 Order 之间也存在整体/部分的组合关系,但它们却不应该放在同一个 DDD 聚合内。

从数据生命周期看,一般如果数据库中顾客数据删除了,那么他对应的订单也会删除。

但不适合建模成聚合。

因为这两个类并没有共同体现一个完整的领域概念;同时,这两个类也不存在不变量的约束关系。

而且聚合后,订单就不再具有独立身份,每次查询订单时候,必须提供客户身份和订单身份,构成级连身份才可以。类似于当仅仅使用订单号查询订单时,是不行的,必须带上身份证和订单号一起,才能查询到订单。

聚合困境

看似把一堆实体和值对象放一起就组成聚合,在《IDDD》中提供了一个示例。使用过JIRA的人应该很容易理解。我简单按书中的思路叙述下。

第一次尝试

这次尝试,应该是完全按产品需求直接建模。

Product,BacklogItem,Release,Sprint的确是一组组合关系。

而且Product是聚合根。

1
2
3
4
5
6
7
8
public class Product {
private Set<BacklogItem> backlogItems;

private Set<Release> releases;

private Set<Sprint> sprints;

}

虽然这完整表达了模型,但实现很残酷。不得不考虑的问题:

1、持久化时,乐观并发,增加了操作失败概率

2、影响系统性能和可伸缩性

第二次尝试

将一个大的Product聚合拆分成了4个相对较小聚合。

虽然解决了事务问题,多个用户请求可以同时创建任何数量的BacklogItem、Release和Sprint实例。

但对客户端来说,这4个较小的聚合却多少会带来一些不便。

设计小聚合

一个完整的聚合

如果要加载一个完整的聚合,需要把所有这些实体与值对象都加载出来。那系统性能和可伸缩性大受影响。

为了解决这些问题,所有提出要设计小聚合。

小聚合不仅有性能和可伸缩性上的好处,它还有助于事务的成功执行,即它可以减少事务提交冲突。这样一来,系统的可用性也得到增强。在你的领域中,迫使你设计大聚合的不变条件约束并不多。当你遇到这样的情况时,可以考虑添加实体或者是集合,但无论如何,我们都应该将聚合设计得尽量小。

聚合之间不能直接加载到同一个边界之内,得通过唯一标识引用其他聚合。

通过标识引用并不意味着完全丧失了对象导航性。有时在聚合中使用Repository来定位其他聚合。这种作法也被称为失联领域模型(Disconnected Domain Model)。

这就是矛盾体,一方面希望保障模型的完整性,我们需要完整的聚合;另一方面又有各种实现限制条件。

这些原因正是《实现业务逻辑的本种方式》中提到的单体架构演变为分层架构的局限性。

总结越来,聚合有三点特性:

1、Global entities(aggregation root and entity) is the boundary of consistency

2、Global entities(aggregation root and entity) is the boundary of lifecycle

3、Object model assumes same lifecycle boundary within the global entity

DDD困境

由聚合的困境,管窥一斑,DDD落地的困境何尝不是类似原因:

1、Domain Driven Design,but technology may have a say

2、Using fundamentalism DDD,it only works for simple cases,so does transaction script,procedure of oriented programming

3、Smaller aggregation works as fine as a EJB2-refurbished architecture

由上面的聚合示例观察,第一点的确是现实。

我们使用DDD,就是想Domain Driven,可考虑了很多技术落地因素,打破了完整的模型。选择模型还是选择性能,是放在我们面前的第一道选择题。

而第二点在现如今多运行时分布式架构中,肯定不可能像在一个单休中加载完整的聚合对象。因此当要使用原味的DDD时,只能在简单的项目中,而DDD却说要在复杂场景下再去使用。不要简单问题复杂化。这又是个矛盾体。

这些问题怎么解决?

当前能想到的解决方案似乎只有在《DDD对象生命周期管理》提到的关联对象模式。既能保证模型的完整,又能兼顾性能。

总结

聚合设计时,尽量使用小聚合。这对吗?解决设计困境了吗?

如果使用小聚合,会造成一种现象。会出现很多的service。

只有使用service,才能在聚合间跨越对象生命周期,维持一致性。

这会慢慢演化成贫血模型,因为一部分逻辑在对象中,另一部分会放到service中。

所以我们得重新审视一些指导原则。或者时时提醒是不是过多的考虑了实现细节,破坏了模型。

怎么才能更好地保证模型完整性,而兼顾当前的技术限制。

技术人的三个阶段

发表于 2022-08-13
字数统计: 1.6k 字数 | 阅读时长 ≈ 5 分钟

技术人员的未来是什么?

我想作为一名技术人,在夜深人静时候,都会想想这个直击灵魂的问题。

作为一名搬砖人,哪里需要哪里搬?

不甘于作为工具人,但出路在何方?

新技术层出不穷,学到何年何月?怎么保持活到老学到老的状态?

在之前的一系列文章中,写过《程序员成长职级》、《最好的职业建议》、《首席架构师的打怪之路》等,其实也都是来源于对上述问题的思考。

怎么突破呢?其实市场也给了部分答案,比如市场要求从几年前的T型人才到π型人才,这也是作为一名技术人,需要三条腿:技术、业务、管理。

尤其业务,对于技术人员的重要性,很多技术人不理解,甚至工作多年的人也不理解。技术只是工具,达到目标的手段。而业务才是最终目标。不懂业务,最后必然会成为工具人。

很多技术人被沦为随时可替代的工具人也不自知,甚至对自身技术自鸣得意,孤芳自赏。

也许会有人反驳,我再深入解释一下。技术人想干什么?技术牛啊,怎么体现技术牛呢?写个数据库,写个操作系统,写个docker,或者写个开源框架等等。

为什么是这些东东呢?因为这些技术通用,与技术紧密关联,但其实这些只是更具技术属性而已,它们对外的统一特征是产品,首先是个产品,其次是个技术属性偏强的产品。

如果你对自己要达到的目标都不了解,再牛的技术也会有种无力感,报国无门空自怨。

回归到主题,技术人员的三个阶段。最近看到陶勇医生著作的《自造》,他讲了学医的三个阶段,我常见得对技术人是通用的。

阅读全文 »

优秀架构师的四个素质

发表于 2022-08-11
字数统计: 1.5k 字数 | 阅读时长 ≈ 5 分钟

架构师,程序员追求的最高title。

那么想成为一名架构师,就会出现一系列的疑问:

什么是架构师?

怎么才能成为架构师?

优秀架构师需要哪些素质?

之前根据《郭东白的架构课》总结了《成为首席架构师的打怪升级之路》。这也是郭东白对架构师的思考之路。

其实在2021年5月28日的Qcon软件大会上郭东白就做了一场主题是《如何成为一名优秀的架构师》演讲。相当精彩。特地找到PPT,学习一下。可惜是没有找到完整的演讲视频。

什么是架构师

架构师:

1)a person engaged in the design of certain large constructions. – dictionary.com

设计人员从事某些大型建筑物设计的人

2)a person who designs and guides a plan or undertaking. – Merriam-Webster

设计和指导计划或事业的人。

在之前的总结文章中,其实都没有给出架构师的定义,只是说比普通程序员更牛的程序员,以前提出了“技术+影响力+领导力”的能力模型。

而郭东白给出了一个更确切的定义:

具备架构能力的人。

架构能力是指为相对复杂的场景设计并引导一个或多个研发团队,来实施结构化软件系统的能力。

兼职架构师与全职架构师

对于任何一家公司,架构设计永远是必要的。

其实在很多公司,架构师职位是有职无岗的,就是有架构师职责的需要,但不会独立设置架构师的岗位。很多时候都是由TL兼任。

架构师的生存

必要条件一:目标正确

一个优秀的架构师应该具备对软件做出正确的设计和决策的基本能力,在做这些决策时往往需要考虑很多限制条件,如:时间成本、人力成本、资金成本、范围要求、质量要求、组织结构等,架构师从来不是独行侠,他是上承需求,下接实现的一种角色。

总结一下就是要有工程化思维。

必要条件二:能力满足

这些能力,从硬实力再到软件实力,一条也不能落下。

能力范围从普通研发到兼职架构师,再到全职架构师,层次递增。

对于能力攻关,是先宽度再深度,还是先深度再宽度?

建议先深度再宽度,这样可以一通百通,没有深度是谈不上宽度的。

架构师的素质

第一是:有眼光,什么叫有眼光?就是要有深度的业务理解,能看到好的机会;

第二是:善于思考,有足够的技术视野能找到正确的技术和组织设计,亦即独立思考能力;

第三是,有良知,这是一个架构师随着时间的流逝,沉淀在身上最重要的品质。什么是有良知?为人正直,选择做正确的事情。很多人是非常聪明的,业务理解能力强,技术实践丰富,但他不一定为公司或为组织做最正确的事情。有良知是非常重要的一个事情,如果架构师没有素质,他会让一家公司的损失很惨重。

第四是:能感召,能感招是什么概念?他要能够引导组织作出正确的决策,大多数架构师不是决策者,但他是决策的建议者,因为他是个决策的建议者,所以他的沟通能力,表达能力可以引导一个组织最终走向正确的选择,这也是架构师必须具备的素质。

这几点,可以对应上前面的能力模型:技术、影响力、领导力。但这四点总结得更完善些

1、眼光其实就是业务,有了业务理解深度,才能知道价值点,进而通过影响力发挥价值

2、思考是根本,不管是对技术,还是业务。这是第一源能力

3、良知是德

4、感召力,对应上领导力,能引导他人一起走向正确的选择

对于思考能力,再补充点,业务理解是什么?什么是业务理解不够?

架构师的价值创造来自于独立、理性的、有深度的思考。从技术视角看业务,从业务中发现技术机会。通过复盘发现思考漏洞,提升思考质量。

讲一个技术人做业务理解,是在技术视角往上看到业务,从技术视角理解业务叫业务理解。

并不是一个做业务的,把业务怎么做说的很详细,没用,要从技术视角看到对业务的理解,同样,技术洞察,从业务视角反过来看技术,找到洞察,这才是要的洞察。

技术做技术的洞察,业务做业务的思考,这两个完全是割裂的。

总结

架构以及架构师,是程序员奋斗的事业和目标。所以虽然已经总结了很多了篇文章,但还是不时从高手那儿不停地吸取和反复思考。这是一个持续过程。现在有,未来还有。

通过不断地思考架构的本源和架构师的价值,成为一名德才兼备的优秀架构师。

基于”如何成为一名优秀的架构师”的分享总结

QCon大会官方PPT下载(源PPT下载)

InfoQ官方视频合辑(可能有视频上传)

程序员得懂点马斯洛理论

发表于 2022-07-23
字数统计: 1.5k 字数 | 阅读时长 ≈ 5 分钟

马斯洛需求层次理论是管理心理学中人际关系理论、群体动力理论、权威理论、需要层次理论、社会测量理论的五大理论支柱之一。

以往对马斯洛需求层次理论的理解很肤浅:人对需求是有层次的,层次间都有依赖关系。当低层次的需求被满足后,才会去考虑更高一层次的需求。

而且好像也没什么用处。知道之后既不能提升硬技能,也不能升职加薪。

马斯洛理论定义

通过《郭东白的架构课》的学习,了解到马斯洛理论的本意是:我们可能同时并行存在着多个需求,这些需求之间并不存在依赖或层次关系。

包含两个重点:

第一点:不是需求有层次,而是动机有优先级

如果这些需求得不到满足,那么它们各自会诱发动机。但动机有优先级,且具备抢占性质。所以任何时候,只有一个动机在主导着整个人的意识和行为。

第二点:动机跃迁模型

人有且只有一个主导动机。这个动机由人的内在需求所驱动,并独占且主导你当前的一切意识和行为,你整个人,包括你的视觉、听觉、嗅觉,你的思考、记忆、行为等。直到这个动机背后的需求被完全满足之后,更高层次的动机才可能进入主导位置。

学习了理论本意后,由于理论抽象度高,需要更具象的事才能更好地理解,经过反复思考,找到了一些日常工作中的常见的场景,可以用为进一步理解此理论的抓手。

指导架构

郭东白讲架构活动需要尊重和顺应人性,架构师必须洞察研发人员的人性,在方案设计和架构活动组织中充分考虑研发的人性,才能保障方案的正确性,以及方案的高效实施。

什么样的架构才算是尊重和顺应人性呢?不得不再次搬出当下两个流行的事物:中台和微服务

中台

对于中台概念,可以温习之前写的中台是什么。定义中台为企业级能力复用平台。

在做企业级业务架构时,做出来的模型,需要考虑两点:

1、凡是公用的部分,应该照顾到所有利益相关方的需求;

2、凡是已实现的功能都应该对新的需求方开放并支持必要的扩展。

部门利益是做企业级的最大障碍,跨越这个障碍是对业务架构师设计能力的最高挑战。

中台建设虽然需要兼顾各方的利益,但更多主要还是解决企业管理层对于公司长期生存与可持续发展的恐惧与焦虑问题

中台建设的这两个考虑点,似乎是郭东白架构课程中重要价值输出的浓缩版本。

前半段结合马斯洛理论,中台的建设通常都会伴随企业内的组织重构以及利益和职责的再分配,中台必然会触碰到前端业务团队利益,这自然威胁到了业务团队。

尤其很多公司建设中台是跟风式的,本身就没有明确的战略意图,相当于架构活动没有正确目标,甚至没有目标,更没有清晰的构建路径,冒然简单粗暴地下沉前端业务功能,让原本根错综复杂的前端业务团队缺失了安全感,按马斯洛理论,业务团队满足安全需求的动机占据主导位置,可以想象,整个中台的推进速度。

后半段则是任何企业活动的目标,都是为企业带来长期生存的优势。管理层、架构师都得考虑当下的活动是否真的带来生存优势。生存优势我简单理解为降本增效,ROI越高越有优势。

微服务

之前谈到微服务常会提到《康威定律与反康威定律》,随着近些年互联网行业的不景色,从以前的拆掉中台到现在的《拆掉微服务》。

康威定律有个常用示例:如果有四个小组合作开发一个编译器,那么你将得到一款具有四个步骤的编辑器。

结合马斯洛理论,每个小组都有了自己的一亩三分地,也就有了归属感和安全感,从人性角度,团队会更有活力、进取心和责任感。

技术管理

作为TL,在《码而优则仕》中提出三件重要事情。尤其激活团队。

TL如何激励团队?

都说现在00后是来整顿职场的,所以不能再像过去,员工以生存为目的,无条件接受资本主义压榨。

现在生活条件大大改善,对应马斯洛最底层需求已经完全满足。管理者怎么办呢?会通过内部激励机制,如晋升职级;和红线制度,明确惩罚事项。

有没有更高阶的方式方法呢?脱离上述的靠外部驱动。

马斯洛理论给出指导方针:让员工有更多的归属和尊重。激发他们内部需求。

总结

一个看似无用的理论,经过高人的讲解,可以感知到原来这个理论贯穿在每日生活的细节中。

马斯洛理论就是这样,不管是做架构还是做管理,只要有人参与的活动,都渗透着它的威力。

123…16
朱兴生

朱兴生

151 日志
3 分类
53 标签
© 2016 — 2023 朱兴生 | Site words total count: 345k
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4
沪ICP备18040647号-1