PS:2022年是非常奔溃的一年,因为种种事情,直到5月份才开始进入阅读,后面有各种打断,标记红的的只能放到2023年了。
持续时间:2022-05-02 — 2022-06-15
链接:https://book.douban.com/subject/27044219/
印象最深刻:绝对当之无愧排名第一的入门GO语言书籍,非常不建议在网上董东抄西摘零散的学习GO语言的基础,看完这本书基本能做到覆盖写GO
程序的方方面面,从语言的语法,goroutine机制,并发锁机制等从应用的方面讲解了GO语言的基础知识。
持续时间:2022-06-15 — 2022-07-19
链接:https://book.douban.com/subject/35635836/
在线阅读:https://draveness.me/golang/
记录:https://draveness.me/golang/docs/part2-foundation/ch04-basic/golang-interface/
持续时间:2022-12-14 — 2022-12-27
链接:https://book.douban.com/subject/35144587/
https://book.douban.com/subject/26612779/
完结:2022-07-10 — 2022-08-08
印象最深刻:如果Redis用的比较多的话可能这本书的内容基本上在工作中都能碰到,对于业务开发来说,很多Redis问题已经沉淀到运维层解决了,但是了解Redis的部署架构和Redis的数据类型也是我们平时工作中不可缺少的
https://book.douban.com
请假申请人填写请假单提交审批,根据请假人身份,请假类型,和请假天数进行请假审批规则校验,再根据审批规则逐级递交上级领导审批,批核通过则完成审批,批核不通过则退回申请人。
另外,根据考勤规则,核销请假数据后,对考勤数据进行系统分析,输出考勤统计。
战略设计阶段需要有领域专家,业务需求方,产品经理,架构师,项目经理,开发经理,和测试经理一同参与,主要采用事件风暴的方式进行。
产品愿景是对软件产品进行顶层价值设计,对目标用户,核心价值,差异化竞争等信息达成一致,避免产品设计和建设偏离方向。
最终形成的产品愿景图,有了这张图我们可以知道我们这个产品的定位。
在完成产品愿景分析,明确产品建设方向和目标后,我们就可以针对产品的具体业务场景开始场景分析了。场景分析是从用户视角出发,探索业务领域中的典型场景。这里列举两个用户故事
用户故事名称:请假
参与者:请假人
主要功能:
1)请假人登陆系统,从权限服务那获取请假人信息和权限信息,完成登陆认证。
2)创建请假单,打开请假页面,选择请假类型(事假,病假,婚假等)和起始时间,录入请假信息,创建并保存请假单,提交请假审批。
3)提交审批,获取审批规则,根据审批规则(事假+1上司审批,婚假+2上级审批规则等),从人员组织关系中获取审批人,给请假单分配审批人。
用户故事名称:审批
参与者:审批人
主要功能:
1)审批人登陆系统,从权限服务那获取请假人信息和权限信息,完成登陆认证。
2)获取请假单,获取审批人名下待办请假单,选择一个请假单。
3)审批,填写审批意见。
4)逐级审批,如果还需要上级审批,根据审批规则,从人员组织关系中获取审批人,给请假单分配审批人。如果还有上级审批人,重复这个步骤。
5)最后全部审批人完成审批
其实,你不得不承认,文字描述很难让全部的参与者达成一致,所以用户故事最好的呈现方式是采用图文的形式进行阐述。
用户故事分析快速帮我们达成一致,让所有角色都知道我们接下来要做的事情,但是这里并不会过度关系细节,这里的分析不能转成战术分析。
我们会召集到全部的领域专家,产品团队,架构师,对产品进行一次事件风暴,事件风暴的过程参考:http://blog.leanote.com/post/weibo-007/DDD系列-限界上下文
领域事件
从我们和领域专家的进一步深入沟通中,我们可以发现
按照上一回合,我们使用事件风暴的方式,把我们一个复杂领域的微服务划分方案解决了。但这个时候,如果要你写代码,那肯定不可能,因为这个东西距离代码实现还有很长的路。那我们如何落地代码实现,这就战术设计需要解决的问题。战术设计负责最后的落地阶段。
我们用划分出来的界限上下文(不好理解的话可以理解成微服务)将用户故事连起来,这一步我们把限界上下文当成黑盒,确认它们之间是如何协作的,但是我们不关心限界上下文内部的情况。
这里借用一下尤达老师自动售卖机的例子,围绕自动售卖机为例子梳理出来的限界上下文,自动售卖机系统的界限上下文如下:
然后我们借助架构师,PM,领域专家梳理的用户故事,用限界上下文交互的方式将整个用户故事串联起来。
我们这里只用到一个用户扫码在自助售卖机上扫码支付的例子来串联一下。注意,有的时候我们可以只针对业务的几个关键用户故事进行串联,串联的时候需要把真正的研发人员也召集到一起进行讨论了,因为这里距离研发已经很近了
值得一提的是,有的同学可能会问,这里为什么没有用户上下文,注意,这只是用户故事之一,如果列举全的话,比如涉及免密支付的场景,就肯定会出现用户上下文。包括运营上下文,如果涉及商品补货的用户故事,肯定会出现运营上下文。
注意:这张图必须是多个限界上下文负责,包括即将分配的研发人员一起讨论,因为这里已经可以看到很多开发关注的东西了
1)图中连线表示是接口操作,箭头表示自己,或者对方必须要提供的接口协议,比如设备上下文的负责人必须提供一个弹出商品的接口
2)据我的经验,一般业务会有5-8个核心流程,这些核心流程都需要串联一遍,方便我们再检验一遍我们划分出来的限界上下文是否合理
如果经过充分讨论,没问题的话,这一步基本把限界上下文的职责界定清楚了,其实相当于限定了微服务功能点。比如支付下文的功能描述
1)对外:对业务方提供创建支付订单接口,创建成功返回支付链接
2)对外:提供支付结果回调,支付结果会异步回调业务方
3)内部:调用微信开放平台接口,获取微信支付code
4)内部:接受微信支付回调结果,变更支付订单内部状态
所以接下来,再深入到每个限界上下文内部,如何实现这些功能。
这一步开始充斥大量的细节,好比我们平时用微信开放平台进行支付,微信开放平台暴露给我们的是非常简单的接口。但是其内部的实现,我们做技术的应该不难猜
《架构整洁之道》里面有一句话定义业务逻辑
严格来讲,业务逻辑就是程序中那些真正用于挣钱/省钱的业务逻辑与过程。更严格得讲,不管这些业务逻辑是计算机上实现的还是人工实现的,他们在省钱/赚钱上的作用是一致的
软件开发通常被应用到真实世界中已经存在的自动化流程。从一开始,我们就必须明白软件脱胎于领域,并且跟领域密切相关。在我们开发软件的时候,当然也可以直接坐下来敲代码,但仅限于在开发价值不大的软件时。
为了创建一个复杂的软件,你必须知道这个软件究竟是什么,在你充分了解金融业务是什么之前,你是做不出一个好的银行业软件系统的,你必须理解银行业的领域。
没有丰富的领域知识能做出复杂的银行业业务软件吗,没门!那谁了解银行业业务呢?软件架构师吗?不,他只是用银行来存储自己的金钱,软件开发人员?别为难他了。只有银行业务人员才是这个领域的专家,他们知道所有的细节,所有的困难,以及所有的规章。这些就是我们永远的起始点:领域
模型是软件设计中最基础的部分,我们对领域的所有思考过程被汇总到这个模型中,我们需要就这个模型跟领域专家就进行交流,跟资深的设计人员进行交流,跟开发人员进行交流,模型是软件的根本。要表现这种模型,常见的方式是将模型图形化:图,用例等。我们需要用模型交流。
软件设计有不同的方法,其中之一是瀑布设计法。这种方法包含了一些阶段,业务专家提出一堆需求同业务分析人员进行交流,分析人员基于那些需求来创建模型并将结果传递给开发人员,开发人员根据他们收到的内容开始编码。瀑布设计法中知识只有单一的流向。这种传统方法虽然应用很多年,但是还是有一定的缺陷和局限。主要是业务专家得不到分析人员的反馈信息,分析人员也得不到开发人员的反馈信息。
另一个方法是敏捷方法学,这些方法不同于瀑布方法的一堆动作,产生背景是预先很难确定所有的需求,特别是需求经常变化的情况。敏捷开发试图解决另一个问题被称为"分析瘫痪",团队成员会因为害怕做出任何设计决定而无济于事。敏捷开发者反对预先设计,相反,他们应用大量的灵活设计。敏捷开发也有自己的不足,
敏捷开发缺乏真实可见的设计原则,由开发人员执行持续重构会导致代码难以理解或者难以变更。瀑布流会过度工程,过度工程可能会带来另一种担心:害怕做出深度,彻底的设计。
本书介绍领域驱动设计原则,这些原则会增进对领域内复杂问题进行
这里要吐槽一下目前网站上的各种不说人话系列的文章,基本上全是书本上抄过来的,根本不是正常人能理解的,DDD难怪会变成玄学,读者在搜读文章的时候几乎是奔溃的状态。
为了好理解,也可能有局限性,但为了防止被喷,这里声明只是个人理解,不保证正确性。
以我们熟悉的电商领域为例:电商领域的商品概念,必须要配合一定的语境,比如商品在销售阶段,是我们理解的商品的含义;当商品进入了运输阶段,就变成了"货物";现在流行假一赔十,当进入保险阶段,那商品又是另一个概念。可见,同样一件商品,由于业务领域边界的不同,这些通用语言的术语就有了不同的含义。
上下文=边界,这即是业务领域的边界,也是微服务边界。
假设我们面对一个庞大的系统,或者我们要从庞大的系统解放出来,我们首先想到的就是微服务,但是这里有一个难点,如何合理拆分微服务?我们就冲这个问题去解释。我们把限界上下文拆开看,限界表示划分,划分上下文,上下文表示名词。
1)我们拆分出来的一个个微服务就是一个个上下文
a)支付微服务 = 支付上下文
b)交易微服务 = 交易上下文
c)风控微服务 = 风控上下文
2)上下文映射就是微服务直接调用方式,或者说调用协议
a)假设你们部门很强势,那你的微服务永远不变,让别人适配你的协议,微信支付协议就长这样,你爱用不用
b)假设你们部门很弱势,你要调用别人的微服务,那你加防腐层(适配器)去调用它的服务,你要去适配微信支付协议
3)关于微服务划分合理性,我们可以用事件风暴法来指导我们拆分合理性
所以当我们需要拆分一个庞大系统的时候,首先用事件风暴把微服务合理划分出来,然后确定微服务调用关系和协议。
事件风暴,首先得定义什么是事件,然后围绕事件进行头脑风暴。
领域事件是领域中发生的任何领域专家感兴趣的事情。这些事件我们从用户故事中就可以提取出来,比如我们用电影票务系统来举例,这里就会产生以下事件
1)当售票员标记座位后,产生一个座位已标记事件
2)当用户确认座位后,产生一个座位已确认事件
3)当售票员收到用户的确认之后,产生一个座位已预约事件
4)可能后续还有支付动作,会产生座位已支付事件
值得一提的是:领域事件不是技术概念,而是作为通用语言的一部分和领域专家,运营人员,产品团队达成一致。这一点很重要,很多技术团队内部有各种Event事件,然后这些Event事件和产
领域划分是以分离关注点为原则对问题空间的划分。领域划分必然会划分成很多个子域。
子域是领域中某个方面的问题和解决它所涉及的一切。
其实我们面对复杂问题的时候,都会采用这种方式,纵观我们整个社会,有各行各业,有餐饮业,保险业,房地产业,然后每个领域解决自己领域内的问题,最后连接成一个高效运作的社会形态。
同样,我们做互联网如果要达到高效运作的效果,就必须做到各司其职,做细做精。但是这里有一个难题,如果做领域划分,就跟社会上不会平白无故出现一个保险业一样,它肯定有社会普遍的问题,最终人们抽象成了一个保险业领域来解决保险相关的所有问题。
其实很多业务初期的时候,都是没有领域划分的,基本是按照需求来。接下来我们看下,如果这样长期演变下去会有什么问题。笔者是做医疗的,还是用那个医疗的用户故事来讲解。
我们假设梳理出来了下面几个顶层用户故事
1)收集用户的病情
2)用户购买订单
3)指派订单给医生
4)医生接单
5)医生和患者IM对话
6)医生开具处方
7)用户购买处方药
假设在一个项目压力比较大,而研发leader又没有做太多思考的情况下,很容易将这6个顶层用户故事派发给6个研发同学,也就是按照需求来划分指责。
这样会导致:
1)问题点和领域知识重叠,比如"用户提交订单"和"用户购买处方药"都会处理支付问题,支付领域的问题他们都需要处理一遍,痛苦加倍
2)基础模型重叠,比如"收集用户病情"和"医生接单"和"用户购买处方药"都会用到就诊人和病例的模型,他们很可能自己定义一套内部的模型,将来想打通简直就是噩梦
3)难以区分核心和非核心,如果你作为一个在线医疗系统,结果一半的人力在支付问题上,那可想而知,医疗真正的核心问题谁来解决?
很多人可能已经想到了,建立通用底层服务,然后上层业务统一调用底层服务,这样可能有一定的思路了,但是还不够彻底,因为一旦涉及到服务,就会涉及到拆分的依据和原则,这个问题就相当复杂了。所以我们假设带着这个思路去拆分一下上面的用户故事划分的域。
1)交易域,首先能想到的是交易域,这里处理支付相关的问题,所以支付领域的问题汇聚到这
2)订单域,这里包括订单,商品等重要的信息
2)对话域,作为医生和用户的医疗IM通用工具,这里需要处理各种个性化的通信协议,比如医嘱,病例等医疗特性内容
3)医生域,医生具备接单,看病,开处方等能力
所以还是上
直接看一段例子
package main import ( "sync" "fmt" ) func main() { m := map[int]int{} var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(i int) { if _, exists := m[i]; !exists { m[i] = i } wg.Done() }(i) } wg.Wait() fmt.Println(m) }
这个例子可能有三种输出情况
情况1
输出结果:正常
map[0:0 1:1 2:2 3:3 4:4 5:5 6:6 7:7 8:8 9:9]
情况2
报fatal,错误信息显示存在读和写冲突
fatal error: concurrent map read and map write
情况3
报fatal,错误信息显示写冲突
fatal error: concurrent map writes
map在go的内部实现其实是哈希表,哈希表结构体定义。重点关注flags字段。这个字段在处理冲突中起了关键作用。
type hmap struct { // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go. // Make sure this stays in sync with the compiler's definition. count int // # live cells == size of map. Must be first (used by len() builtin) flags uint8 B uint8 // log_2 of # of buckets (can hold up to loadFactor * 2^B items) noverflow uint16 // app
不管你有没有想过这问题,领域模型一直都在,与是否发现它无关。假设我们去专卖店买手机,买完手机之后留下了自己的名字和电话号码,并且店员用一张单子做记录。
什么人需要查看这个单子呢?店员A需要查看,店员B也需要查看,老板也需要查看单子计算今天的销售额。而且大家的关注点不一样,店员要求名字和电话号码对上就行,可是老板却需要售卖金额。这个时候大家都要看的情况下,必须保持格式统一,否则就乱套,要不是少记电话号码,就是少记金额。这个时候老板和店员其实默默的形成了一个心智模型。订单=名字+电话号码+商品+金额
下面是店家和老板达成约定的订单:
名字 | 电话号码 | 商品 | 金额 |
张三 | 13249383944 | iPhone13 | ¥9000 |
李四 | 18393843432 | 小米9 | ¥2100 |
假设手机卖出去之后,客户需要维修手机,于是客户来到店里询问店员,店员查核查了订单记录,然后引导用户到维修部。维修部发现手机过了保修期,所以他写了一个维修单让顾客去缴费。这个时候是维修员和收银员达成的心智模型。维修单=名字+电话号码+商品+维修费用
维修人员和收营员约定的维修单:
名字 | 电话号码 | 商品 | 维修费用 |
张三 | 13249383944 | iPhone13 | ¥500 |
王五 | 13637673434 | ViVo C9 | ¥600 |
这里对应一下有的公司的现状:这一点并不陌生,如果我们将时间拉回到10几年前,我们眼中的领域模型其实说白了就是数据库设计。这种开发方式是基于数据模型的,我还记得很经典的一句话:只要数据库定了,对应的业务逻辑就定下来了。这个时期面对需求的时候,表现就是开发人员一开始就围绕数据库进行设计
但是,这个时候他们一定要接受抱怨并且开始动脑筋,抱怨就是:
1)维修人员抱怨为什么用户下单的时候有的信息,在维修的时候还需要再抄一遍
2)收营员开始抱怨,这个用户维修单上的信息和订单上的信息不一致,总是搞错,每次都需要重新核对
这时候你会发现用户信息到处都有,需要做一个客户模型,客户信息独立于订单和维修单,并且你可能也发现,用户拿身份证核对就好了,所有部门共享客户模型。
这时候你又在思考,好像维修单里面的大部分信息在订单里面都有,能不是直接复用订单数据呢,于是你可能做一个订单模型。用订单ID去和其他服务关联。
所以,及时你没有使用任何的建模概念和工具,有的时候其实你已经在做这一类的事情了。但是你的目标和建模的目标是一致的,建模的作用:
1)
1.下载安装包
https://prometheus.io/download/
2.解压运行
解压后,运行./prometheus --config.file=prometheus.yml
这里mac系统可能会碰到无法打开的问题,按照如下操作之后,再次运行命令
a.点击屏幕左上角的苹果图标,选择菜单:系统偏好设置...。
b.打开系统偏好设置界面,点击"安全性与隐私"->"通用"。
c.在窗口底部会看到:已阻止使用“XXX”,因为来自身份不明的开发者。点击后面的"仍要打开"按钮
3.访问
浏览器地址输入:http://localhost:9090/,即可访问
在go项目中引入prometheus之后,默认已经有监控项了,我们可以在prometheus的监控面板中看到,比如下面的例子
1)编写一个最简go应用
package main import ( "net/http" "github.com/prometheus/client_golang/prometheus/promhttp" ) func main() { http.Handle("/metrics", promhttp.Handler()) //暴露 metrics 指标 http.ListenAndServe(":8091", nil) //启动Server }
2)查看默认指标
2.1. 运行上面的go程序
go run main.go
2.2 浏览器访问http://localhost:8091/metrics 然后查看默认指标输出
这里我们关注promhttp_metric_handler_requests_total指标,表示http访问量,每次刷新这个指标会+1。
3)将go应用节点添加到prometheus的prometheus.yml配置
追加配置到prometheus.yml中
# 这里新增一个监控Go应用节点 - job_name: "gomonitor" # metrics_path defaults to '/metrics' # scheme defaults to 'http'. static_configs: - targets: ["localhost:
和以往的开发模式不一样的是,DDD不是从用户需求开始的,DDD的起点是用户故事,值得一提的是,为了避免瀑布流模式的缺点,从用户故事到战术设计不是一条不可逆的路,相反,最好的模式应该是斡旋前进。
挖掘用户故事必须包含产品团队,领域专家,研发团队共同参与,这里值得一提的是,领域专家这个角色的定义,这里是相关领域有资深经验的专家,比如
1)针对医疗行业,那么医生就是领域专家
2)针对教育行业,那么老师就是领域专家
但是,普遍存在的一个现象是,公司里并没有这些领域专家,往往PM承担了这个角色,这样在一定程度上也是可以的。但是还是建议每间隔一段时间邀请真正的领域专家来介绍一些这个领域的故事。
用户故事属于问题空间的描述,用户故事描述公式为
公式: 作为什么人,我希望做什么,达到怎么样的一个目的。
例子:作为用户,我希望能够通过手机在线上和医生交流,从而更加便捷的清楚病情
复杂版
公式:合适的人(who),通过合适的渠道(channel),将合适的内容(what),在合适的时段(when),发送给合适的客户(whom),产生一定的效果和反馈(effect)
例子:复杂的一句话用户故事,甚至可以把模型建立出来。
电商运营人员 希望 对7天内没有回购的用户 在 周六晚上8点 的时候 通过 短信提醒 的方式 发送一张优惠券 并且希望用户回来再次购物并 评价购物体验
这里列举一个电影票务系统的例子,假设我们并不知道票务系统长什么样子,那么我们(架构师)就去聆听另一方(领域专家或者产品经理)陈述我们的电影票务系统
这里的话术是用简单的公式: 作为什么人,我希望做什么,达到怎么样的一个目的。注意,中间一定要问答和讨论,最后形成一个图形表述的用户故事。
例如,这是一个电影票务系统的用户故事描述,这种图会作为架构师,产品经理,领域专家讨论之后的用户故事图形表达。
先来看一个笑话:
一飞机在四川上空遇到故障,四川空管台问:“出了啥子问题?”
机上发出了求救信号:“MAYDAY!MAYDAY!”
空管回复:“没得问题就好。”
(四川话“没得”和“MAYDAY”发音差不多,“没得”就是没有,“MAYDAY”是国际通用的无线电求救信号)
这个笑话在平时的工作中非常常见,基本产品团队一套术语,研发团队一套术语,经常出现两个平行时空的对话。有的研发或者产品能发现这个问题,当讨