API 鉴权

▎常见的鉴权模型

  • OAuth 2.0
  • ClientSide A <-(token)-> B, A -> B
  • ServerSide A -> B

> OAuth 2.0

这是一种平台上常见的方法,用户授权去拿它的数据。

是一种用户参与的主动行为。

> ClientSide

Client 不一样,Client 都是向服务端申请一个 token 令牌,再由这个令牌去查信息的。
原因主要是 Client 轻量,不适合保存私钥,也不适合计算 token。

Client 用于用户自己使用网页登录的模式。

> ServerSide

Server 方式,用于两个系统间通信,而 Client 的方式,则用于向手机端签发一个 token。

ServerSide 也有两种常用的方式

  • app key、secret 模式
  • 公私钥签名模式

- app key、secret 模式

就像是 basic 验证时的用户名密码,secret 就是 digest 了。

因为服务端吗,不经过复杂的链路。也不需要啥安全的。

- 公私钥签名模式

还有一种就是 A 用私钥进行签名,
B 用公钥进行验签。


▎两种类型

平时,我们一个系统里,会有用户和平台两种角色

用户只能看到自己的数据,而平台可以看到整体的数据。

平台 != Admin

> 用户角色的 API

而用户,根据角色不同,又分为

  • 用户
  • 商家
  • 客服/管理员

其各自有着不同的功能和权限。通常我们会用不同的域来对其做区分:

  • /uv1
  • /mkv1
  • /admv1

▎授权模型

根据用户的角色,对应其可以使用的授权模型:

User Maker Admin APP
ServerSide Y Y Y Y
ClientSide Y Y Y /
OAuth 2.0 Y / / /

▎谈谈 APP 授权

> 使用及场景

似乎不常见哪个平台,对自己平台数据有开放 API 的,这些都是内部关键数据。

都是自己的人员,比如说微服务中的同公司其它服务,也就是说这里不用对权限做太多的限制?

> app 的权限范围

如果不设计权限范围的话,那不就乱来了?

app 关联到同户,不然找不到人

> 归属权

  • 归属到一个用户
  • 一个组织

如果是关联到用户下面,那这和用户的自己的 token,除了权限上的不同,其它都一致了。

> 申请流程

这个应该不能申请,只能后台批。

> auth 定义

app 是关连到用户,还是组织?

表名:tokens / authkeys

字段:

  • user_id
  • scope
  • session_id
  • private_key

▎JWT - ServerSide 公私钥签名模式

1
2
3
4
5
type MyJWTClaims struct {
jwt.StandardClaims
AppID string `json:"appid,omitempty"` // appid: appid
Mode string `json:"mode,omitempty"` // mode: Side
}

通过 mode 字段标识是一个服务端对服务端的 token

> JWT 定义

jti - serverside 用,表示一次性,uuid
iss - serverside 用,表示是谁,用户是谁
sid - serverside 用,给谁

uid - clientside 用,表示谁

app 模式

iss - ????
jti - 一次性
sid - 表示 app 要用的 key

app 是谁呢?要标识吗?


▎REF::

Git 201 – Git LFS in Action

Git Large File Storage (LFS) 是一个由 GitHub 开发的 Git 扩展项目, 用于增强 Git 对大文件追踪的支持.

Git 对于一些二进制,及大文件的处理一直不是很好,二进制文件的对比,差量存储都未能优化处理。

这导致了在 Git 的仓库中,会储存二进制文件的所有历史全量版本。这样一来,会给储存带来很大的困扰,同时在进行 Clone/Fetch/Pull 时都会产生大量的流量,特别是在一些网络不大好的地方。

而 Git LFS 就是为了解决这一问题而产生的工具。它将标记的大文件保存至另外的地方,而在主仓库仅保留其轻量级指针。这样在获取版本时,只更新主库对应的大文件,会更加轻量,高效。

主要的特征如下:

  • 超大文件支持
  • 减小仓库占用空间
  • 更高的远程获取效率
  • 和 Git 操作保持一致

▎安装

1
2
# macOS with HomeBrew
$ brew install git-lfs

为 Git 账号进行初始化设置(只需要一次)

1
$ git lfs install

▎使用

> 配置 LFS

在需要使用 LFS 的项目中,通过 git lfs trace 配置需要加入 LFS 的文件,可以使用通配符。

编辑完之后,会创建或修改项目的 .gitattributes,也可以通过手工修改该文件来进行配置。

1
$ git lfs track "*.bin"

> 将 .gitattributes 加入到仓库中

1
2
3
$ git add .gitattributes

$ git commit -m "track files using Git LFS"

执行 push 时会输出一些 LFS 的指示

1
2
3
4
5
6
7
$ git push

Uploading LFS objects: 100% (1/1), 104 KB | 0 B/s, done.

Enumerating objects: 6, done.
Total 4 (delta 2), reused 1 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.

> 查看文件的状态

1
$ git show HEAD:cat.bin

输出当前文件关于 LFS 的一些值:

1
2
3
4
5
6
7
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
version https://git-lfs.github.com/spec/v1
oid sha256:ce5c0e9713c54368d4a6b9a30c46503bac70c293091f0e60e30aa14f6813cca0
size 103890

▎操作命令

> 基本操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git lfs track
# 将一个或者一类文件以 git lfs 的方式加入到版本控制中 (实质是修改 .gitattributes 文件)

$ git lfs untrack
# 将一个或者一类文件从仓库中移除

$ git lfs status
# 类似于 git status , 显示 git lfs 方式的文件在 暂存区的状态

$ git lfs lock
# 锁定一个或者一些文件, 只允许当前的用户对这些文件进行修改, 防止在多人协作的场景下冲突

$ git lfs unlock
# 同上, 解锁一个或者一些文件

> 其它操作

显示当前仓库的 env 配置:

1
$ git lfs env

LFS 文件操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ git lfs ls-files
# 展示使用 git lfs 方式的文件列表

$ git lfs prune
# 删除全部旧的 Git LFS 文件

$ git lfs fetch
$ git lfs pull
# 不通过 git 获取文件

$ git lfs push
# 不通过 git 单独提交文件

$ git lfs checkout
# 取消文件修改,切换版本

正常情况下会随着 git pull/push 一起执行
如果在 git pull/push 的过程中断了, 导致二进制文件没有被拉取的时候, 可以使用这些命令(支持断点续传,速度不慢)

▎常见问题

> Git LFS 迁移 - 已提交 git 文件的处理

已经被 git 提交了,怎么转到 LFS 里呢?

这时就要把 git 的旧记录也一并删除,不然该文件及历史版本,会一直存在 Git 的数据中,每次获取都占用大量带宽和存储空间。

1
2
3
4
5
6
7
8
$ git lfs migrate

# 用来将当前已经被 git 储存库保存的文件以 git lfs 的保存 (将 git 对象转为 lfs 对象)
# 例如如果将当前远程不存在的的所有 mp4 文件清除
$ git lfs migrate import --include="*.mp4"

# 如果是已经上传到其它服务器的内容, 则需要指定分支 (可能需要 push --force)
$ git lfs migrate import --include="*.mp4" --include-ref=refs/origin/master --include-ref=refs/origin/dev --include-ref=refs/origin/test

上述操作,会将所有的 git 对象,转换为 git-lfs 对象,git 历史记录会受影响。

但 .git 目录下依然储存着该文件,需要进行清空处理:

1
2
# 然后使用如下命令清理 .git 目录
$ git reflog expire --expire-unreachable=now --all && git gc --prune=now

> 如何只获取仓库本身,而不获取任何 LFS 对象?

如果相关工作不涉及到被 Git LFS 所管理的文件的话,可以选择只获取 Git 仓库自身的内容,而完全跳过 LFS 对象的获取。

1
$ GIT_LFS_SKIP_SMUDGE=1 git clone git@github.com:username/my_lfs_repo.git destination_dir

使用该方式 Clone 仓库后,又需要这些 LFS 管理的文件了,如何再次拉取下文件呢?

我们可以通过如下 Git LFS 的 pull 命令进行拉取:

1
$ git lfs pull

> 如何获取历史版本

Git LFS 文件,每次提交时,都会将文件的索引记录在对应的 Git 的 commit 中。

所以只要通过 git checkout 就能恢复出对应的版本。

> 更复杂的场景

如果一个项目中,有很多用 LFS 管理的文件,有一些目录(或文件)不是通用的,但是另一些是必要的。那么如何设置在 clone 或 checkout 时只下载必要(默认)的 LFS 上的文件呢?需要时,再去下载另一部分文件。

可以用在 git config 中设置(要先保证该路径已被 git lfs track 追踪):

1
$ git config -f .lfsconfig lfs.fetchexclude="*.mp4"

这个命令会生成一个.lfsconfig文件(如果不存在),并在其中加入。

1
2
[lfs]
fetchexclude = *.mp4

这样配置后,每次 checkout 和 clone 就会过滤掉当前目录下以 .mp4 的为结尾的文件。

那如果需要这些文件时,要怎么办呢?

可以通过上文中的 git lfs pull 进行复写,如:

1
git lfs pull -X ''

就会在拉取时,去除 fetchexclude 选项。

▎GitHub 中关于 LFS 的使用

GitHub 在 Billing 账单页面可以账号的 LFS Data 使用情况。

默认每个账号都有 1GB 的免费存储空间和 1GB 的免费带宽,可以通过购买,来添加更多的容量及带宽。

▎REF::

简单的 ID 生成器设计

关于在分布式系统里如何生成各种 ID 的问题,对于小的业务来说一直是件麻烦的事情。

小业务注定不像大厂的服务一样,有超多的服务器,很多的开发者组合,所以方案要求简单,并不太高的性能。

ID 要求

  • 唯一性
  • 保护业务隐私(不能透漏有多少用户数)
  • 数值型(更少占用空间)
  • 单向增长,精确有序 (写入性能保证)

常见的方式是有两种:

  • 中心式发号器
  • 单机发号规则

中心式发号器需要额外的服务,增加了成本,一旦发号器出了问题,会影响业务。

单机发号,在号码的规则中,约定一定的位数属于某台单独的机器,也需要在机器上执行一定的配置。

这两种对于小企业小业务来讲,都不方便。

用户 ID 的方案

该方案仅使用数据库,搞一个 Cron 进程,不断的维护一个表的 ID 队列,等待业务去消费。

简单的说,做一个小的业务规则。不断的向数据库中生成一些等待使用的 ID,然后业务在用时,从数据库中查最小可用的 ID,做为本次的实例的 ID 即可。

> 流程

用户 ID 从 3000001 开始,从 ID 中选最后一条记录,然后随机 (0-9 间)选一个数值用作跳跃值,然后加在最后一个 ID 上,生成一个新 ID,写入表中。

通过 Cron 进程,在用户表中,维护 1000 个这样的未消费 ID 队列(小时级别的调度就可以了)。

每次有新用户注册,就取最小一个未消费的 ID 值。然后更新状态到待消费。

等用户填写完信息后,将该值变成正常用户状态。

状态值

  • unspent
  • pending
  • normal
  • disable

其中 unspent/pending 这两个就是为了 ID 生成器所专门设计。

- 好处

好处是,简单啊,哪怕有多个服务,也不用关心 ID 的问题,解决了分布式的麻烦。

通过数据库的解决方案,成本较小。实施简单。

订单 ID 的方案

订单和用户不同的是,定单可以使用大长度数值。

不同于普通 ID,定单还有额外的要求:

  • 可以快速分析出有用信息 (在定单中加入时间内容)

- 关于 uint64

uint64 是定单所使用的 ID 值生成范围:

uint64 取值范围:0 - 18446744073709551615

去掉第一个 1 后,可以拆解为如下字段:

1
8446 7440 7370 9551 615

- 符合 uint64 的设计

基于这样的要求,将时间作为定单上的信息,时间天然是自增长的。

1
2020 0210 1212 1455 123
  • 2020 0210 - 日期
  • 1212 1455 - 时间(厘秒单位)中午 12 点 12 分 14 秒 55 厘秒
  • 123 - 随机数

当然也可以把前面的 20 去掉,毕竟一个系统存活几十年的概率几乎为零。

1
2020 0210 1212 1455 123

这样可以在时间精度上进行加长,并且在随机数上进行加长,可以根据实际情况来做调整

1
2002101212 14550 123

在一个小型定单系统里,在厘秒的时间段内+三位随机数,重复的概率非常低(没有做过大型测试),但理论上非常可行。

1
2
3
4
+---------------+----------------+----------------+
|timestamp(cs)14 | timestamp(cs)2 | random(3) |
+---------------+----------------+----------------+
id = sign + delta seconds | workerid | sequence

总结

ID 生成器有很多很好的方案,像 Twitter 的 Snowflake,大型项目非常推荐,但对于小项目来说,一个可行的方案不仅是速度,可行性,还有非常重要的成本。

这个方案,是实践下来一个很不错的方案。

NAS 清洁小记

春节期间,把手机,相机里的照片统一整理了次,放进了 NAS,做了一年的收尾。

这款 716+II 是 16 年末买的,也已经服役三年之久了,这三年来机器本身从没出过问题,使用上也带来了不少的便捷性。堪称近几年买过最省心好用的产品。性价比这回事不是很重要,省心比较重要。

简单的回顾下这几年的使用情况吧

- 省心好用到不记得TA的存在

  • 买回来就放在一个角落里,然后有年夏天小区总是断电,于是配了个电源。
  • 硬盘有过一次坏道,在质保期内,换了块新的。

其它就没了

- 群晖卖的是软件

这句话是对的,Synology 的配套软件才是最好用的,像 DS Photos,DS Video 装在 Apple TV 上,可以方便的看照片,电影。后来出的 Synology 系列主要面向团体用户,像 Driver,Moments。Driver 是我最常用的。在多机之间同步,重要文档几乎都放在这上面了。

几年来也总结了一套成熟可信的同步方案。

- 电源

好像这个世上除了 Apple 外,其它商家都不会做电源,弄一个大的外置电源,好丑也不方便。虽然你不会动它,但就是不好看。

- Wi-Fi

没有 Wi-Fi 就让他的摆放位置很受限制,只能放在路由器的边上。

- 备份问题

现在只有两个盘位,做备份吧,就只剩下一个盘的容量了,又不够用。

没有备份总觉得不踏实。

关于备份,有想过换一个 918+,或者额外加一个 5 盘位的拓展,但都没实施,毕竟也没那么强需求,而且现在东西换代太快了。

清理

把机器搬出来,机身上全是灰,没有做防尘处理,就简单的摆在一个架子上。

这里还找不到钥匙了,左下角硬盘的孔位是要用钥匙旋转开锁的,但实在找不到了,后来用螺丝刀慢慢打开的。

拆下风扇,做个清理。灰真大 😓

原配 2G 的内存,这次的原意是加两根大内存

拆机后发现,只有一个内存插槽,并且手里要换的内存也是 2G 单条。尴尬。只能当是清洁了。

软件开发周期

▎为什么要有周期

生命周期的每一个周期都有确定的任务,并产生一定规格的文档(资料),提交给下一个周期作为继续工作的依据。按照软件的生命周期,软件的开发不再只单单强调“编码”,而是概括了软件开发的全过程。

  • 周期带来明确,让每个人知道当下在何时、何地、要做何事。
  • 周期带来秩序,让所有人知道如何分工。
  • 周期带来简化,软件工程太复杂,只有把工程分解为小周期可理解可完成的
  • 周期带来成就,开发者可以很快看到成果,而不是一味的编码
  • 周期带来减损,短周期可以让需求者迅速做反应,而不是等整个流程结束后才知道产品和需求不对应

▎周期中包括

  • Propose(Requirement) | 提案(需求)
  • Development | 开发
  • Integration + Testing | 集成和测试
  • Release | 发布

在周期中,有很多规范可以参考,像 Agile/SCRUM,这也是整个工作期间,用过的最多的一种开发方式。

SCRUM 中定义了需求的标准,优先级的制定,开发的一些方式,集成和测试的建议,这套体系比较复杂,有机会再细讲。但好的体系学习成本高,背后的收益也大,可以在团队中树立标准的沟通语言,构建共同的认知体系,降低总成本。

另一个要提到的点是在周期中技术的应用,软件开发不仅是在开发软件,更多的是在利用现在工具整合流程,提升效能。有时,技术的采用就是标准的采用。


▎常见软件开发周期

  • 两周

以上是我见过主流的开发周期。

> Annual | 年

以年为单位的,主要是一些大型软件,以开发语言,操作系统为主。

开发语言 Node.js / Java / Python 等
操作系统 macOS / Ubuntu 等

这种系统的特点就是庞大,且有着明确的发布日期,需求的来源也有着明确的标准。

> Monthly | 月

一些偏传统的公司会采用一种发布列车的方式,每月定制发布时间。如果一个功能能在发布列车启动前做好准备,就可以上车。

总体来说,月还是偏少见的。

> Biweekly/Weekly | 双周/单周

从 MS/EF,双周一直是比较推荐的,原因是一个标准周期里的项目有点多:需求及评审,开发,集成,测试,发布,回顾。放在单周中,会挤压开发时间,让开发者疲于应对。

单周应用于一些特殊时刻,需要产品尽快上线,有种做一点上一点的感觉。


▎实践

> 角色:

  • Product | 产品
  • Designer | 设计
  • Developer | 开发
  • Tester | 测试

> 流程图

这是我在上家公司所实施的流程,图中表示了不同的周期,不同的角色,在一个时间线里,分别要做的事务。

在每个生命周期里,流程是有严格的依赖的关系,产品先行,设计其实,然后是开发,测试,直至发布。

产品是建议先行最少一个迭代的,设计要在下一个迭代开始前,准备好大量的稿件,不要等开发阶段开始后还在等设计。

开发和测试同行,其次是集成和测试同步进行。

在开发期间,同时处理线上的一些问题。


▎举例

年度 | Python Annual Release Cycle

Python 于 2019 年末宣布使用 12 个月的发布周期,委员会认为有一个一致的时间表会对社区有所帮助:

  • 开发者能制定自己的工作计划
  • 社区知道何时参与测试,提供反馈

https://lwn.net/Articles/803679
https://www.python.org/dev/peps/pep-0602


▎REF::

https://en.wikipedia.org/wiki/Systems_development_life_cycle
https://en.wikipedia.org/wiki/Software_release_life_cycle
https://stackify.com/what-is-sdlc

Windows 10 那些事

自从用了 Macinotosh 后,已经好多年没有在操作系统上纠结了,最近配了一台 NUC,顺带体验了下 Windows 10,发现一些有意思的事。

▎版本

不管愿不愿接受,Windows 10 会是该系列最终的版本了。也就是不会再有 11 了。

原因也很悲惨,Win 10 是单机操作系统的顶峰了,这个市场的机会已经没了。微软的重心也到了云和服务上。就连这个操作系统部门都被合并到了云业务部门。Office 现在出售的方式也主要是订阅了,Office 365。

无论你有多爱它,都要接受这个事实。

即然不会有新的操作系统,那 Windows 10 代表的就不再是版本了,而是一种品牌。版本则由另外的数字来代表。

微软使用了一种全新的命名方式,Windows 10 19H1 是微软于 2019 年春季推出的一个版本(H 是德文 Halbjahr 缩写,译为 6 个月)连起来 H1 就是年度上半年,所以 19H1 即 2019 年上半年发布版本。

这是 Windows 发布的一个版本的信息

- 更新

最近爆发了很多 Windows 升级的问题,作为在微软工作过的开发者,特别能理解。

一款在战略上不再是顶级的产品,资源配置上肯定会下降不少。在流程上也会为了这种配置做简化,像测试的简化。

回想过去,那些优秀的人才在帮 Windows 不停的付出,找到各种问题,代价就是高额的支出。

当然了,这里没有对和错,公司做出的决策,都会由公司来承受最终的代价。


▎Settings 设置

这个面板是面镜子,通过它可以看到很多。

Settings 像是个未完工的产品,很多时候,依然要去控制面板里找到一些设置的地方。

新闻上也有很多微软致力于迁移的文章,但,Windows 产品已是明日黄花,所以这种迁移十有八九都不会再发生了。就像一个城市,在最高点时开始建新城,随后没落了,整个新旧城的结构就这样保留了下来。

这也又一步印证了,属于我们童年、中年的那个时代过去了。


▎内核

Windows 10 有个很有意思的点,看起来它像一个双内核的系统。就是设置和控制面板。

在 Settings 中的 Region 中可以设置 Country 和 Format,但这对于一些旧的软件来并不起作用(银行 APP)。

而在控制面板的 Region Settings 中,可以单独设置 System local,并启用 Unicode UTF-8 为世界语言支持。

通过这个,可以发现,Windows 10 在对待 APP 是有两个内核(暂时这么称呼)来处理的。

对比 macOS 15.12 在 32/64 位的一刀切,Windows 承受了太多的过去。


▎32 位

WoW64(Windows on Windows 64)应该是 Win7 时开始提供的一个功能,即提供一个虚拟技术,让 32 位的应用程序正常地运行在 64 位的 Windows 中。

我在微软时还测过这类的功能,转眼间十年过去了,这项技术依然在为各种软件提供服务,也就是国内市场上目前依然是 32 位软件的居多。像银行,税务等。

能理解,32 能用,还搞啥 64,万一用户是个 32 位的旧系统。

看,这就是 Windows 文化,从来都是能用即可。没有棱角。


▎DevBox

作为一名开发者,Windows 能用吗?

在使用的这几天来看,并不行。

- 专业的 Termial

Windows 没有专业的 Terminal,这一点就不行,开发者大量的工作,都是在命令行里进行完成的。没有专业的命令行,生产效率完全无法根上。

- *nix 内核

虽然 Windows 也能方便安装各种开发软件,但没有 *nix 的内核,好多东西都无法参与,但好在现在 WSL 做的还可以。

Windows 的 FS 不支持 Soft link,我的个人习惯是把很多配置做成可同步 Sync 的,真正的文件在 Dropbox 中,而目录下用的都只是 link。

但 Windows 不支持。


▎AppData

Linux 系统习惯把用户的应用数据存放在 ~/.xxx 目录下。

Windows 现在也有一个 AppData,但不得不说,Roaming,Local,LocalLow,这几者的关系,实在让人费解。


▎其它

如果您不是从事音视频编辑、软件开发,Windows 是一款真心不错的产品。目前还没有其它系统能代替它的地位。

肺炎时期的日记

这是一个我个人完全不擅长的领域,所以开头怎么写,我也不知道。

> 流水账

年初四,按网友的说法,今天是接着不动。

上海的雨已经连续下了一周了,每日都是湿冷,好久没有运动的身体显的很臃肿。

回忘这艰难的假期,漫长如过了一个世纪,但细数起来,也不过才一周而已。

上周初,公司聚餐时,大家还在聊武汉是不是有瞒报,然后假期初还去了次迪斯尼,满城中口罩数量已经多了起来,但还是充满着热情和忙碌的。

疫情来的太快,没有一点防备,都来不及做准备,大家就各被按在了家中。

而居家的时间如蜗牛般的离去,微博朋友圈变成了疫情播报地,每天看的谈的都是疫情的进展,这一过就是多天。

> 回忘 Sars

那时,我还在读高中,那时都是在校住宿的,整个非典期间都是封校,关于外面,只有一些新闻联播可以看,并没有太多的感触和记忆。

封校期间,父亲给我送过一次东西,大约有香蕉和板蓝根,见不到人,只能放在门卫处,然后经过消毒去领取。

这次病毒期间,听母亲讲了很多那时的事,像回乡的人被堵,其实和今日何其相仿。

我们已经战胜过一次,我们终将会战胜这一次。

但这些苦难一定要加在这个民族身上吗,从某个角度来讲,是的,像吃野味,迷信,这些行为,如果不能有效去除,这些疾病会一直像噩梦伴随。

> 封城记

23 号凌晨忽然发布公告,上午 10 点封城。自小,这是第一次见过封城的行为,而随即上演的是大逃离。

河南位邻湖北正北,这次是被重点表扬的省份,因全省尽封路,禁止人员流动,外来人口报备。

这些让我想到战时的情景,而这次正是一场没有硝烟的战争,对象是一种新兴传染病病毒。

但城市并不生产物资,封城的后续就是城市供应链的打断,各种紧缺会出现。所以还是建议大家稍囤积点备灾物资。但依然希望不会,毕竟中国不仅是一个大国,还是一个生产强国。

> 责任人

武汉在这次重大事故中的各种决策都非常糟糕。

像今日最火的一张图,如果我能回到一个月前,内容是:一个月前香港对武汉病毒非常重视,而回复都是香港阴谋论。

从瞒报,人大会,万家宴,到春晚。真可谓步步凌乱。

从物资储备丰富到当天的医务人员联名求助;从凌晨封城禁令到执行 8 小时空白期,从禁止城市通行影响医务人员到调度私车服务公务职员;

更为悲痛的是早期暴露问题的医生还被依法办理了。。。

> 人,真实的人

官方数字都很冰冷,无论是多少人感染多少人离去,都没有真切感。

但人的故事,都很悲伤。

微博上那些短短的文字,记录着一个个家庭的悲惨去离。

最美逆行者的图文渲染着人们的激情。

好像苦难又一次成为这个民族的武器,但大苦很快会忘记,而个人家庭的记忆永远无法抹去。

在这场灾难里,对人的关怀上,我们的表现并不好。

武汉人像过街老鼠,酒店不准入住,甚至看到无处收留的例子。加油站不准加油,鄂A 不敢上路。

他们说,武汉人不是病毒,只是倒霉版的你。

今天对武汉人的刻薄,改日在另一件事上,刻薄的对象就是自己。

> 这个时代

网络带来的信息洪流并没有让人们变得更睿智,相反,躲在看不见的另一处充满着阴暗和毒辣。

人们已无法分辨真实和虚假,也没有能力认真和同情。

这个事情,只能告诫自己,遇到陌生的事情,不要轻易站队,多读专业文献书刊,人生漫漫,学无止境。

最后,信仰科学!

A Little Architecture 【译】

by Robert C. Martin (Uncle Bob)
https://blog.cleancoder.com/uncle-bob/2016/01/04/ALittleArchitecture.html
@04 January 2016

译者注:这是很久前看到的一篇关于架构师的文章,出自 Bob 大叔,这篇文章曾被我分享给了好多人,好多不知什么是架构师的人,我不确定他们有看。但我确定我很认可这其中的道理:什么是架构师要关注的,不是数据库,不是库,也不是服务器,是业务及一些顶级的设计原则。
在当下,顶级的架构变成了云设计师专属,而中低层则是数据库直连的时代,再次把这篇文章翻译出来,给那些有志的开发者,关注哪些方能成就自己,勿做井底之蛙。



我想成为一名软件架构师。

这对于一个年轻的开发者来说是一个很好的目标。

我想要带团队做重要决策,包括数据库,框架和服务器以及其它一切。

恩,这样听起来,你并不是想成为一名架构师。

我当然想成为架构师,要在团队里做重大决策的那种。

但你刚才并没有指哪任何重要决策,你说的都是不想干的事项。

什么意思?数据库难道不是重要决策?你不知道我们花了多少钱在这上面?

可能很多,但,数据库不属于重要决策。

怎么能这么讲?数据库简直就是整个系统的内核了!它是整个数据的归集,排序,索引和访问的地方。没有它就没有整个系统。

数据库只是一个 IO 设施。它是提供了一些功能来做数据的排序、查询及报表,但这都只是系统架构的辅助。

辅助??这种解释太不可理喻了。

没错,就只是辅助。系统的业务规则可能会使用到这样那样的一些工具,但工具本身不是业务所固有的。也就是说,如果需要,你可以替换这些工具,但业务规则不会变。

恩,但我依然需要数据库来记录这些数据。

那是你的问题!

什么意思?

你认为业务规则依赖于你的数据库工具。但不是的,最少好的架构不应该有这样的依赖关系。

搞笑,如果不用这些必须的工具, 那我如何创建业务规则。

我并没有说不用这些工具,我是说不应该依赖于它们。业务规则应该不知道你用的是哪种具体的数据库。

如何让业务规则使用工具但不去了解工具?

你搞反了依赖。你的数据库依赖于业务。你需要明确业务不依赖于数据库。

胡扯。

正相反,我在讲计算机架构语言。这是一种依赖反转原则。低级策略应该依赖于高级策略。

更胡扯!高级策略(我想你是指业务)调用低级策略(这里指数据库)。所以是高级依赖于低级,就像呼叫者依赖于被呼者。路人皆知。

在运行时,这样讲是对的。但在编译时,我们希望的是依赖反转。高层级代码不应该直接标明低层级代码。

得了吧!不可能调用一个方法但又不提及它。

当然可以,这就是面向对象技术在做的事。

面向对象是指创建直接世界的对象。它是把数据和方法放在一起的内聚对象。它是把代码按直观的结构进行的一种组织。

谁给你讲的?

每个人都是这样理解的,绝对正确。

恩恩。使用面象对象原则你可以在不提及一个方法时来调用它。

你知道如何在一个面向对象设计中,对象间如何发送消息吗?

当然。

那你应该知道消息的发送者不知道接收者的类型吧。

这个取决于语言,在 Java 中,发送者要知道接收者的 Base type,在 Ruby 中,也要确定接收者能处理发送者所发送的消息。

是的。但这两种示例中,发送者都不知道具体的接收都类型。

恩,是的。

因此,发送方可以执行接收方的函数,而不需要指明接收方的确切类型。

我知道,但发送方依然依赖于接收方。

在运行时,是的。但在编译时,发送方的代码没有指明,或依赖于接收方的相关代码。事实上,代码层面,接收方依赖于发送方。

什么!发送方依然依赖于发送的 class。

来点代码吧。先看看 sender

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package sender;

public class Sender {
private Receiver receiver;

public Sender(Receiver r) {
receiver = r;
}

public void doSomething() {
receiver.receiveThis();
}

public interface Receiver {
void receiveThis();
}
}

这是 receiver

1
2
3
4
5
6
7
8
9
package receiver;

import sender.Sender;

public class SpecificReceiver implements Sender.Receiver {
public void receiveThis() {
//do something interesting.
}
}

注意了!receiver 依赖于 sender。还要注意 SpecificReceiver 依赖于 Sender。另外还要注意 senderreceiver 一无所知。

你这作弊了。你在 sender 的类里引用了 receiver 的接口。

你开始懂了。

懂什么?

架构原则。当然发送者有它的接口,接收者必须要实现该接口。

这表示我必须使用嵌入对象,然后。。。

嵌套对象只是一种达到目的的方法,这里是其它的。

等下。这些东西怎么使用数据库,回到讨论的起点。

那看更多的代码。首先看个简单的业务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package businessRules;

import entities.Something;

public class BusinessRule {
private BusinessRuleGateway gateway;

public BusinessRule(BusinessRuleGateway gateway) {
this.gateway = gateway;
}

public void execute(String id) {
gateway.startTransaction();
Something thing = gateway.getSomething(id);
thing.makeChanges();
gateway.saveSomething(thing);
gateway.endTransaction();
}
}

That business rule doesn’t do much.
这个业务倒是很简单。

It’s just an example. You’d likely have many classes like this, implementing lots of different business rules.
这只是个示例,你可以多弄点类,实现更多的业务。

OK, so what’s that Gateway thingy?
恩,Gateway 是个啥?

It supplies all the data access methods used by the business rule. It’s implemented as follows:
它提供了业务所需的数据访问方法。它的实现如下:

1
2
3
4
5
6
7
8
9
10
package businessRules;

import entities.Something;

public interface BusinessRuleGateway {
Something getSomething(String id);
void startTransaction();
void saveSomething(Something thing);
void endTransaction();
}

注意哦,这个包名是 businessRules

恩,那 Something 呢?

这里描述了一个简单的业务对象。我把它放到 entities 的包里。

1
2
3
4
5
6
7
package entities;

public class Something {
public void makeChanges() {
//...
}
}

这里是 BusinessRuleGateway 的最终实现。这个类知道真实的数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package database;

import businessRules.BusinessRuleGateway;
import entities.Something;

public class MySqlBusinessRuleGateway implements BusinessRuleGateway {
public Something getSomething(String id) {
// use MySql to get a thing.
}

public void startTransaction() {
// start MySql transaction
}

public void saveSomething(Something thing) {
// save thing in MySql
}

public void endTransaction() {
// end MySql transaction
}
}

再次注意,在运行时业务方法调用数据库,但在编译时,database 包提及并依赖于 businessRules 包。

恩恩,我想我有点懂了。你在用一个多态来对业务隐藏数据库实现。但你依然对业务提供了数据库的接口。

不,不完全是。我们并没有尝试为业务提供所有的数据库。相反,我们只为业务创建了所需要的接口。这些接口的实现方来调用相应的数据库。

恩,但如果所有的业务需要所有的工具,然后你就不得不把所有的工具写在 gateway 的接口里。

看来你还是不大明白。

明白什么?这对我来说已经很清除了。

每个业务都为自己的数据访问定义单独的接口。

等等?

这叫接口分离原则。每个个业务类将只使用数据库数据的一部分。所以每个业务都提供一个接口去访问该部分数据。

这不就意味着要有一堆的接口,并且有一堆的类来调用其它的数据库类。

这才算开窍吗。

但这不乱吗,又浪费时间!为啥这样干啊?

你想又清晰,又省时间。

这简直是为了代码而代码。

相反,相对于那些不相关的决策,这才是最后要的架构。

这啥意思?

还记得你开始时说要成为一名软件架构师?你想做那些真正的重要的决策?

是啊,我是想这样啊。

在这些决策间,你想的是确定使用哪些数据库,哪些服务器,哪些框架。

恩,然后你说这不是重要的决策,这是不相关的。

是的,对架构师而言,重要决策,不包括使用什么样的数据库,服务器和框架。

但你总要先决定这些吧!

不,你不用。事实上,你更希望在开发周期更迟些时再做这些决定,这样你可以有更多信息。
有时,架构师早早的决定上个数据库,最后发现搞个文件写写就够了。
有时,架构师早早的上了个 Web 服务器,最后发现团队想要的只是一个 socker 接口。
有时,架构师早早的选定了一个框架,最后发现框架提供了一堆没用的功能,还增加了一堆有的没的限制。
可幸的是,团队架构师有一种方法论,可以将这些决策推迟到有足够的信息时再来做。
可幸的是,架构师将团队从速度慢、资源匮乏的 IO 设备和框架中隔离出来,使得团队能够创建快速、轻量级的测试环境
祝福那些架构师关心真正重要的事情的团队,并推迟那些不重要的事情。

胡扯。不知所云。

如果你还没有进入管理层,也许十年后你可以的。