目录

聊聊第一次外包

有个朋友是个外包巨佬,去年几个玩得非常来的朋友建了个几人小群,前后端、产品、设计都有。机缘巧合巨佬接到了一个类商城外包,大概中五位不到点,可能一个人不太敢接,就带着我们一起做了,互相充胆量。正好群里做什么的都有。我是个容易忘事的人,所以去年开始就决定用文字记录经历,这篇文章鸽了这么久也是该写了。

一、代码之外

外包比想象的繁杂许多。

首先是他们和甲方谈了很久的价格,确定了大概的需求。然后就是拟定合同,发动各种朋友圈资源找懂法律的朋友咨询;大概拟完之后读了一遍又一遍,就怕一不小心出事。

合同里最麻烦的,反而是需求的定义了。甲方的需求是一个结构非常混乱的 xmind,什么都想做,各种功能都往里面堆。而且他完全分清「功能点」与「页面」。所以用 xmind 当需求书自然是不可能的,我按模块梳理了功能点,列成了个表格,直接写在了合同里。外包佬又和甲方谈了一谈,把需求分成了一二期,需求瞬间减少了大半。砍需求+加钱也是一种艺术。

最后合同是在某软件签的,打钱主要分为三个阶段:预付,交付架构设计图数据库设计等文档后再打一笔,测试交付后打尾款。

二、后端架构选型

我是个后端程序员,加上当时其他人在忙其他的项目,所以我主要做了前期的技术选型、搭项目架子,简单搭个前期环境,以及后端一半左右的模块。后面还有个简单而经典的后台管理系统, 外包佬用 go-admin 搭了个架子,我速冲了剩余的部分。代码生成还是舒服:)

后端,大家都是 Go 程序员,自然是用 Go 的框架,且必须认真玩玩微服务。起初外包佬打算用 go-micro,但在我的建议下,最后是选用了 go-zero

起初是因为在社群内看到了 go-zero 作者 Kevin和 dtm 作者的直播分享。印象很深的是,Kevin说他在整合各种微服务的生态,比如这次的dtm,所以 go-zero 的生态也是比较完整的。

看了看文档,觉得确实不错。和 go-micro 相比,最显而易见的优点就是,文档非常细致,更新也迅速,能减少非常多的学习成本。而且人总是有惰性的,中文文档更为易读,使得深入了解整个框架的设计、读完所有的文档成为可能。

除了官网列举的优点外,也介绍了许多理念,和 Go 语言核心价值观一致。它除了吹牛逼、教人怎么用外,还能大讲设计范畴的内容。goctl 也深得我心。

然后是 Kevin 很喜欢发布技术文章,我之前就看过一些,所以感觉是个对技术狂热且热爱分享的人。go-zero还有很多社群,我觉得这很值得敬佩,是真的在用心做 go-zero,因为维护社群真的是一件非常非常费力的事情。也正是因为 Kevin 帮助许多人推广基于 go-zero 的项目,我们这次的项目也得以有许多参考。

当然还因为, go-zero 发布没多久,star 就非常多了,群众的眼光也是雪亮的。

社团最近的新项目采用的都是模块化的结构,协作开发起来非常爽所以go-zero 的分层结构,我一看到,DNA 就动了。rpc 层作为基础服务提供方, api 层组合 rpc 服务,同级模块不互调,依赖非常清晰。go-zero 的目录设计,使得 goctl 生成的代码架子与逻辑代码分离这点,对代码生成很有启发性。这导致 go-zero 的代码生成可以是逐渐扩展的,例如加了个接口,原先的业务代码几乎不会受到影响。相反,go-admin 的表导入的代码生成,是覆盖性的,改动了某个函数,又想再给表加个字段的话,要么只能手动在代码里加,要么只能重新生成覆盖之前函数的改动。所以 go-admin 我们基本是第一次生成一下,后面有需求再手动改。

go-zero 的 service context 在 model 层、rpc 层、api 层的传递,也很值得学习。这使得 goctl 的 handler (也就是模板代码)格式基本一致的情况下,又能非常自由地传递各种配置、model 接口、rpc server 等内容。

遗憾的是,因为用 gorm 实在太顺手了,所以把 model 层的 sqlx 换成了 gorm,舍弃了许多特性。

三、收获

收获主要是在两方面,一方面是技术知识的提升与拓展,另一方面是细节把握的提升。

技术的拓展,比如对 jwt 的掌握程度从了解概念变成了熟悉,甚至还因为当时 postman 截断长度导致了所有请求 401,以致于怀疑 go-zero 的 jwt 的 token 解析模块失败,而写了个简单的从 token 中获取 user id 注入到 context 的中间件。

又比如 looklook 的错误处理篇让我对 go 的错误与日志这块有了更加实际的使用经验,以前都是简单返回个 error,没有太多包装与日志处理。

写项目的过程中,也是互相学习的过程。例如我从外包佬负责的模块上学到了许多 gorm 的奇技淫巧。

又比如很多有用的库,比如 gorm 作者的 copier 库,用来做 model、rpc、api 三层的数据格式转化非常高效。

细节方面,写完这个项目,很大的一个体验就是,对细节的把握,都是积累出来的。或者说,这次踩了个坑A和B,脑子里就记下了;下次写项目的时候,自动会规避AB,然后又遇到了C;那么以后写项目的时候,就基本不会踩ABC的坑了。

细节其实又分为技术细节和业务细节。

技术细节,比如 go 的数据类型,mysql 数据类型、proto 的数据类型的转换关系。还有不同层之间的 id 的转换,例如 gorm.Model 的 ID 默认是 uint,而 proto 可能用的是 uint64,go-zero 的 api 文件的定义又是 int64(因为如果定义成 uint64,而前端传了个负数,go-zero 不会报错,会解析成一个非常大的正数,一个简单的补码原理)。

又比如写代码的时候开始会认真得想数据库的读写竞争、事务,以及跨模块的分布式事务。

又比如如何设计一个简单的业务配置模块,使得交付后客服管理人员能自己改动数据。业务配置每次重新从数据库读效率比较低,又需要加个简单的缓存机制,以及设计更新机制。例如可以在程序启动时把业务配置全部加载至内存,业务配置更新后通知系统重新读取配置;也可以简单地隔几秒自动获取一次配置,读至内存。

又如,商城的「钱」怎么存,精度?显示?后面大家一起讨论后,其实也比较简单,考虑到没有支付宝那样的超大规模,我们不需要非常高的精度,float64 够了。至于什么时候四舍五入,也比较简单,数据库对余额的修改不四舍五入,前端显示的时候控制一下显示精度就好。

还有些表设计的取舍。如,目前投票业务应该都是两个选项。是否要支持任意个数的投票以方便后续业务更新?即,投票的选项是否应该独立成一张表,不独立的话要么只支持固定个数选项,要么投票字段投 json;独立的话可玩性非常强,如任意个数字段,以及选项支持改名字等,但复杂度又上来了。

业务细节,只能说,太搞人了。比如突然意识到要加个个性签名字段,又突然意识到学生认证可以主动撤回。

四、感想

这次外包让我尝到了赚钱的喜悦,但同时也体验了业务的繁杂以及被工期 push 的那种难受感。(因为期末考加互相拖延,导致工期过三分之一了才差不多开始)。由此发觉自己更喜欢钻研如何设计代码架构、阅读开源项目、研究语言特性了。也可能,程序员的一生,就是在纯技术和业务二者之间不断循环的。

开始认真考虑一个项目的安全性、稳定性以及程序中的敏感内容(如钱的存储计算)。

遗憾的是,本来想试试 DDD 的,但因为太着急开始,没空深入学。

以及,自己是该好好学学运维一类的东西了。之前收藏的那些 k8s 文档什么的,真的可以看起来了。

自己还是太菜了。