53

思考,问题和方法

 5 years ago
source link: http://www.10tiao.com/html/296/201807/2649828020/1.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

转眼已是七月。距我上次更新公众号,已经一月有余。离我加入 Arcblock,也有两月。如果把人看做一个运行的软件,那么这两个月我已经迭代好几轮,就像龙珠里在飞往那美克星的太空船里不懈修炼的悟空。

人的成长是有诱因的,但不外乎得到他人指点,和自己开悟两种情况。吕蒙原本一介武夫,经孙权劝学后,发愤图强。有次鲁肃跟他议事,大惊失色,于是有了「士别三日,当刮目相待」的故事 —— 这是他人指点迷津。我是因为换了新的环境,破而后立,往日的积累和思考有了实施的空间和对象,又加上自己在西雅图孑然一身,事情又多,于是就把时间都扑在工作上,想得多,做得更多,能力也就上来了 —— 这是自己开悟。

孔子说三十而立,立的是什么?是立德、立功、立言这三不朽么?还是小家子些的,立身,立家,立业?每个在奔四路上的人都会有自己的体味和解读。但不容置疑的是,三十岁往上,要渐渐形成自己的思想和方法论。上篇文章 Code is Law,我为 Arcblock 的 github repo 定义的一套规范,就是我自己的思想和方法论的产物 —— 你在任何已有的公开的文档中找不到类似的做法。它完美么?不,肯定不,我们在使用一段时间后已经有了一些新的感悟;它独特么?独一无二,且很有价值。

我在 上帝说:要有一门面向未来的语言,于是有了 erlang 引用了 Joe 老爷子在其博士论文中提到的他对 erlang 的 worldview:

  • everything is a process.

  • process are strongly isolated.

  • process creation and destruction is a lightweight operation.

  • message passing is the only way for processes to interact.

  • processes have unique names.

  • if you know the name of a process you can send it a message.

  • processes share no resources.

  • error handling is non-local.

  • processes do what they are supposed to do or fail.

而在 Joe 的眼里,erlang 其实没有什么神秘的,仅仅六个函数就能涵盖它的全部:spawn,send,receive,register,whereis,self。

(感谢小山同学贡献的老爷子亲笔阐述)

仔细想想,它简单地可怕,就像物理学的大一统理论一样,试图从纷繁复杂中跳脱出来,回归本源。更可怕的是,这六个函数不仅仅涵盖了 erlang,似乎也可以解释软件领域里的很多系统 —— 它们无所不在,在系统里面的意义就像原力之于星战。

  • spawn:创建一个资源。对于 erlang,这资源是 process;对某个 service,是 service 本身。

  • send / receive:给资源发指令和接受指令。对于 erlang,这指令是 message,封装成 erlang term,走的是 IPC/RPC;而对某个 http service,指令是 request,封装成 json / msgpack / protobuf,走的是 http / http2。

  • register / whereis:资源怎么注册,怎么发现。对于 erlang,这是 process 在 name register 的注册和发现;对于某个 service,可以是 Consul / DNS。

  • self:返回自己的 identity。在 erlang 里,这是 process 找寻自我的方式;在 micro service 的场景下,每个 service 隐含着有自己的 identity。

我喜爱 Joe,和我喜欢 Rich Hickey 一样,他们在传播知识的同时,传播他们自己对事物独特的理解和思考。

回到我自己对做事的流程和方法的感悟。那些表层的方法之下,其实蕴含着一个重要的思考:如何让团队低成本的沟通和协作。我的方式是:convention by configuration。你弄懂了一个 repo 的结构,知道 make init,make build,make run,make create-pr 干些什么之后,就自然懂得我们在 arcblock 里所有 repo 的运行法则 —— 不管它是 elixir,还是 nodejs,还是 python。也不管 elixir 是否使用 asdf,nodejs 是否使用 nvm,python 是否使用 virtualenv,一个 make init 就把所有的环境帮里构建好,然后就可以安全地 make build 以及 make run 了。一个新来的工程师不需要跑来问我(或者其他人),这个 repo 怎么初始化,怎么运行起来,这大大节省了我们的时间;同时他也不需要为了能运行起来费力去读 READMME.md 里一个长长的「安装须知」的章节,因而也大大节省了他的时间。

这只是冰山的一角。我们还做了很多其它降低团队沟通协作成本的工作。而为大家节省下来的时间和精力,可以被用来做那些更重要的事情。这样点点滴滴的累积,最终有机会转化成一倍或者多倍的生产力,从而形成竞争优势。

我们的 Open Chain Access Protocal(OCAP)选用 GraphQL 而非 REST 接口来做 API 层,也有类似的考量。对于开发者而言,起初,他们有一些学习曲线,适应之后,我们无论是提供 1 个 API 还是 100 个 API,是支持一条链还是若干条链,对使用者的使用成本都是近似的。而 REST API,学习 100 个形态各异的 endpoints 对开发者来说将会是个梦魇;对于我们自己而言,初期搭设地基需要很多时间,随后,花样繁多的 query 背后,都是几种基本的 resolver 的组合。

「如何让团队低成本地沟通和协作」是我过去两个月的重要思考,也是我过去若干年知识和经验的储备的一次厚积薄发。

这两个月我的另一个尚处在摸索中的思考是:「如何用更先进更高效的方式来构建我们的服务及其生态?」

arcblock 目前是个小团队,即便研发团队发展到数十人的规模,依然很小。在我们想要做的事情的范畴上来看,如果找不到一个更行之有效的开发方式,我们会开发得很累,且开发进度会比较缓慢。就拿 OCAP 来说,打造一套供开发者使用的 API,不仅仅是 API 及其背后的服务那么简单。API 要有文档,要有 SDK,要有 API interface 的定义,以及支撑这个 interface 的服务。这里面会有很多重复的劳动:API doc 和 API interface,以及 SDK 都在不断地重复类似的内容和代码。当我们对 API 的定义进行改变的时候,往往牵一发而动全身,数个地方都需要修改,而这些都是非常机械的行为。所以,我们要寻找能够「降维打击」的方式。

在 Tubi,我做的 UAPI 系统,就整合了 API 和 API 的文档,使其可以一次定义,两处生效,节省大家的时间。而对于 OCAP,我们更进一步,试图把问题定义成这样:

  1. 定义一门「语言」,来描述我们的 API

  2. 撰写不同方向上的 Parser(Code generator),将其转换成特定场景的代码

  3. 将 Parser 构建在 build pipeline 中,可以一次 build,生成各种结果

  4. 生成的结果要能很方便地扩展,以及和系统里的其他部分整合

我们定义的语言,姑且称之为:AADL(Arcblock API Description Language),为了方便每一个人撰写和理解(比如,产品经理也可以很方便地定义),我们使用了 yaml 格式,比如 RichestAccounts 这个查询,其定义为:

而这里面使用的 data structure,是这么定义的:

通过这种定义,我们生成:

  • slate 风格的 API 文档(github.com/lord/slate)

  • Absinthe 的 GraphQL 的 query schema 定义(Absinthe 是 elixir 的 GraphQL lib)

  • Absinthe 的 GraphQL 的 type notation 定义

  • Ecto 的 DB repo 定义

  • Ecto 的 DB schema 定义

  • Ecto 的 DB migration 的定义

  • 各种语言的 SDK(比如 nodejs,python,go,etc. 筹划中,还未开始)

然后在一个 build pipeline 里,生成所有代码。比如生成的 Absinthe 的 query 长这个样子:

以上生成的代码符合前文中所述的「生成的结果要能很方便地扩展,以及和系统里的其他部分整合」这个限定条件。在这个例子里,程序员只需要进一步撰写 Resolver.paged_bitcoin_accounts 这个函数就可以了。

目前这套流程还在实验当中,我们线上的服务,OCAP playground 就跑的是生成的代码。我们自己写了大约 3500 行 elixir,1000 行 yaml;生成出来 1500 行 elixir 代码(Elixir 支持 Macro,所以我们生成出来这些源码只是方便自己排查问题)。

虽然还有很多问题,但这套系统最大的好处是,在开发过程中,我们可以随意调整 API 的结构而不必每次调整都苦逼修改很多地方的代码。这在我们对很多 API 的行为还没有一个良好定义的时候,是个莫大的福音。而之后,当我们要大规模增加新的 API 时,我们将能够很快地支持。

这目前是我们对「如何用更先进更高效的方式来构建服务及其生态?」的一个答案。它离完美还有十万八千里,但立等可用。很多时候,问对问题比找对答案更有意义。好的问题就像在黑暗的隧道里寻觅出口,突然手边摸出一把手电筒,瞬间照亮整个征途。

先写这么多吧,希望能引发你的思考和问题。



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK