DoorDash 的后端服务如何从Python迁移到Kotlin?

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"美国外卖平台DoorDash原先的代码库是基于Django的单体应用。之前这个平台对业务的支持能力已逼近天花板。为给送餐服务提供更坚实的基础,DoorDash需要全新设计的技术栈。新平台应能很好地支撑企业的未来增长,并支持团队在构建中持续推陈出新,用上更好的模式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原系统的每次发布都需更新大量的节点,这显著增加了所需的发布时间。并且每次部署中都有大量的提交,一旦部署存在问题,难以通过对分定位("},{"type":"link","attrs":{"href":"https:\/\/en.wikipedia.org\/wiki\/Bisection_(software_engineering)","title":"","type":null},"content":[{"type":"text","text":"Bisecting"}]},{"type":"text","text":")发现具体导致问题的某次或某些提交,问题定位耗时也更长。此外,原单体应用是基于"},{"type":"link","attrs":{"href":"https:\/\/www.python.org\/download\/releases\/2.0\/","title":"","type":null},"content":[{"type":"text","text":"Python 2"}]},{"type":"text","text":"和"},{"type":"link","attrs":{"href":"https:\/\/www.djangoproject.com\/","title":"","type":null},"content":[{"type":"text","text":"Django"}]},{"type":"text","text":"构建的,而旧版本Python正迅速进入寿命终止期(EOL,End of Life),难以继续获得可靠的支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为实现具有更好可扩展性的系统,DoorDash工程团队需要去"},{"type":"link","attrs":{"href":"https:\/\/doordash.engineering\/2020\/12\/02\/how-doordash-transitioned-from-a-monolith-to-microservices\/","title":"","type":null},"content":[{"type":"text","text":"分解单体应用"}]},{"type":"text","text":",确定新服务的界面和交互行为。接下来的首要问题是如何确定支持团队工作的技术栈。通过对多种语言的调研,团队选定了具有丰富的生态系统、与Java良好互操作性和对开发人员友好的"},{"type":"link","attrs":{"href":"https:\/\/kotlinlang.org\/","title":"","type":null},"content":[{"type":"text","text":"Kotlin"}]},{"type":"text","text":"。针对Kotlin逐渐暴露出来的痛点问题,团队做出了一些改进。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"确定适用的技术栈"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当前存在多种可用的服务器端软件构建方案。但是出于以下方面考虑因素,团队考虑只使用单一语言。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有助于团队聚力,推动最佳开发实践在整个工程组织内的共享。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"支持针对企业场景构建优化的通用软件库,很好地适应企业规模和持续增长。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"极大降低工程人员在团队间转岗的摩擦,推动相互合作。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"综合上述因素,对于团队而言,问题并非是否应该使用单一的开发语言,而是应该选定哪一种语言。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"选择适合的编程语言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"选择编程语言时,团队要从企业的需求着手,考虑因素包括未来服务的体验以及交互方式等。团队很快取得一致,决定对服务间的相互同步通信机制使用"},{"type":"link","attrs":{"href":"https:\/\/grpc.io\/","title":"","type":null},"content":[{"type":"text","text":"gRPC"}]},{"type":"text","text":"、消息队列使用"},{"type":"link","attrs":{"href":"https:\/\/kafka.apache.org\/","title":"","type":null},"content":[{"type":"text","text":"Apache Kafka"}]},{"type":"text","text":"。数据存储将继续使用"},{"type":"link","attrs":{"href":"https:\/\/www.postgresql.org\/","title":"","type":null},"content":[{"type":"text","text":"Postgres"}]},{"type":"text","text":"和"},{"type":"link","attrs":{"href":"https:\/\/cassandra.apache.org\/","title":"","type":null},"content":[{"type":"text","text":"Apache Cassandra"}]},{"type":"text","text":",因为团队成员对此经验丰富、技能熟练,并且二者的技术成熟度也非常高,广泛支持所有的现代编程语言。下面还需要考虑其它一些因素。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"无论选定何种技术,都需要满足:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"高效利用CPU,可扩展到多核。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"易于监控"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具有强大的软件库生态支持,使团队可聚焦于业务问题本身。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"确保提供良好的开发人员生产率"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可靠的扩展性"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"面向未来,为企业业务增长提供良好支撑。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"团队基于上述需求考虑了各种语言,弃用了"},{"type":"link","attrs":{"href":"https:\/\/en.wikipedia.org\/wiki\/C%2B%2B","title":"","type":null},"content":[{"type":"text","text":"C++"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https:\/\/www.ruby-lang.org\/en\/","title":"","type":null},"content":[{"type":"text","text":"Ruby"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https:\/\/www.php.net\/","title":"","type":null},"content":[{"type":"text","text":"PHP"}]},{"type":"text","text":"和"},{"type":"link","attrs":{"href":"https:\/\/www.scala-lang.org\/","title":"","type":null},"content":[{"type":"text","text":"Scala"}]},{"type":"text","text":"等主流语言。尽管这些语言颇受欢迎,但它们难以支撑每秒查询数(QPS)和用户数的增长,不能满足团队对未来技术栈的一项或数项核心需求。基于上述需求,选择范围锁定在"},{"type":"link","attrs":{"href":"https:\/\/kotlinlang.org\/","title":"","type":null},"content":[{"type":"text","text":"Kotlin"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https:\/\/www.java.com\/en\/","title":"","type":null},"content":[{"type":"text","text":"Java"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https:\/\/golang.org\/","title":"","type":null},"content":[{"type":"text","text":"Go"}]},{"type":"text","text":"、"},{"type":"link","attrs":{"href":"https:\/\/www.rust-lang.org\/","title":"","type":null},"content":[{"type":"text","text":"Rust"}]},{"type":"text","text":"和"},{"type":"link","attrs":{"href":"https:\/\/www.python.org\/","title":"","type":null},"content":[{"type":"text","text":"Python 3"}]},{"type":"text","text":"。为比较和对比各语言相互之间的优劣之处,团队形成了如下对比表。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Kotlin"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"优点:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"软件库生态系统强大"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对gRPC、HTTP、Kafka、Cassandr和SQL提供一等支持"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"继承Java的生态系统,速度快,可扩展。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原生支持并发原语,避免了Java的繁琐,去除了对复杂的"},{"type":"link","attrs":{"href":"https:\/\/howtodoinjava.com\/design-patterns\/creational\/builder-pattern-in-java\/","title":"","type":null},"content":[{"type":"text","text":"Builder"}]},{"type":"text","text":"\/"},{"type":"link","attrs":{"href":"https:\/\/www.tutorialspoint.com\/design_pattern\/factory_pattern.htm","title":"","type":null},"content":[{"type":"text","text":"Factory"}]},{"type":"text","text":"模式的依赖。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java Agent提供强大的组件内省(introspection),仅需少量编码,即可自动定义并导出度量和追踪,以实现监控。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不足:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常很少用于服务器端,开发人员缺少可供参考的示例代码。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相比Go而言,并发实现相对繁琐。Go在语言基础层和标准软件库中集成了goroutine(译者注:原文是“gothreads”)这一核心理念。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Java"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"优点:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具有强大的软件库生态系统"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对gRPC、HTTP、Kafka、Cassandr和SQL提供一等支持"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"速度快、可扩展"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java Agent提供强大的组件内省(introspection),仅需少量编码,即可自动定义并导出度量和追踪,以实现监控。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不足:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相对Kotlin和Go而言,"},{"type":"link","attrs":{"href":"https:\/\/en.wikipedia.org\/wiki\/Concurrency_(computer_science)","title":"","type":null},"content":[{"type":"text","text":"并发"}]},{"type":"text","text":"实现相对繁琐。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"编码存在极端冗长问题,难以编写整洁的代码。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Go"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"优点:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具有强大的软件库生态系统"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对gRPC、HTTP、Kafka、Cassandr和SQL提供一等支持"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"速度快、可扩展"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原生支持并发原语,简化了并发代码的编写。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具有大量可用的服务器端例子程序和文档。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不足:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于不熟悉Go语言的开发人员,配置数据模型并非易事。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"尽管Go最终会支持泛型(generics),但当前尚不支持。这意味着"},{"type":"link","attrs":{"href":"https:\/\/www.freecodecamp.org\/news\/generics-in-golang\/","title":"","type":null},"content":[{"type":"text","text":"一些软件库中的类相对难以在Go中构建"}]},{"type":"text","text":"。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Rust"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"优点:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"运行速度非常快"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"没有垃圾回收机制,依然内存和并发安全。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一些大型企业开始采用该语言,因此具有大量投资及很好的发展。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相比其它语言,其提供强大的类型系统,更易于表达复杂理念和模式。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不足:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"语言较新,这意味着例子代码、软件库以及具有模式构建和调试经验的开发人员相对不足。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相比其它语言,生态系统不算强大。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当前async\/await尚未实现标准化。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"要掌握内存模型,需要一定的学习时间。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Python 3"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"优点:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提供强大的软件库生态系统"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"易用"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"团队已经具有丰富的经验"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"易于招聘开发人员"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对GRPC、HTTP、Cassandra和SQL提供一等支持"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提供REPL,便于App运行时的测试和调试。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不足:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相比其它语言,运行速度较慢。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解释器全局锁(GIL)难以完全高效利用团队的多核机器。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"缺少强大的类型检查特性。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当前对Kafka支持不够,特性发布存在延迟。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根据以上对比,团队决定开发一个经过测试和扩展的Kotlin组件的“黄金标准”。kotlin本质上是一种更适合团队的Java版本,但缓解了Java存在的痛点问题。由此团队选择了Kotlin,但必须要去解决进一步发展中经历的一些问题。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"相对Java,Kotlin的优点"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相对Java,Kotlin的一个最大优点是“空值安全”("},{"type":"link","attrs":{"href":"https:\/\/kotlinlang.org\/docs\/null-safety.html","title":"","type":null},"content":[{"type":"text","text":"null safety"}]},{"type":"text","text":")。Kotlin中,开发人员必须明确定义可为空值的对象,并强制开发人员采用安全方式处理,避免了必须处理大量潜在的运行时异常的痛点。也可使用"},{"type":"link","attrs":{"href":"https:\/\/kotlinlang.org\/docs\/null-safety.html#safe-calls","title":"","type":null},"content":[{"type":"text","text":"空值合并(null-coalescing)操作符"}]},{"type":"text","text":"“?.”,用单行代码实现对可为空值子域的安全访问。例如,Java实现为:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"int subLength = 0;\nif (obj != null) {\n if (obj.subObj != null) {\n subLenth = obj.subObj.length();\n }\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用Kotlin实现为:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"val subLength = obj?.subObj?.length() ?: 0"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"尽管上面给出的例子非常简单,但已经足够体现出空值合并操作符的强大之处,即大大降低了代码中条件语句的数量,提高了代码的可读性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相比其它语言,在实现服务度量的仪表盘监控中,使用Kotlin更易于迁移到"},{"type":"link","attrs":{"href":"https:\/\/prometheus.io\/","title":"","type":null},"content":[{"type":"text","text":"Prometheus"}]},{"type":"text","text":"事件监测系统。团队开发了一个注解处理器(Annotation Processor),自动按度量生成相应的函数,确保正确数量的标注按正确的顺序给出。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面例子给出了Prometheus软件库的标准集成代码:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/\/ to declare\nval SuccessfulRequests = Counter.build( \n \"successful_requests\",\n \"successful proxying of requests\",\n)\n.labelNames(\"handler\", \"method\", \"regex\", \"downstream\")\n.register()\n\n\/\/ to use\nSuccessfulRequests.label(\"handlerName\", \"GET\", \".*\", \"www.google.com\").inc()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"修改为如下代码,API更不易出错:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/\/ to declare\n@PromMetric(\n PromMetricType.Counter, \n \"successful_requests\", \n \"successful proxying of requests\", \n [\"handler\", \"method\", \"regex\", \"downstream\"])\nobject SuccessfulRequests\n\n\/\/ to use\nSuccessfulRequests(\"handlerName\", \"GET\", \".*\", \"www.google.com\").inc()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"采用如上的集成方式,不必再去记住某个度量所具有标注的数量和顺序,而是由编译器和所使用的IDE去确保标注的正确数量和名称。DoorDash使用了分布式追踪,监控的集成可简化为类似于"},{"type":"link","attrs":{"href":"https:\/\/github.com\/open-telemetry\/opentelemetry-java-instrumentation#getting-started","title":"","type":null},"content":[{"type":"text","text":"在运行时添加Java Agent"}]},{"type":"text","text":"。。对于一个新服务,不需要代码属主团队去修改代码,可观测性和架构团队就能快速地推出对应的分布式追踪。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在团队看来,Kotlin的另一个非常强大之处是协程("},{"type":"link","attrs":{"href":"https:\/\/kotlinlang.org\/docs\/coroutines-overview.html","title":"","type":null},"content":[{"type":"text","text":"Coroutines"}]},{"type":"text","text":")。协程模式让开发人员无需纠结于回调这个天坑,能使用近乎命令式编程的方式去编写代码,这正是大部分开发人员更为驾轻就熟的方式。协程也非常易于组合,必要时可并行运行。下面例子给出了团队使用的某个Kafka消费者:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"val awaiting = msgs.partitions().map { topicPartition ->\n async {\n val records = msgs.records(topicPartition)\n val processor = processors[topicPartition.topic()]\n if (processor == null) {\n logger.withValues(\n Pair(\"topic\", topicPartition.topic()),\n ).error(\"No processor configured for topic for which we have received messages\")\n } else {\n try {\n processRecords(records, processor)\n } catch (e: Exception) {\n logger.withValues(\n Pair(\"topic\", topicPartition.topic()),\n Pair(\"partition\", topicPartition.partition()),\n ).error(\"Failed to process and commit a batch of records\")\n }\n }\n }\n}\nawaiting.awaitAll()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Kotlin协程支持在编码中按分区快速地切分消息,并对每个分区启动一个处理消息的协程,不破坏消息插入队列时的顺序。此后,在检查偏移并返回Broker前,连接所有的Future。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Kotlin支持团队以更可靠和可扩展的方式快速推进。从上面的例子中可见一斑。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"解决推广Kotlin中遇到的问题"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为更好地利用Kotlin的全部特性,团队必须要解决以下问题:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何培训团队更高效地使用Kotlin"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"建立使用协程的最佳实践"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"解决与Java互操作上的痛点"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"进一步简化依赖管理"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面展开介绍团队时如何解决上述问题的"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"培训团队使用Kotlin"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"采用Kotlin的一个最大问题,就是如何确保提升团队的开发速度。团队中大多数人具备优秀的Python开发背景,后端团队具有一些Java和Ruby经验。考虑到在后端开发中很少使用Kotlin,因此团队必须要建立指导后端开发人员使用Kotlin的良好指南。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"尽管在线上可以找到大量的学习教程,但是大多数"},{"type":"link","attrs":{"href":"https:\/\/kotlinlang.org\/docs\/learning-materials-overview.html","title":"","type":null},"content":[{"type":"text","text":"Kotlin线上社区"}]},{"type":"text","text":"主要专注于安卓开发。团队的高级开发人员编写了“如何使用Kotlin编程”,其中给出了编程建议和代码片段。我们团队发布了“碎片化学习教程”(Lunch and Learns session),告诉开发人员如何避免一些常见的坑,如何有效地使用IntelliJ IDE开展工作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"团队更多地传授开发人员Kotlin的函数式编程方面内容,包括如何使用模式匹配、不可变性默认优先等理念。团队建立了人人可加入、提问并获得建议的线上交流小组(Slack Channel),形成了一个Kotlin工程互助指导社区。通过以上工作,团队构建一个强大的、熟练掌握Kotlin的工程人员团队,并在团队进一步扩展时能传承知识,形成可持续发展和改进的内循环。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"避免掉进协程中的坑"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"团队在选择Kotlin时,尚缺少对协程的支持(译者注:"},{"type":"link","attrs":{"href":"https:\/\/blog.jetbrains.com\/kotlin\/2018\/10\/kotlin-1-3\/","title":"","type":null},"content":[{"type":"text","text":"2018年10月,Kotlin 1.3推出了coroutines稳定特性"}]},{"type":"text","text":")。因此团队选定gRPC作为服务间通信方法,为充分利用Kotlin需做一定改进。当时gRPC-Java是Kotlin gRPC服务的唯一选择,因为Java中并不存在协程,因此gRPC-Java也缺少对协程的支持。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"两个开源项目"},{"type":"link","attrs":{"href":"https:\/\/github.com\/marcoferrer\/kroto-plus","title":"","type":null},"content":[{"type":"text","text":"Kroto-plus"}]},{"type":"text","text":"和"},{"type":"link","attrs":{"href":"https:\/\/github.com\/daviddenton\/protokruft","title":"","type":null},"content":[{"type":"text","text":"Protokruft"}]},{"type":"text","text":"可以解决这个问题,团队最终分别使用了这两个解决方案的各一部分去设计服务,创建更具原生感的解决方案。最近"},{"type":"link","attrs":{"href":"https:\/\/github.com\/grpc\/grpc-kotlin","title":"","type":null},"content":[{"type":"text","text":"gRPC-Kotlin"}]},{"type":"text","text":"发布了"},{"type":"link","attrs":{"href":"https:\/\/developers.googleblog.com\/2020\/12\/announcing-grpc-kotlin-10-for-android.html","title":"","type":null},"content":[{"type":"text","text":"一般可用(GA)版"}]},{"type":"text","text":"。为提供更好的Kotlin构建系统体验,团队我们正在迁移服务以使用官方绑定版本。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于已转向Kotlin的安卓开发人员,对协程中存在的其它坑应该并不陌生。例如,不要在请求中重用"},{"type":"link","attrs":{"href":"https:\/\/kotlinlang.org\/api\/latest\/jvm\/stdlib\/kotlin.coroutines\/-coroutine-context\/","title":"","type":null},"content":[{"type":"text","text":"CoroutineContexts"}]},{"type":"text","text":",因为一旦取消或出现异常,CoroutineContext就会转入“cancelled”状态,这意味着任何进一步尝试在此Context中加载协程将会产生失败。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"正因为此,需对服务器处理的每个请求新建一个CoroutineContext,不能再依赖于"},{"type":"link","attrs":{"href":"https:\/\/www.baeldung.com\/java-threadlocal","title":"","type":null},"content":[{"type":"text","text":"ThreadLocal变量"}]},{"type":"text","text":",因为协程可在Context中换入换出,导致数据不正确或被覆盖。另一个需要警惕的坑是避免使用未绑定的"},{"type":"link","attrs":{"href":"https:\/\/stackoverflow.com\/questions\/54335365\/why-not-use-globalscope-launch","title":"","type":null},"content":[{"type":"text","text":"GlobalScope"}]},{"type":"text","text":"加载协程,会导致资源上的问题。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"解决虚引用Java NIO问题"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"支持现代"},{"type":"link","attrs":{"href":"https:\/\/en.wikipedia.org\/wiki\/Non-blocking_I\/O_(Java)","title":"","type":null},"content":[{"type":"text","text":"Java非阻塞IO(NIO)"}]},{"type":"text","text":"标准的软件库,可以很好地与Kotlin协程互操作。但在选定Kotlin后,我们发现很多宣称支持Java NIO的软件库的实现方式并非可扩展的。它们在底层协议和标准实现中并非基于NIO原语,而是使用线程池包裹阻塞IO。我们称这种NIO实现策略为“虚引用(Phantom)Java NIO”。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虚引用NIO策略实现的副作用是线程池在协程环境中很容易耗尽,由于其本质上是阻塞IO,会导致高峰值延迟。为确保线程池的规模能满足团队的需求,虚引用NIO软件库都需要对线程池做调优,恰当调优和资源维护的需求增加了开发人员工作量。因此,使用真正的NIO或Kotlin原生库,通常会提供更好的性能、更易于扩展,实现更优的开发人员工作流。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"依赖项管理:使用"},{"type":"link","attrs":{"href":"https:\/\/gradle.org\/","title":"","type":null},"content":[{"type":"text","text":"Gradle"}]},{"type":"text","text":"颇具挑战"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相比Rust Cargo和Go module等最新解决方案,构建系统和依赖管理无论对新手还是熟悉Java\/JVM生态者都相当不够直观。尤其是对于团队而言,一些依赖直接或间接地对版本升级非常敏感。Kafka和Scala等项目并不遵循"},{"type":"link","attrs":{"href":"https:\/\/semver.org\/","title":"","type":null},"content":[{"type":"text","text":"语义化版本管理(semantic versioning)"}]},{"type":"text","text":",这会导致编译成功而应用却由于一些看上去毫不相干的奇怪回溯(backtrace)而启动失败的问题。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"寸积铢累,团队逐渐掌握了哪些项目通常会导致此类问题,积累了一些如何捕获并过滤问题的例子。特别是,Gradle针对如何"},{"type":"link","attrs":{"href":"https:\/\/docs.gradle.org\/current\/userguide\/viewing_debugging_dependencies.html","title":"","type":null},"content":[{"type":"text","text":"查看依赖树"}]},{"type":"text","text":"提供了一些有参考的页面,非常适用于此类问题。掌握多项目代码库的进入导出情况,需假以时日,期间非常容易导致冲突需求和环形依赖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"预先规划多项目代码库的布局,对项目的长期发展是大有裨益的。尽量确保依赖树简单,避免基础代码库对任一子项目的依赖(并且永不依赖),进而在此基础上做迭代构建,防止出现难以调试或厘清的依赖链。DoorDash主要使用了"},{"type":"link","attrs":{"href":"https:\/\/jfrog.com\/artifactory\/","title":"","type":null},"content":[{"type":"text","text":"JFrog Artifactory"}]},{"type":"text","text":",简化了软件库在代码库间的共享。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Kotlin在DoorDash的未来发展"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DoorDash服务标准将继续完全采用Kotlin,平台团队正基于"},{"type":"link","attrs":{"href":"https:\/\/github.com\/google\/guice","title":"","type":null},"content":[{"type":"text","text":"Guice"}]},{"type":"text","text":"和"},{"type":"link","attrs":{"href":"https:\/\/armeria.dev\/","title":"","type":null},"content":[{"type":"text","text":"Armeria"}]},{"type":"text","text":"努力构建下一代服务标准,并通过预先部署监控、分布式追踪、异常追踪、运行时配置管理工具和安全集成等工具和功能,简化团队的开发工作流。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这些工作有助于DooDash开发共享性更好的代码,减轻开发人员查找依赖项、协同工作和维持最新依赖的负担。构建此类系统的投资,已体现在团队具备了针对涌现的需求而快速启动新服务的能力。Kotlin支持开发人员聚焦于业务用例,减少了编写Java生态中不可避免的"},{"type":"link","attrs":{"href":"https:\/\/en.wikipedia.org\/wiki\/Boilerplate_code","title":"","type":null},"content":[{"type":"text","text":"模板代码"}]},{"type":"text","text":"所用的时间。总而言之,Kotlin是很好的选择,我们期待这一语言和生态的持续改进。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"基于DoorDash的经验,强烈推荐后端工程人员首选Kotlin。Kotlin是更好的Java语言,该理念在DoorDash得到了验证,带来了更大的开发人员生产率,降低了运行时发现的错误。这些优点支持团队聚焦于解决业务需求,增加敏捷性和速度。未来DoorDash将继续投资于Kotlin,希望继续与更广泛的生态合作,开发以Kotlin为主的更强大服务器端用例。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"答疑"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"问题:为什么没有选定Python 3?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"答:尽管Python 3具有更强大的生态,但整个生态系统依然不够健壮。Pip在依赖管理上存在很多问题,而conda、poetry、pipend、pip-tools等工具也并未解决问题。对于构建和软件包工具存在同样问题。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用协程时遇到的最大坑:取消或异常会导致CoroutineContext进入“cancelled”状态,这意味着进一步尝试在此上下文中加载协程将会失败,对于服务器处理的每个请求,需要创建一个新的CoroutineContext。更坏情况时,新的上下文每次创建的代价很大。需要建立一类发生异常后无需取消的特殊任务类型,以及建立很好的协程异常处理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"团队使用Kotlin在Apache Flink中实现流处理。为解决虚引用NIO问题,团队拟出了一个符合“黄金准则”的软件库列表。其中软件库或是很好地实现了协程,或是提供预优化版本的库。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"问题:非阻塞IO是如何实现的?DoorDash最终使用了第三方软件库,还是推出了自己的?DoorDash的主要IO是网络调用、文件系统还是消息代理?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"答:DoorDash构建了自己的软件库,针对特定服务使用gRPC。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"问题:DoorDash在从Python迁移到Kotlin中,是如何解决CI\/CD问题的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"答:团队采用CI\/CD的层级和版本一直在演进,至少在今年就发生了不少变化,每次迭代都会向前推进一步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文链接:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"https:\/\/doordash.engineering\/2021\/05\/04\/migrating-from-python-to-kotlin-for-our-backend-services\/"}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章