可落地的云原生应用规范

应用的规范定义是一个权衡的过程,你不能一下把规范定义得太死,太死了导致无法很好的在不同团队推广,最后可能导致规范失去信用。你也不能把规范定义得太泛,导致人们不知道如何下手。 在经历了传统部署(使用Ansible自动化部署应用到虚拟机)和Kubernetes的部署(使用Helm实现自动化部署)后,我们总结出一套云原生应用规范。它无关语言,无关框架,无关部署方式。 定义此云原生应用规范,我们有以下几个目的: 节约人员沟通成本:你不需要像以前那样需要反复的问对方的服务的端口; 节约运维成本:因为应用是标准的,所以,对于所有的应用,只需要使用统一的部署方式、统一的监控方式; 节约开发新应用的成本:根据规范,我们可以搭建各种语言或者框架的工程的脚手架; 以下是规范正文: 业务端口规范 所有的Pod或部署在虚拟机上的应用要求: http协议的服务使用8000端口 grpc协议的服务使用9000端口 如果有其它协议可以在此添加 所有的Service:使用80端口 实践Tips1:遗留工作通常没有统一的端口,我们可以在部署环节通过环境变量来覆盖应用本身的端口来实现统一端口的目的。 实践Tips2:对于虚拟机上的部署,过去,一台机器上我们常常部署多个应用,所以要求每个应用的端口都不能相同。我们的做法,缩小虚拟机的配置,一个虚拟机只部署一个应用。 监控端口规范 监控端口统一使用:30000。监控端口与业务端口分离是基于安全的考虑而设计。而且监控端口只允许内部访问。 提供Prometheus监控接口/private/prom:返回 prometheus标准数据结构; 提供优雅停机接口/private/shutdown:POST请求即代表发起停机操作; 提供健康检查的接口/private/health:http code返回200代表健康; 提供应用ready接口(可以与健康检查接口相同)/private/ready:http code返回200代表ready; 提供实时修改日志级别的接口:/private/loggers 。这个接口,我们可以参考Java的Logback框架的实现。 Docker镜像规范 提供 curl 命令行,因为我们需要使用命令进行优雅机器:curl -XPOST <http://127.0.0.1:30000/private/shutdown; 应用需要提供应用进程的环境配置入口。比如JVM应用需要提供 JAVA_OPTS 的环境变量设置 实践Tips:实际工作中,可创建一些基础镜像方便开发人员使用。 日志规范 日志要求统一输出到console。输入日志统一使用json结构。json结构中必须包含字段: @timestamp: 日志打印时间 thread_name:进程名 level:日志级别 appId: 应用标识,同一个namespace全局唯一 namespace:用于隔离app,租户的功能 env:环境标识 ver:应用版本 msg:帮助debug问题的 traceId: 其实就是traceId 同时,我们建议使用以下通用字段: event:代表事件,建议不要使用带空格的字符串 method:方法名 result:代表执行结果,可以是方法的返回结果,也可以http方法的response req:代表请求参数体 以下是日志示例: {"@timestamp":"2021-07-15T17:24:07.912+08:00","userName":"Foobar","thread_name":"http-nio-8080-exec-150","level":"INFO","appId":"UserService","env":"prod","ver":"v1.0-5598","event":"register_user"} 实践Tips1:在实际工作,我们需要为不同的语言实现符合此日志规范的框架。 实践Tips2:日志规范需要配合日志处理环节考虑,在日志处理环节没有准备好之前,保持原样是更明智的选择。 小结 此规范已经在我们团队实践一年多。正在向其它团队延伸的过程。不敢说它是一套面面俱到的规范,但是它是一套能在一些团队进行落地的规范。 每个人都存在认知不足的情况,我也是人,所以我也不例外。此规范只是版本1.0。将来发现不足,持续改进。

2024-02-26 · 1 min · 62 words · 翟志军 Jack Zhai

我是如何将同事的代码改成DDD风格的

DDD是领域驱动设计的简写。前段时间听群友说行业里少有DDD的代码案例,进而对DDD没有一个感性的认识。我想这是行业里普遍存在的现象吧。所以,我就有了写此文的想法。 本文开篇介绍了行业里比较普遍的代码风格,接着,我采用DDD风格对其进行修改。 我无意说服读者要按照我认为的DDD的风格来写代码,只是想告诉大家,这个世界上,还存在另一种代码风格。 如果各位觉得这样的风格好,可以尝试一下。非常欢迎大家反馈,平时太少人和我交流这些了。 文章标题说的是“同事的代码”,其实只是为了让此文更具传播,没别的意思。 如果你觉得此文对你有帮助,麻烦转发。干货好文不易。谢谢。 本文虽是以Java语言为案例演示,也希望对其它语言的读者朋友有帮助。 行业里普遍的代码风格,简称A风格 代码结构如下: ├── domain domain模块被同事认为是用于存放专门和DB打交道的类的地方 - src/main/java/com/xx/domain/account/repository/AbcLoginInfoRepository.java - src/main/java/com/xx/domain/account/AbcLoginInfo.java ├── repository-impl - 包路径太长省略/AbcLoginInfoRepositoryImlp.java ├── server - src/main/java/com/xx/server/login/LoginService.java - src/main/java/com/xx/server/login/LoginController.java - src/main/java/com/xx/server/login/AuthCodeVo.java - src/main/java/com/xx/server/login/UserInfoVo.java - src/main/java/com/xx/config/AbcWebMvcConfigurer.java Server模块 A风格下,整个业务系统的业务逻辑都在此模块中。 LoginController.java 实现http服务: @Controller @RequestMapping public class LoginController { @Autowired LoginService loginService; // 省略一些不重要的代码 @GetMapping(value = "/login") @ResponseBody public UserInfoVo login(String code) throws IOException { UserInfoVo userInfoVo = loginService.login(code, httpServletResponse); httpServletResponse.sendRedirect("/"); return userInfoVo; } @GetMapping(value = "/logout") @ResponseBody public boolean logout() { return loginService.logout(httpServletRequest,httpServletResponse); } } UserInfoVo.java是返回给前端的用户信息的结构体: ...

2024-02-26 · 4 min · 849 words · 翟志军 Jack Zhai

这10年,我所经历的领域驱动设计(DDD)

笔者2011年入行时,运气好,遇到了我的恩师simon杨。 当时,我们几个还不知道什么叫SSH(Spring、Struts、Hibernate)的毕业生和一个高级程序员基于DDDLib就开始实践领域驱动设计。现在想想还是觉得不可思议。一毕业就开始接触这门DDD技艺。 我记得当时simon杨经常谈如何利用抽象、解耦,在不增加复杂性的同时实现简单性、一致性、灵活性、可扩展性。至于如何实现CRUD,那是具体实现的问题,可以放在最后做。当他谈到某个精妙的设计时,眼神里都满是光。 在他的教导下,我就开始似懂非懂地读《领域驱动设计》、《企业应用架构模式》、《敏捷软件开发——原则、模式与实践》等这些高层设计类的书籍。 也许你会说,对于一个刚毕业的人学习这些“高层”设计,是不是太早了,毕竟你连CRUD都还不熟练。 在我工作10年之后看来,越早学习这些,越好。当你习惯使用CRUD的思维方式来看所有的问题时,你的思维方式已经固化,是很难接受DDD这套思维方式的。我尝试过说服不同工作经验的人使用DDD,无果。 2013年,在国内,也只有Jdon网站讨论DDD。而我们已经跟着simon杨在项目上实践DDD两年了。 2014年左右,为了验证了我是否真的理解DDD,我利用一次比赛的机会采用DDD的方式设计了一个BlackJack的游戏的核心。到此,我才算是对DDD有了一个更深入的理解。 但是,在2014年以后到现在的2021年,我就再也没有接触过DDD的项目了。而我,只能在自己的工作内容范围内实践,比如某个功能、某个模块。 值得说的是,我最近3年做的是DevOps相关的工作,但是DDD的思维方式依然能帮助我很好的完成工作。 比如在实现流水线时,将构建工具逻辑与流水线流程逻辑解耦、在设计 版本号时,将构建工具本身的版本号与流水线流程生成的版本号分离。 如果你不熟悉DevOps也没有关系,你只需要知道DDD贯穿着整个系统设计的每一个细节。 现在市场上把DDD吹得很高大上,似乎只有在架构上做DDD,才叫DDD。又或者非得和微服务关联在一起,才叫DDD。 其实不然,DDD的原意是领域驱动设计。只要你是使用领域知识来驱动你的设计,这个设计可以是方法级别的设计、类级别的设计、前端UI的设计等一切设计,你都可以叫DDD。 也许,这样的话听起来就像“色即是空,空即是色”一样让人不知所云。 没办法,10年了,我也没有找到非常好的让人一下就懂这种思维方式的方法。 如果非得要我介绍一下DDD的思维是什么,我觉得就是:不停地问问题是什么,解决方案是什么。然后优先从问题域着手设计。在确定问题域设计得差不多了,才开始实现解决方案。 这里要提个问,如果拿到一个需求时,先设计MySQL的表结构,再根据表结构导出类。这样的方式符合DDD的思维方式吗?如果不符合,与DDD的思维方式有什么区别? 最后,我想说的是《领域驱动设计》我读过3遍了,也实践这么多年了,我依然记不清“战略设计”、“柔性设计”等这些术语的准确定义。我也建议各位不要去记那些术语的定义,更不要去争论那些术语的定义,而是要从DDD的原意开始去理解DDD。也就是每当遇到一个术语,你就提问:它是帮助你领域驱动设计的呢。

2024-02-26 · 1 min · 20 words · 翟志军 Jack Zhai

Effortless English英文学习小结

学习英文最大的两个问题: 正确的学习方法 坚持 正确的学习方法,我是自认为是已经掌握了的。但是,我很难坚持下来。 Effortless English是我follow最长时间的英文教程。我断断续续听了有一年了。 有一段时间,我真正地做到按照课程里的听,并练习。然后在那段时间,我的大脑会不自觉地使用英文描述生活中的事情。这种状态是真正把英文内化的一个状态。 在两天前,我看了别人推荐《把你的英语用起来!》,书中提到了“坚持这件小事”,我才发现,在过去,我的问题出在坚持。 在看了《把你的英语用起来!》我没有坚持下来的原因是: 给自己的目标期望太高了; 没有养成正面的自我激励模式。 通常我们认为坚持会是一件痛苦的事情。然而,这是一个错误的认知。如果它是一件快乐的事情,坚持是不需要痛苦了。 所以,接下来,我需要针对性解决以上两个问题。 说回Effortless English课程的学习方法。 该课程由A.J. Hoge录制的纯音频的全英文课程(没有写作和阅读)。它由一系列的由易到难的mini-story组成。 每次课一个story,它又分成三部分内容(三个音频),每部分要达到的目标有所不同: 第一部分:几分钟mini-story陈述 目标:能听懂故事的内容 第二部分:介绍Mini-story中的重点单词 不需要刻意的记忆这些单词,因为在重复听和练习过程中,自然就记住了它们 第三部分:采用不同的时态对同一个story进行复述 目的是让你自然形成英文语法的语感 AJ会在课程中提问。问题包括两类: 问你懂的(如果你不懂,说明你没有听懂第一部分)。针对这类问题,你要做的就是快速回答。回答通常只需要一两个单词。 问你不懂的(需要你猜测的)。针对这类问题,你要立刻作出猜测性的回答。 这个过程像是锻炼你的大脑肌肉,对英文问题的回答形成肌肉记忆。 而且,AJ会从各个角度对同一件事情进行提问,问发生地点、发生时间、谁参与了……。这个过程,很像父母在教两岁左右的小孩学习说话。 以上是AJ Hoge每节课的pattern。但是随着Level的上升,课程里会逐渐出现复述、跟读的练习。 复述过程是最容易放弃的阶段。 说完内容,说说学习计划。 每次课都要坚持一个星期。每天至少一个小时(早上30分钟,晚上30分钟)。 如果遇到难的课程,延长学习该课的时长,直到你真正掌握该课。 最后,我想说,Effortless English能非常好地锻炼你英文听说的真正能力,如果你学习英文的主要目标是为了雅思拿个好成绩,这个课程不能在短时间内提升你的分数。

2024-02-19 · 1 min · 37 words · 翟志军 Jack Zhai

如何将DDD应用到基础设施设计?

前段时间在面试的时候,面试官问到:你是如何将DDD(领域驱动设计)应用到基础设施的? 我很惊讶,终于有人问我这个问题了。 在过去从事基础设施(DevOps、SRE、运维)的这5年里,我经常说起DDD是一种思维模式,可以应用到任何的领域,包括基础设施的设计。 但是,从来没有人像这位面试官问起我具体的做法。 为什么没有人问?原因大概是这两个概念通常是不会放在一起的。大多数开发不会深入理解基础设施的设计,而大多数从事基础设施设计的人是不会接触到DDD。而且,开发人员对于DDD的理解,也仅局限于用它开发业务系统。 我就是那少部分人,即做基础设施的设计,又觉得自己懂DDD的人。 说回问题本身。 我所理解的DDD 我首先会向提问的人澄清我所理解的DDD。 为什么要这样呢?很久以前有一次面试,因为我说我擅长DevOps,面试官就认为我不懂GitOps。然后在这个点上他就认为我不适合,不再问我DevOps方面的问题。我只能说没有缘份。 我是这样解释DDD的: 就像开发一个象棋游戏,不论你要开发手机端,还是web端,象棋规则本身都是不变的。这个规则本身就可以理解为“领域”。 其它所有的技术(包括架构)都是具体实现,它们应该由“领域”来驱动设计。 当时的解释与以上的解释大差不差。 将DDD应用到基础设施设计的具体做法 那么该如何将DDD应用到基础设施的设计呢? DDD的思维方式要求我们首先问:我们要设计的软件的领域(核心)问题是什么? 基础设施的领域问题是什么?我的回答是配置。 我认为基础设施的搭建、维护,本质就是配置的设计、部署、维护。 寻找领域(本质)问题的能力是DDD的核心能力。 为了让读者更好理解,我们以一个一个基于云上的虚拟机的分布式系统为例。它的基础设施就包括:vpc、LB、MQ、DB等。 要搭建、维护这一套基础设施。 根据“配置管理是基础设施设计的核心问题”,我首先将基础设施的所有的配置放在清单代码(并不一定是一个文件)中,如下: vpc: # .... LB: # ... DB: # ... MQ: # ... APP1: mq_addr: "{{ MQ.addr }}" db_host: "{{ DB.addr }}" APP2: mq_addr: "{{ MQ.addr }}" db_host: "{{ DB.addr }}" app1_addr: "{{ APP1.addr }}" 从清单中,你看不出它使用何种部署方式、部署顺序。你只知道APP1引用了MQ和DB,APP2会调用APP2这样纯粹的领域知识。 现实通常是多个环境,所以,我一开始就会将不同环境的值从清单上抽离到独立的文件夹中。 第二步,我才会考虑如何部署它们。这时,我通过两个工具实现: Terraform负责云基础设施; Ansible负责业务基础设施。 Ansible是可以直接读取我们的YAML格式配置文件。而Terraform代码引用YAML代码,就没有那么方便了。 ...

2024-01-27 · 1 min · 78 words · 翟志军 Jack Zhai

程序员想告赢开发商一户一表违约

导言 正如标题所言,我是一名程序员。2023年5月之前,我是一个法盲,不懂起诉的流程,更不懂开庭的步骤。 但在过去的一年中,我以各种身份参与庭审多次,包括: 以被告的公民代理的身份,为被告辩护,参加庭审:1次 以原告的公民代理的身份,为原告辩护,参加庭审:1次 以原告的身份起诉开发商:2次 以起诉人身份参加二审:1次 以旁听的身份参加庭审:2次 多次为小区其他业主免费写起诉状、答辩状、再审申请书等多份文书。 但是结果是什么呢?后文会说。如果你只想知道官司输赢,可以直接划到文章最后。 本文比较长,你可以挑选自己感兴趣的部分开始。 如果您觉得本文有意义,还请转发给其他遇到相同问题的业主,以帮助更多的人。如果帮到你,还请用实际行动赞赏本文。 你我的权益,需要法制社会来保护,也需要你我的努力。 背景介绍 我是2017年购买的商品房,坐落在一个18线的县城。开发商是市里的一家房地产开发商。 但是直到现在小区还是由物业代抄电表,代收电费交给开发商。 在2020年和2022年的两次水灾中,其它实现了一户一表的小区,供电局很快就恢复供电了。但是我们小区停电了半个多月,因为开发商需要自己找人去修该变压器。 这里有两个背景知识: 开发商需要将小区的变压器的产权无偿移交给供电局,变压器的维护才由供电局负责; 一户一表:说大白话就是由业主与供电局直接发生供用电关系,而不是由开发商代缴电费。 业主是2020后才知道小区并不是一户一表的。所以,部分小区业主从那时开始拒绝“交电费”。开发商只能为这部分业主“垫付”电费。 2023年5月,开发商不再为业主“垫付”电费。小区因此被供电局停电。 接下来,小区业主与开发商、镇政府、供电局、住建局等多方,进行长时间地“拉扯”(中间的故事可以再写一篇文章,但是考虑到篇幅,本文不详写)。 虽然现在小区有电用,但至今小区依然没有实现一户一表。这就是多方拉扯的结局。懂的都懂。 其间,我开始研究业主与开发商之间的合同《商品房买卖合同》(由国家住建部和国家工商局2014年制定的格式合同),商品房验收条件中,关于电的部分,合同上的原文是这样的: 供电:交付时纳入城市供电网络并正式供电。 也就是合同里并没有写明“一户一表”。也请读者朋友拿出自己的合同,找到相关条文确认。因为合同一字之差就差个十万八千里。 根据这条合同条文,我们不能以“未实现一户一表”来起诉开发商。只能起诉开发商没有实现“交付时纳入城市供电网络并正式供电”。 本文为了方便,会将两“一户一表”与“交付时纳入城市供电网络并正式供电”混用,但是它们意思大致相同。注意,在庭审过程,它们不是同一事物。 这里再次提醒读者:起诉开发商,必须以合同为依据起诉。 以上简单介绍了背景知识(实际情况更复杂)。接下来解答一些读者会提出来疑问: 为什么要写下这些 在前几天,我刚刚结束了二审(判决结果还没有出),我决定将整个经历写下来。原因有二: 一是因为我这人记性不好,想记录下这段有意义的经历;二是希望为法制社会做出一点点微薄的贡献。 为什么不找律师? 我在找律师前做了很多功课,比如起诉开发商涉及的法律条文、开发商会如何抗辩等。但是,在找过多家律师所后,我最终决定不找律师。 原因有: 本县城的律师不愿意为我们打官司,有律师认为钱太少,有律师听到小区名字就说不打。部分业主甚至愿意与律师平分违约金。 隔壁县城的律师找了两个,我个人判断不可靠。当我拿出《合同》与他们讨论其中的条文时,他们并没有给出令我满意的答案,而且整个市的律师都在同一个律师协会里,懂的都懂; 省会的律师不太可能受理我们这种小金额官司。连他们的差旅费,我们可能都给不起; 小区里并不是所有的人都愿意打这个官司(这个是关键)。 我以个人名义打官司,就不会有以上问题了。 为什么不是起诉物业? 说白了,小区物业就是开发商的一家子公司。所以很多业主经常误以为物业就是开发商,毕竟“交电费”是直接交给的物业的。 小区的几次停电,部分业主跑去物业那里“闹”,这是找错对象了。因为我们小区的供电主体是开发商,不是物业。 再多说一句,如果物业没有代缴电费的权力(具体要看物业合同),所以他们也无法起诉你不交电费给他们。 为什么不是起诉开发商不移交变压器? 这在上文已经说明了,要以合同为依据来起诉。合同里并没有写“移交变压器”的条文。 集体诉讼,还是一个个单独诉讼? 在没有接触真正的诉讼流程前,我也以为我们是可以集体诉讼的。 但是在学习后,发现中国是没有集体诉讼的概念的。只能单独诉讼。如果我错了,还请纠正我。多谢。 假如有律师接受整个小区对开发商的诉讼,律师也是分别和每一位业主签法务合同。律师只不过是批量操作,并不是真正意义的集体诉讼。 我可以代理其他人的诉讼吗? 如果没有集体诉讼,那么,我将面临两个问题: 我是自己起诉开发商,还是和其业主一起? 我可以代理其他业主的起诉吗?并不是每一个业主都有时间。 对于问题1,经过深思熟虑,我做出的策略是:我以个人名义单独起诉。 因为我完全没有诉讼经验,不知道法官和开发商会如何抗辩。即使我本人这次官司输了,其他人也可以以我的诉讼经验发起另一次诉讼,直到打赢。 关于问题2,你是可以以公民代理的身份代理你的邻居或者亲戚的诉讼。中国不允许没有律师执照的人为其他人代理诉讼,公民代理算是对这机制的补充。如果我理解错了,还请纠正,谢谢。 公民代理的方法是: 取得证明你们关系。 如果是邻居关系,就拿双方的购房合同或者房产证去当地居委会开证明; 如果是亲戚关系,就拿双方的户口去公安局、居委会或者村委会。可能每个地方不一样,你需要咨询当地的居委会或者村委会; 准备授权委托书,即你的邻居或者亲戚将案件委托给你的证明; 在提交起诉状或者开庭前向法庭提交双方身份证复印件、委托书和关系证明。 以上是我个人的总结。但是,还是建议有需求的读者,请咨询当地法院。 题外话,我在给邻居代理时,发生了两件“有趣”的事情。 一件是当我给其中邻居代理房产证逾期的诉讼时,负责立案的”漂亮“的小姐姐,气急败坏似地不给任何理由地拒绝了我的代理。这超出了我认知。她不是在法院知法犯法吗? 另一件也是这个“漂亮”的小姐姐,我在立案时,故意刁难我,本来可以使用微信线上交纳诉讼费的,却跟我说法院规定月底最后三天,需要线下去银行柜台汇款。 当时,我不清楚,天真去线下交了,浪费了我大量时间。后来有一次也是月底,我看到其他所有人都不需要线下汇款。我想法院应该是没有任何理由拒绝我线上支付的。如果有懂相关法律的读者可以告诉我。 ...

2024-01-22 · 3 min · 467 words · 翟志军 Jack Zhai

Bazel使用案例:构建Springboot工程

本文是关于如何使用Bazel搭建Springboot 3.1.0工程(基于JDK17)。为什么使用Bazel,而不是使用Maven或者Gradle?可以看我之前关于Bazel的介绍文章。 前期准备 在根目录加入.bazelversion文件,并加入6.2.0,指定当前工程使用的Bazel的版本。这样,Bazel命令自动使用该版本的Bazel进行构建。 在根目录加入.bazelrc文件,并指定构建和测试时使用JDK17,内容如下: build --java_language_version=17 --java_runtime_version=17 --tool_java_language_version=17 --tool_java_runtime_version=17 test --java_language_version=17 --java_runtime_version=17 --tool_java_language_version=17 --tool_java_runtime_version=17 外部依赖准备 在根目录中创建以下两个文件: WORKSPACE:在Bazel中,所有的外部依赖统一定义WORKSPACE文件中; BUILD.bazel:内容留空即可,用于告诉Bazel当前目录也是一个Package。 Bazel本身是支持多语言的。所以,我们需要特定语言的rule来帮助我们在WORKSPACE中定义外部依赖。 对于Java工程,我们使用rules_jvm_external进行外部依赖的管理。它的使用步骤如下: 步骤1:在WORKSPACE中增加rules_jvm_external配置 以下配置指定了rules_jvm_external的下载位置,并进行rule的初始化: load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") RULES_JVM_EXTERNAL_TAG = "4.5" RULES_JVM_EXTERNAL_SHA = "<sha hash value>" http_archive( name = "rules_jvm_external", strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, sha256 = RULES_JVM_EXTERNAL_SHA, url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, ) load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps") rules_jvm_external_deps() load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup") rules_jvm_external_setup() load("@rules_jvm_external//:defs.bzl", "maven_install") maven_install( artifacts = [ # The project's dependencies "junit:junit:4.12", "org.hamcrest:hamcrest-library:1.3", ], repositories = [ # Private repositories are supported through HTTP Basic auth # "http://username:password@localhost:8081/artifactory/my-repository", "https://maven.aliyun.com/repository/public", ], ) 以上采用了非Bzlmod的管理rule。 ...

2024-01-19 · 3 min · 461 words · 翟志军 Jack Zhai

DevOps架构师是如何看待Github Actions的共享制品解决方案的?

前言 Github Actions是Github提供的一个CICD Pipeline服务。除了Pipeline,它还提供Secret和简单的配置管理。 本文并不是它的一个完整介绍和知识的罗列。而是我在实际使用Github Actions后,对Github Actions的“共享问题”的解决方案的总结。 不要小看这个问题,它是所有的Pipeline平台(包括Gitlab CI)都会遇到的问题。只要对这一问题深入理解,所有的平台一通百通。 提示1:下文可能会是Workflows和Pipeline两个术语共用。因为它们本质上就是同一个东西,只是不同平台不同的叫法。 提示2:下文可能会共用DevOps平台和Pipeline平台,虽然它们可能是完全不同的平台,但是在本文中,它们都是能提供Pipeline的平台。 共享问题 只要是Pipeline平台,都会遇到共享问题。那么,什么是共享问题? 共享问题就是Pipeline中不同的位置之间共享资源,以实现不重复执行、生成准确结果的目标。 定义听起来有些枯燥。我们列举一个有共享的场景,就比较好理解了: 比如对于一个单仓库,它同时包含多个前端工程,这些前端工程同时依赖于一个common模板。其它前端工程只有等它构建完成,并取得构建,才能开始自己的构建。如果common模板的构建结果不能被其它工程构建共享使用,就会存在构建结果不一致、重复构建的问题; 比如一个Pipeline中,版本号是有特定格式的,需要在第一步骤计算出来后,其它步骤取得这个版本号,进行打包工作。如果没有实现版本号在多个步骤之间共享,很可能会导致版本号不一致问题。 我们稍微对共享问题进行抽象和理解,根据共享的范围,共享问题,可以分为: Workflows之间进行共享; Workflow内的Jobs之间进行共享; Job内的Step之间进行共享。 根据共享的内容,可以分为: 共享源码; 共享制品; 共享变量。 Github Actionsr制品的定义 在Github Actions中的制品(Artifact)的概念和我们平时所说的“制品”有一定的区别。在Github Actions中,制品指的是Job生成的文件或者文件夹。 我们平时所说的,更广意的“制品”,在Github Actions叫Packages。 Workflows之间的共享制品 一般只有在大型项目才会存在Workflows之间的共享。而我个人是不建议将依赖Pipeline实现大型项目的构建的,而是依赖构建工具本身的能力。 由于笔者时间有限,不再亲身做实验,本节内容,请读者自行测试。 如果Workflow是由workflow_run事件触发的情况下,它们就可以直接使用actions/upload-artifact和actions/download-artifact两个actions来实现制品的共享。相关文档:https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts#downloading-artifacts-during-a-workflow-run 有趣的是Github Actions提供了一种reusable的Workflow概念。说到底是一种模板化Workflow的方式。但是这种方式不适合用来实现共享制品。因为它并不是共享制品。相关文档:https://docs.github.com/en/actions/using-workflows/reusing-workflows#using-outputs-from-a-reusable-workflow Jobs之间共享制品 在同一个Workflow中,多个Job进行制品共享。如下代码: jobs: // 生成制品的job build: runs-on: ubuntu-latest steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and export uses: docker/build-push-action@v5 with: context: . tags: myimage:latest outputs: type=docker,dest=/tmp/myimage.tar - name: Upload artifact uses: actions/upload-artifact@v3 with: name: myimage path: /tmp/myimage.tar // 使用制品的job use: runs-on: ubuntu-latest needs: build steps: - name: Download artifact uses: actions/download-artifact@v3 with: name: myimage path: /tmp - name: Load image run: | docker load --input /tmp/myimage.tar docker image ls -a 以上案例来自:https://docs.docker.com/build/ci/github-actions/share-image-jobs/ ...

2024-01-16 · 2 min · 290 words · 翟志军 Jack Zhai

Ebean:一款被低估的ORM框架

ORM框架为什么不香? 对ORM框架的偏见 看了一些MyBaties与Hibernate进行对比的文章。可能是因为一些Hibernate历史原因,国内对于Hibernate普遍存在偏见,我摘抄了几点: hibernate是全自动,而mybatis是半自动 hibernate完全可以通过对象关系模型实现对数据库的操作,拥有完整的JavaBean对象与数据库的映射结构来自动生成sql。而mybatis仅有基本的字段映射,对象数据以及对象实际关系仍然需要通过手写sql来实现和管理。 sql直接优化上,mybatis要比hibernate方便很多 由于mybatis的sql都是写在xml里,因此优化sql比hibernate方便很多。而hibernate的sql很多都是自动生成的,无法直接维护sql 应用场景 MyBatis 适合需求多变的互联网项目,例如电商项目、金融类型、旅游类、售票类项目等。 Hibernate 适合需求明确、业务固定的项目,例如 OA 项目、ERP 项目和 CRM 项目等。 也不知道是不是因为这些对Hibernate的偏见,导致大家对ORM框架也普遍存在偏见。 现状是不论大小公司,国内清一色地使用MyBaties。有时,我都不敢说,我喜欢使用ORM框架。 本文并不是一篇为Hibernate洗地的文章,而是介绍另一款比较小众的ORM框架:Ebean。 领域问题分析 介绍Ebean之前,我们需要弄清楚一个问题:为什么会有MyBaties和ORM这些框架?对于这个问题,我们无从下手,那么,我们将问题倒置:如果没有这些框架,会怎么样? 问题倒置的好处是我们立马就有了可下手的方向。我们找到了不使用框架的情况下,Java代码与数据库进行交互的代码: public static void viewTable(Connection con) throws SQLException { String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES"; try (Statement stmt = con.createStatement()) { ResultSet rs = stmt.executeQuery(query); while (rs.next()) { String coffeeName = rs.getString("COF_NAME"); int supplierID = rs.getInt("SUP_ID"); float price = rs.getFloat("PRICE"); int sales = rs.getInt("SALES"); int total = rs.getInt("TOTAL"); } } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } } 这样的代码存在什么问题呢? ...

2024-01-02 · 2 min · 336 words · 翟志军 Jack Zhai

PostgreSQL15 Public Schema没有权限问题解决

PostgreSQL15后,Public Schema的权限发生了变化:普通用户默认在Public schema中不再有CREATE的权限。当他们执行CREATE TABLE命令时,就会报以下错误: ERROR: permission denied for schema public 所以,我们需要为该用户再分配权限。命令如下: GRANT USAGE, CREATE on SCHEMA PUBLIC to <username>; 因为某些应用程序的sql的migration是自动的,你可能还需要为用户分配更多权限,命令如下: grant all on database <db_name> to <username>; ALTER DATABASE <db_name OWNER to <username>;

2024-01-01 · 1 min · 33 words · 翟志军 Jack Zhai