Skip to main content

Git工程实践

· 3 min read

2015年12月8号的时候,注册了github。正是通过github接触了git,那时对于我而言,只是觉得从一个svn工具切换到另外一个git工具,使用上并无太大的差别。后期使用的过程中踩过一些坑,通过官方的教程,学习了git内部的原理,通过做实验慢慢的加深了对git的理解。这里将一些经验上的总结、实践分享出来,希望能够帮助各位在git的学习之路上少走些弯路。

主要特性#

  • 基于快照的版本管理
  1. 本地搭建实验环境,新建一个空的文件夹test,在该目录下初始化空的git项目。

    mkdir test; cd test; git init
  2. 新建h.txt,将hello world写入到h.txt中

    echo 'hello world' >> h.txt
  3. 添加文件并提交代码

    git add .; git commit -m 'feat: add h.txt'
  4. 追加hello world到h.txt中

    echo 'hello world' >> h.txt
  5. 提交代码

    git commit -am 'feat: modify h.txt'
  6. 查看文件h.txt的提交记录

    git log --pretty=oneline h.tx# 日志展示的内容如下:# 7fa1b21d9b0defe1c2feb8be7bfc3e662bcc31a9 (HEAD -> master) feat: modify h.txt# aa56da444f61cec3a80e231c8b9d66cd75dd8f2b feat: add h.txt
  7. 查看h.txt第二次提交的文件内容

    git cat-file -p 7fa1b21d9b0defe1c2feb8be7bfc3e662bcc31a9# commit hashgit cat-file -p bb0ae28448e6c19fdbe294df1a518edd92cdf7f8# tree hashgit cat-file -p 10bda51843e912c86901eea164d0b3f7c9a56df3# blob hash# h.txt文件的内容# hello world# hello world
    
  1. 查看h.txt第一次提交的文件内容

    git cat-file -p aa56da444f61cec3a80e231c8b9d66cd75dd8f2b# commit hashgit cat-file -p 5ee620adb265fa7cb43f76e641da23cec27cd4d9# tree hashgit cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad# blob hash# h.txt文件的内容# hello world 

    通过上述几步的实验操作,可以加深对git快照特性的理解。这里留个任务给各位

    新建一个空的git项目,添加一个50M左右的文件并提交,之后在该文件的末尾出新增一行记录并提交。根据git快照的特性,两次提交后,总的大小应该大于等于100M,通过命令du -sh <git项目名>查看该项目总的大小 ,是否跟你想的一样?如果不是,git是如何处理的。

  • 可离线开发

    这是git区别svn的主要特性之一,通过git help列出常用的命令。

    These are common Git commands used in various situations:
    start a working area (see also: git help tutorial)   clone             Clone a repository into a new directory   init              Create an empty Git repository or reinitialize an existing one
    work on the current change (see also: git help everyday)   add               Add file contents to the index   mv                Move or rename a file, a directory, or a symlink   restore           Restore working tree files   rm                Remove files from the working tree and from the index   sparse-checkout   Initialize and modify the sparse-checkout
    examine the history and state (see also: git help revisions)   bisect            Use binary search to find the commit that introduced a bug   diff              Show changes between commits, commit and working tree, etc   grep              Print lines matching a pattern   log               Show commit logs   show              Show various types of objects   status            Show the working tree status
    grow, mark and tweak your common history   branch            List, create, or delete branches   commit            Record changes to the repository   merge             Join two or more development histories together   rebase            Reapply commits on top of another base tip   reset             Reset current HEAD to the specified state   switch            Switch branches   tag               Create, list, delete or verify a tag object signed with GPG
    collaborate (see also: git help workflows)   fetch             Download objects and refs from another repository   pull              Fetch from and integrate with another repository or a local branch   push              Update remote refs along with associated objects
    'git help -a' and 'git help -g' list available subcommands and someconcept guides. See 'git help <command>' or 'git help <concept>'to read about a specific subcommand or concept.See 'git help git' for an overview of the system.

    上面的命令除了clonefetchpullpush,其他的命令都可在本地完成操作。

经验总结#

  • 慎用GitLab中的冲突解决, 除非你知道它的命令组合

    git checkout "source branch"git merge "target branch"
  • 只在属于自己的分支中执行rebase,否则只会将提交历史变的更加混乱

  • 慎用revert,除非你知道revert是在原有的基础上做了相反的操作。如果恢复的话,需要执行 revert revert操作

工程实践#

实用工具#

  • git commit hook 通过hook做一些事前检查,比如检查提交是否符合Angular规范 commit-hook
  • changelog生成器 配合自动化工作流,自动生成changelog changelog
  • rewrite history 可以用来更改邮箱地址、删除大文件、拆分目录等
  • draw image 通过绘制图形的方式,可视化的展示了git内部的结构 changelog

分支策略#

关于分支模型最理想的模式为trunk based development,实际使用过程中很少有团队能够做到。实践过程中比较好的分支模型:Aoneflow,通过将多个特性分支,合成一个release的方式,实现“准持续集成”

回退流程#

日常开发过程中,有时不小心会污染了Master或者Main分支。此时需要一种方式来回滚代码

  1. gitlab中将MasterMain分支改成非默认分支
  2. 删除MasterMain分支
  3. 从当前稳定的分支中拉出一个全新的 MasterMain分支
  4. MasterMain分支设置为默认分支

权限管理#

代码作为软件企业的核心资产,核心的算法及设计方案作为核心中的核心,只允许少部分人有权限查看,这时就需要权限上的管控。git本身提倡的是开放,权限管理不是它的强项,需要借助hook实现。git无法做到目录级别的权限管控,需要将目录拆成单独的项目。gitlab中提供了几类角色(访客、报告者、开发者、管理者),让我奇怪的是为啥gitlab不实现rbac(如果有的话,应该是个亮点),实际企业内部开发的过程中可能不止这些角色。对于多角色的使用场景,实际操作过程中可以给开发者授予访客的角色,研发负责人授予开发者的角色,运维负责人授予管理者的角色。

评审机制#

  • 基于提交 如果对代码的质量有较高的标准并且团队所有成员都认同该方式,可以考虑引入gerrit
  • 基于分支合并 在合并分支代码的时候,完成代码的审查。一种较宽松的方式,也是我觉得企业内部可推行的方式。

大概在2020年9月份的时候,读到了蒋鑫先生的文章AGit-Flow 阿里巴巴集中式 Git 工作流,蒋先生列的几点特征

  1. 仓库的授权模型简单。无需为项目设置复杂的授权,只读用户亦可参与代码贡献。
  2. 通过代码评审提升代码质量。参与者不是将代码直接推送的分支上,而是创建代码评审。
  3. 仓库的分支模型简单。仓库中没有多余的分支,即不需要创建特性分支。

一方面能够解决了分支策略中多分支的问题,另一方面也解决了权限管理中权限的问题。在git2.29版本已包含蒋先生文章中的proc-receive 。目前阿里云的云效已实现该特性,感兴趣的可以试试。gitlab目前还没有该功能,自己集成的话,蒋先生提供了一些思路。(https://github.com/alibaba/git-repo-go/issues/3)

单体仓库#

数据库脚本版本管理工程实践

· 2 min read

已经好久没写博客了,最近一直在研读leveldb的源码,进展不是很顺利。想要写出一篇自己的见解还要一段时间。正好最近在数据库脚本版本管理控制方面有一些自己的想法与实践,拿出来与各位分享。

需求#

  • 数据库脚本可以用git进行版本管理
  • 数据库脚本可以像普通代码一样纳入code review流程(Gitlab、Gerrit等)进行脚本合规的审查
  • 数据库脚本可以与CI集成,作为pipeline中的一个环节。通过与上一版本进行比较,生成待上线的脚本及对应的回退脚本。

思路#

关于数据库脚本版本管理方面的工具,我在网上也搜了很多工具,比如:flyway、liquibase、uml建模工具等,但是这些工具都不是我想要的。这里简单讲下我的思路:

  1. 在代码仓库下创建数据库脚本的仓库进行脚本的维护
  2. 每个系统一个文件夹,文件夹下放入系统英文名称.sql的文件(比如订单系统叫oms,则脚本文件的名称为oms.sql)
  3. 脚本文件中只放创建表语句,每次脚本的修改,只是对表、列的属性做增删改。

基于以上,实际需要的能够解析DDL中建表语句的工具,通过对比两个版本的DDL生成对应的diff文件。基于这样的诉求,在github上找到了这样的工具,工具的名称叫schemalex

实践#

  1. 本地环境创建个文件夹并初始化git仓库
$ mkdir demo$ cd demo$ git init
  1. 创建demo.sql文件,初始数据为
CREATE TABLE student ( id INTEGER NOT NULL AUTO_INCREMENT,    PRIMARY KEY (id));

  1. 将demo.sql文件保存到本地工作区并打上tag
git add .git commit -m 'add demo.sql'git tag -a v1.0 -m 'add demo.sql'
  1. 修改demo.sql,修改后的数据为
CREATE TABLE student (    id INTEGER NOT NULL AUTO_INCREMENT,    name VARCHAR (64) NOT NULL DEFAULT "",    PRIMARY KEY (id));
CREATE TABLE course (    id INTEGER NOT NULL AUTO_INCREMENT,    name VARCHAR (64) NOT NULL DEFAULT "",    PRIMARY KEY (id));
  1. 保存文件到本地工作区
git add .git commit -m 'modify demo.sql'
  1. 执行脚本./gen.sh /root/demo demo,脚本的第一个参数为数据库脚本文件夹的全路径,第二个参数为数据库脚本名称
#!/bin/bash#set -e
dst_dir=$1sys_name=$2if [ -z "$sys_name" ]; then    echo "usage gen_script.sh dst_dir sys_name"    exit 1fi
script_file=${sys_name}.sql
cur_dir=$(pwd)cd "$dst_dir"
if [ ! -f "$script_file" ]; then    echo "$script_file not be found"    exit 1fi

# test git commandif ! command -v git &> /dev/null; then    echo "git cound not be found"    exit 1fi


if ! git checkout master &> /dev/null; then    echo "checkout master failed"    exit 1fi
current_file=$(mktemp)
if ! cp "$script_file" "$current_file" &> /dev/null; then    echo "copy $script_file $current_file failed"    exit 1fi

# list latest taglatest_tag=$(git describe --tags "$(git rev-list --tags --max-count=1)")
if [ -z "$latest_tag" ]; then    echo "current repo no tag."    exit 1fi
if ! git checkout "$latest_tag" &> /dev/null; then    echo "checkout tags failed"    exit 1fi
tag_file=$(mktemp)if ! cp "$script_file" "$tag_file" &> /dev/null; then    echo "copy $script_file $tag_file failed"    exit 1fi
rollback=$(schemalex "$current_file" "$tag_file" )echo "$rollback" > "$cur_dir"/rollback.sql
diff=$(schemalex "$tag_file" "$current_file" )echo "$diff" > "$cur_dir"/diff.sql
  1. 最终会生成两个文件:diff.sql和rollback.sql
  • diff.sql
BEGIN;
SET FOREIGN_KEY_CHECKS = 0;
CREATE TABLE `course` (`id` INT (11) NOT NULL AUTO_INCREMENT,`name` VARCHAR (64) NOT NULL DEFAULT '',PRIMARY KEY (`id`));
ALTER TABLE `student` ADD COLUMN `name` VARCHAR (64) NOT NULL DEFAULT '' AFTER `id`;
SET FOREIGN_KEY_CHECKS = 1;
COMMIT;
  • rollback.sql
BEGIN;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE `course`;
ALTER TABLE `student` DROP COLUMN `name`;
SET FOREIGN_KEY_CHECKS = 1;
COMMIT;

结语#

虽然schemalex对脚本语句上有一些限制,但是基本满足了我对数据库版本管理的诉求,以上是我在数据库版本管理上的一些实践,供各位参考。

联系方式#

  • 欢迎订阅我的公众号,这里主要会发表些软件工程上的一些想法及实践 微信公众号

如何阅读源码

· One min read

出于某一些原因(工作的需要、个人技术上的自我提升),时常需要阅读一些开源项目的源码。面对一大坨的代码,该如何阅读源码?

目的#

首先,我是不建议上来就阅读源码的。很多功能点是可以参照官方文档,以做实验的方式去学习、掌握、加深印象的。通常我阅读源码的目的主要有:

  • 学习架构上的设计、单测、集测的编写以及文档的书写
  • 加深在某一技术栈上知识的积累
  • 在其现有功能基础上做定制化开发

选型#

可以选择工作中常用的开源项目或者未来一段时间内非常有前景的开源项目。刚开始的时候不建议读中大型项目的源码,原因有几点:

  • 会消耗大量的时间,投入产出比不成正比
  • 需要更高的素质、更广的知识面
  • 打击积极性

工具#

使用自己熟悉的工具,好的工具会令你事半功倍。通常我用到的工具有:

  • Visual Code 轻量级文本编辑器,其提供了很多语言的插件。
  • Visual Studio 主要用来阅读C++、C#的代码,它的debug功能是我目前用过的工具中最好用的
  • Jetbrains全家桶 主要用来阅读java、go、python的代码
  • Visual Paradigm 主要用来绘制类图、时序图

方法#

好的方法,有助于快速的阅读源码,达到自己预期的效果。通过我用到的方法有:

  • 调试 通过debug可以知道程序执行过程中的交互流程
  • 打日志 开启debug级别的日志,通过观察日志的输出,猜出程序的大致执行流程
  • 识别 识别流程中的主要步骤,过滤次要步骤。先理清程序的骨架,再逐一击破
  • 跑用例 一般好的开源项目中,都会提供丰富的用例。可以通过跑用例,了解某一功能的使用、实现逻辑

案例#

java开发中通常会用到mybatis,mybatis作为ORM框架,有着简单易用等特点。 这里以mybatis-3的源码举例,整个mybatis不含测试用例的代码量为37189,如果没有一定的方法看着万行代码还是挺累的。

核心功能#

  • 加载配置 加载配置的过程,就是为了构造Configuration类,mybatis中关键的几个类,都跟Configuration类有着千丝万缕的关系。 altalt
  • 执行SQL 通过SqlSession、Executor、StatementHandler这几个类的协作,最终通过Statement完成SQL的执行。 altalt

结语#

这里不鼓励阅读源码,很多时候可以通过做实验的方式掌握某一门知识。通过调试打日志识别跑用例几种方式,便于我们阅读源码,掌握其核心功能。

联系方式#

  • 欢迎订阅我的公众号,这里主要会发表些软件工程上的一些想法及实践 微信公众号

构建单体仓库

· One min read

本文主要介绍在单体仓库上的一些思考及实践,至于是选择单仓还是多仓,需要根据实际的情况去做选择。

优势#

  • 代码共享和重用

    因为只有一个仓库,每个人都可以看到其他人写的代码。在写新的工具或者通用功能时可以在仓库中查找是不是已经有现成的代码了,避免重复的造轮子

  • 单一版本管理

    可以直接依赖其他团队的代码, 而不是使用其提供的类库。使用类库会存在着钻石依赖问题。 alt

  • 方便重构

    可以使用重构工具,对仓库中的类、方法等进行重命名。而不用担心有任何遗漏的地方

Git单体仓库上的实践#

实验环境: Win10git版本: 2.26.2VFSForGit 版本: 1.0.20112.1克隆对象:https://github.com/torvalds/linux
  • 部分克隆和稀疏检出

    从2.25.0开始支持的特性,部分克隆可以避免下载不必要的对象,从而减少下载的时间。稀疏检出 客户端可以检出特定的目录。通常是两者搭配使用

$ git clone --filter=blob:none --no-checkout https://github.com/torvalds/linux$ cd linux/$ git sparse-checkout init --cone$ git sparse-checkout set init/usr # 这里只检出init和usr目录
  • VFSForGit

    微软开发的产品,本地的Git库副本都是虚拟化的。只包含元数据和必要的源代码文件。 alt

$ gvfs clone https://lokiworks@dev.azure.com/lokiworks/linux/_git/linux # 只需要将git命令换成gvfs,因为github目前不支持gvfs协议,所以将linux代码克隆到个人的代码仓库下$ cd src/ # 所有的代码文件全都放在src文件夹下

结语#

  • 单体仓库固然好,但是并不适合每一个企业
  • git目前在大型仓库上支持的并不好。即使使用了部分克隆稀疏检出,克隆的速度依然很慢
  • VFSForGit固然很好,但是目前也只有Azure DevOps、Bitbucket支持gvfs协议。希望未来在github、gitlab平台上也能使用gvfs。

参考#

联系方式#

  • 欢迎订阅我的公众号,这里主要会发表些软件工程上的一些想法及实践 微信公众号

软件设计

· 3 min read

本来是想写篇软件分层的文章,想想介绍的内容不单单是要阐述软件分层的思想。下面以一个具体的例子进入主题

骨架工程#

骨架工程的目的是将软件开发过程中的最佳实践、可重用的代码引入到框架中,形成一套标准。方便他人能够快速的融入到团队的开发规范中,更快、更有效的开发代码,将精力都集中在业务场景下的代码逻辑上,避免了不必要的分心。

名词解释#

术语全称说明
DTOData Trafer Object
DAOData Access Object
DODomain Object
appapplication
intgrintegration
vovalue object

目录结构#

├── arch-app       # 包含了一系列@RestController├── arch-domain    # 包含了一系列@Service和@Component和DO实体模型├── arch-dal       # 包含了一系列@Mapper和DAO实体模型├── arch-intgr     # 包含了一系列其他业务的xxx-client及第三方平台sdk的封装├── arch-util      # 包含了一系列XXXUtils工具类├── arch-api       # 包含了出入参的DTO实体模型以及API接口└── arch-client    # 包含了一系列@FeignClient

结构图#

software-design 打包时会将arch-apiarch-client部署到私服中,供其他业务使用方使用

  • arch-app 引用了arch-api、arch-domain、arch-intgr,所有的@RestController都实现了arch-api定义的接口
  • arch-domain 引用了arch-dal和arch-util
  • arch-client 引用了arch-api

优化版本V1#

  1. arch-domain如何使用arch-api中的实体模型?

    可以从arch-api中分离出arch-api-bean, arch-domain引用arch-api-bean

  2. arch-domain如何使用arch-app中的实体模型?

    arch-app引用了arch-api,理论上不会需要自定义实体。实际操作时并不需要把所有的接口都暴露给业务使用方,只暴露业务使用方需要的接口,所以arch-app中会存在实体模型。 可以从arch-app中分离出arch-app-bean,arch-domain引用arch-app-bean

  3. arch-client中如何提供扩展点给业务使用方做业务降级? 通过Spring提供的ApplicationContext获取业务使用方自定义的降级对象

@FeignClient(value = "xxx", path = "api/v1/xxx", fallbackFactory = XXXClientFallbackFactory.class)public interface IXXXClient extends IXXXApi {    @Override    @RequestMapping(value = "xxxAction", method = RequestMethod.GET)    ResponseEntityDTO<DemoRespDTO> xxxAction(@RequestParam(value = "name") String name);}
@Componentpublic class XXXClientFallbackFactory  extends AbstractFallbackFactory<IXXXClient> {    @Value("${xxx.xxxClientExtend}")    private String xxxClientExtend;
    @Autowired    ApplicationContext appContext;
    @Override    public IXXXClientClient createCustomObject() {        if (appContext != null){            if (StringUtils.isNotBlank(xxxClientExtend)){                return  (IXXXClientClient) appContext.getBean(xxxClientExtend);            }
        }        return null;
    }}
@Slf4jpublic abstract  class AbstractFallbackFactory<T> implements FallbackFactory<T> {
    public abstract T createCustomObject();
    @Override    @SuppressWarnings("unchecked")    public T create(Throwable cause) {
        T obj = createCustomObject();        if (obj != null){            return obj;        }
        return (T) Proxy.newProxyInstance(                getEntityClass().getClassLoader(),                new Class[] { getEntityClass() }, new FeignExceptionHandler(cause));    }
    private Class getEntityClass() {        Class clazz = getClass();        Type type = clazz.getGenericSuperclass();        ParameterizedType p = (ParameterizedType)type;        return (Class) p.getActualTypeArguments()[0];    }

    class FeignExceptionHandler implements InvocationHandler {
        private Throwable cause;
        FeignExceptionHandler(Throwable cause) {            this.cause = cause;        }
        @Override        public Object invoke(Object proxy, Method method, Object[] args) {            log.error("invoke failed, error message:{}", cause.getMessage());            return ResponseEntityDTO.failure(ResponseCode.FAILED);        }    }}

Tips:

  • 优化版本V1 在原来的基础上多增加了两层结构:arch-api-bean和arch-app-bean,使得骨架显得臃肿。能不能抽取出arch-bean,用于放置各种类型的bean?
  • 创建扩展点对象是arch-client的职责吗?

这两个问题留给读者思考,如有疑问请通过文末的联系方式与我沟通

优化版本V2#

  1. 调整模型的依赖结构,优化优化版本V1中的优化点1、2,调整后的结构见下图 software-design
├─arch-app├─arch-domain│  ├─model│  │  ├─converter # 用于存放实体模型转换│  │  ├─do        # 领域实体模型,提供本领域内业务的方法│  │  └─vo        # 值实体模型│  └─service      # 轻service,内部调用do对象及repo完成业务逻辑│      └─facade   # 封装多个service的复杂操作├─arch-dal│  ├─model│  └─repo         # 封装数据访问层的增删改查的操作├─arch-intgr│  ├─facade       # 第三方接口出入参适配层│  └─model        # 用于存放第三方实体模型├─arch-utils├─arch-api│  └─model└─arch-client

Tips:

  • arch-app中@RestController下的接口实现arch-api包下的API接口。
  • arch-domain中service包下的接口继承自arch-api包下的API接口。为什么要继承API接口?这个问题留给读者思考,如有疑问请通过文末的联系方式与我沟通
  • arch-api接口方法中加入@RequestMapping、@RequestBody、@RequestParam,避免arch-client和arch-app做相同的操作
  • arch-util放在总的pom中,横跨arch-dal、arch-intgr、arch-domain、arch-app
  1. 调整arch-client中扩展点的代码,优化优化版本V1中的优化点3,调整后的代码如下
@Componentpublic class XXXClientFallbackFactory extends GenericFallbackFactory<IXXXApi> {    public XXXClientFallbackFactory(){}    public XXXClientFallbackFactory(IXXXApi xxxApi){super(xxxApi);}}
@Slf4jpublic  class GenericFallbackFactory<T> implements FallbackFactory<T> {    public GenericFallbackFactory(){}    public GenericFallbackFactory(T extend){this.extend = extend;}    
    protected   T extend;        @Override    @SuppressWarnings("unchecked")    public T create(Throwable cause) {        log.trace("fallback due to:{}", cause.getMessage(), cause);        if (extend != null){            log.debug("switch to extend fallback:{}", extend.getClass().getName());        }        return extend;    }
}
public class ResponseEntityDTO<T> implements Serializable {    private static final long serialVersionUID = -8567541939288898127L;    public static final ResponseEntityDTO SUCCEED = new ResponseEntityDTO(ResponseCode.SUCCEED);    public static final ResponseEntityDTO FAILED = new ResponseEntityDTO(ResponseCode.FAILED);        public static <T> boolean successAndDataNotNull(ResponseEntityDTO<T> resp){        return success(resp) && resp.data != null;    }    public static <T> boolean success(ResponseEntityDTO<T> resp){        return resp == null ? false:resp.succeed();    }    public static <T> ResponseEntityDTO<T> safeWrap(ResponseEntityDTO<T> resp){        return resp==null?FAILED: resp;    }
    T data;    String message;    Integer errCode;
    public boolean succeed() {        return errCode==0;    }        public ResponseEntityDTO(ResponseCode code){        message = code.getMessage();        errCode = code.getCode();    }    // 此处省略其他代码}

Tips:

  • 将创建拓展点的任务交给业务使用方,业务使用方使用@Bean告知Spring如何构建对象,并将拓展的xxxApi传入到XXXClientFallbackFactory对象中
  • 实现FallbackFactory接口的目的只是为了知晓请求失败的原因,比如:服务down机、网络超时等,所以GenericFallbackFactory中只要记录下日志就可以了
  • 当业务使用方提供了拓展点,则使用业务方提供的拓展点,否则不会做任何处理。为什么不在GenericFallbackFactory中构造新的对象从而在方法中返回默认的返回体?这个问题留给读者思考,如有疑问请通过文末的联系方式与我沟通
  • 通过ResponseEntityDTO中的success、successAndDataNotNull对arch-client返回体做校验,safeWrap用于arch-app层,包装arch-domain层的返回体

优化版本V3#

  • 目前的骨架都是通过写好的模板,提供出去。可以通过一种自由的方式让用户去选择想要生成的骨架及使用的组件等。读者可以参考Aliyun Java Initializer的实现
  • 针对不同的业务提供不同的骨架
  • 优化版本V2的方式能够满足单一技术栈的诉求,但是对于多技术栈显得无力。优化版本V2的方式本质上还是基于契约编程。可以从文档契约上去思考, 定义接口无非是定义好接口的出入参以及方法名。这块可以基于openapi去做,当接口定义完毕可发布时。接口的使用方,可以在接口文档的平台中生成各自技术栈的基于当前文档库版本号对应的库,再细化的可以生成当前技术栈使用的框架(spring cloud、dubbo、grpc等)对应的库(lib、dll、jar等)
  • 往往规范的出发点都是好的,如何保证使用骨架的人按照规范去执行?可以通过提供各种ide(vscode、idea等)插件,去提示用户。即使提示了,用户还是没有执行。这时可以通过骨架质检平台,扫描接入骨架的业务代码库,定期做质检分析。这块可以基于archunit去做
  • 此处留给读者思考,如果你有其他好的想法,请通过文末的联系方式与我沟通

结语#

  • 不断的优化设计,是为了使用的人能够更高效并快乐的工作
  • 所有的软件设计并不是一蹴而就的
  • 软件是要分层的,但也要注意粒度。比较的典型的案例:TCP/IP VS OSI/RM
  • 一个好的软件设计者,一定是一个好的产品设计者
  • 软件的设计最终一定要形成闭环
  • 软件的设计是基于特定的业务使用场景的

今天是母亲节,祝天下所有的母亲节日快乐!

联系方式#

  • 欢迎订阅我的公众号,这里主要会发表些软件工程上的一些想法及实践 微信公众号