一直以来都对“架构”非常感兴趣,早在大学期间,逃课成瘾的我连css都不会但就已经对架构师产生了浓厚的兴趣!

阴差阳错的成了程序员后,又稀里糊涂的做了这么久。工作重心一直围绕着web方向:网站,系统。随着2014年底开始,“微服务”这三个字就好像病毒广告一样烂大街了,丝毫不输于它的前辈“大数据”。

之所以要从“微服务”这个广告语开始谈起,除了因为我自身性格的缘故(总是不自觉的对主流事物感到莫名的反感)外,主要是因为这和我们今天谈的主题非常相关。

从敲下第一行代码开始,我相信每个程序员都在不停的思考一个问题:如何写出更好的代码。这里的“好”,具体应该至少包含:

  • 能解决一个具体的问题(本质)
  • 代码排版格式规整(强迫症)
  • 可阅读性强(尊重他人)
  • 维护成本低(修改成本低,让自己活的更滋润)
  • bug少(逻辑思维能力)
  • 抽象程度尽可能高(适配更多的场景,对外提供的接口灵活度高)
  • 文档齐全(大师级别的最低要求)

以上几点,从前到后是有优先级的,希望你可以用心体会。

上面提到的这几点常用于某个具体的业务代码,或者函数方法,或者某个能解决通用问题的类库。其实对于“架构”来说,也同样适用,不过可能需要考虑的因素更宽泛。

说这些和我们今天的主题有关系么?是的。真的?嗯!那到底有什么关系?说白了就是服务边界的问题,这和“职责单一”的概念有些相似,但又不完全相同。

相同之处在于让开发者时刻敏感于当前做的上下文规模问题,不同点在于服务切分时存在更多的 “灰色区域”

正是这些灰色区域的存在,才让大师成为大师,才让新手如此纠结。不过,这正是架构设计的乐趣和挑战!

我并没有许多成功的架构设计经验(是真的,如果你对我下面说的产生了质疑,那这个信息无疑是成为你反驳我的有理证据),但在实际工作中却真真切切的感过那种纠结和尴尬!曾经以为已经考虑的完美无缺,但文档才写了一半就傻眼了,现实业务总是像碰瓷大娘一样拦住去路~

那到底哪些细节需要在设计服务时需要我们警惕呢?我尽自己最大努力总结了一下:

  • 能解决一个具体且尽可能完整的业务问题
  • 尽力避免分布式事务
  • 从服务使用者的角度去规划服务的颗粒度
  • 考虑服务监控、容错、负载均衡、服务发现和治理方案
  • 尽量避免采用中心化的服务编排设计,交给服务调用方来做

最后一条应该最具争议性,毕竟这多少有些违背了 开放关闭 原则,至少我们很可能误将原本应该隐藏起来的细节不必要的暴露给了用户,而这个错误在某些时候是致命的(造成了数据的不一致)。

不过,如果拿捏的好呢?假设我们刚好抓住了蛇的七寸,那么我们即将设计出一套实现成本低且实用性强的服务方案。

来举一个实际的例子:

假设我们有一个获取学生列表的api,在返回的数据集中是否应该包含每个学生所属学校的内容呢?还是考虑设计一个专门用来获取指定学生学校的api?

根据数据库设计范式,为了避免不必要的数据冗余,我们可能会有两张表:学生表和学校表。

而界面上为了用户的体验,学生列表页面当然应该显示出对应的所属学校信息。貌似矛盾就这样理所应当的产生了。

强大的sql给我们提供了第一个解决方案:多表join,甚至让你可以跨库做“join”。似乎让我们没有不用的理由?!

不过不要被迷惑,要记住,不论何时,join都不应该是首选。理由是你 不应该将业务逻辑写在sql中(没有例外) ,尽管从我们这里的例子上来看join是完美的。但很多前辈都总结出了这种做法在web项目(互联网)的弊端,我们确实应该反思。

so,既然不推荐直接在sql层做数据拼接,那就上移到代码层,而在传统的分层架构中又面临一个问题:放在哪一层合适?service层还是control层?还是前端调用时(针对前后端分离项目)做数据关联?

考虑到现在REST架构风格比较流行,让我们假设上述的例子是发生在rest api中,到底这个api应该帮用户处理完所有数据关系?还是交给用户自己来按需获取呢?

在这个简单的例子中,我个人坚持后者,这种思想也和Facebook推的GraphQL在某种层面上不谋而合。这么做的好处是,既保证了最终用户的业务需求,又保证了api的可复用性,毕竟学生和学校是比较独立的两个概念域,到底如何来处理它们之间的关系,应该交给数据调用方来自行解决。

当然,这么做也并非毫无缺点的。暴露给调用方,意味着更多的风险:

  • 调用方没有正确的使用(谁会喜欢去看枯燥的使用说明书?)
  • 调用方恶意的使用(暴露出去的东西越多,被攻击的面就越大!)
  • 当提供方api升级时调用方的成本无法估计(怪我喽~)

最后一点其实尤为重要,这也是rest官方资料中为何如此反复强调“服务版本号”概念的初衷。比较保险的方法是增加一层(计算机世界解决问题的银弹!!!):gateway,不仅可以以较粗的颗粒度提供服务,而且可以做很多流控方面的事情。

由于我们的例子是如此的简单,以至于根本不会在数据库表设计上产生什么分歧,但实际项目中,往往也会让我们纠结如何设计数据库,如何将众多的表归类到各自合适的库中。若一开始对领域模型把握的不是很精准,后期可能会导致数据一致性的噩梦:分布式事务。

尽管很多业界巨头都开源出了解决方案或论文,社区也提供了很多可选的解决方案,但相信我,你依然不应该随意的引入这个问题。如何避免分布式事务呢?如果业务并非要求强一致性,那选择最终一致性的概念会让你的生活变得美好很多。否则,最好还是将事务性强表放在同一个物理位置。

当然,现实是残酷的,有时候你还是不得不直面困难,此时你才需要大胆的尝试市面上提供的分布式事务的解决方案,相信我,你是可以搞定的(不然就等着下岗吧!)。

对于业务相对比较简洁的项目,不会需要ESB这样的中心化服务编排的中间件。主动权交给调用方,毕竟鞋子合不合脚,只有自己知道。如果这种设计造成了性能瓶颈,那异步化将会成为你的主战场。

最后,还要提到的是,服务的切分,绝壁不是一锤子买卖。随着需求的变迁,服务会分分合合,所以这个时候,一个良好的服务分层会是一个开启美好明天的基础:

  • 基础服务: 依赖其它服务的服务
  • 复合服务:依赖其它服务的服务

听起来很简单不是么?但相信我,这是最纠结的地方。不过值得分享的是:不要陷入选择恐惧症中。要知道,关键时刻迈出一步,比犹豫不决要更有意义。

感悟:软件架构师之所以称之为架构师而非高级工程师,本质上讲是因为其思考和需要解决的问题更加宽泛,横跨软件开发的整个生命周期,而且“架构演化论”也指明,考核架构师优劣的一个重要标准是:应对需求变化的能力。