将API移植到TypeScript作为问题解决者

执行程序的React前端已从JavaScript 转换为TypeScript。但是用Ruby编写的后端并没有碰到。但是,与此后端相关的问题使项目开发人员考虑从Ruby切换到TypeScript。我们今天出版的材料的翻译致力于将Execute Program后端从Ruby移植到TypeScript的故事,以及这有助于解决什么问题。



使用Ruby后端,我们有时会忘记API的某些属性存储的是字符串数组,而不是简单的字符串。有时我们更改了在不同位置访问的API片段,但是却忘记了在其中一个位置更新代码。这些是动态语言的常见问题,这是代码未100%覆盖测试的任何系统所特有的。 (这虽然不常见,但在测试完全覆盖了代码时会发生。)

与此同时,由于我们将其切换为TypeScript,这些问题已从前端消失。我在服务器编程方面比在客户端方面具有更多的经验,但是尽管如此,在使用后端而不是使用前端时,我犯了更多的错误。所有这些表明,后端也应该转换为TypeScript。

我于2019年3月在大约2周的时间内将后端从Ruby移植到TypeScript。一切都按预期进行!我们于2019年4月14日在生产中部署了新代码。这是一个Beta版本,仅适用于少数用户。在那之后,什么都没有发生。用户甚至没有注意到任何东西。这是一个图表,说明了转换之前和转换之后我们代码库的状态。 x轴表示时间(以天为单位),y轴表示代码行数。


将前端从JavaScript转换为TypeScript,并将后端从Ruby转换为TypeScript

在移植过程中,我编写了大量辅助代码。因此,我们有自己的工具来运行200行的测试。我们有一个120行的库用于处理数据库,还有一个用于连接前端和后端代码的API的更大的路由库。

在我们自己的基础架构中,最有趣的是路由器。它是Express的包装,可确保正确应用客户端和服务器代码中使用的类型。这意味着当API的一部分更改时,如果不进行更改以消除差异,则另一部分甚至无法编译。

这是一个返回博客文章列表的后端处理程序。这是系统中最简单的类似代码片段之一:

router.handleGet(api.blog, async () => {
  return {
    posts: blog.posts,
  }
})

如果我们改变密钥名postsblogPosts,我们得到了一个编译错误,其中的文本如下所示(这里,为了简便起见,省略了关于对象的类型信息。)

Property 'posts' is missing in type '...' but required in type '...'.

每个端点由视图对象定义api.someNameHere客户端和服务器共享该对象。请注意,处理程序声明中没有直接提及类型。它们都是从论点推断出来的api.blog

该方法适用于简单的端点,例如上述端点blog但这适用于更复杂的端点。例如,用于课程的端点API具有逻辑类型的深层嵌套键.lesson.steps[index].isInteractive由于所有这些,现在不可能犯以下错误:

  • 如果我们尝试访问isinteractive客户端,或尝试从服务器返回这样的密钥,则代码将无法编译。键名应看起来像isInteractive,大写I
  • isInteractive — .
  • isInteractive number, , , .
  • API, , isInteractive — , , , , , , , .

请注意,所有这些都包括代码生成。这是使用io-ts和来自我们自己的路由器的几百行代码完成的。

声明API类型需要额外的工作,但是工作很简单。在更改API的结构时,我们需要知道代码的结构如何变化。我们对API声明进行了更改,然后编译器将我们指向需要修复代码的所有位置。

除非您使用了一段时间,否则很难理解这些机制的重要性。我们可以将大型对象从API中的一个位置移动到另一位置,重命名键,可以将大型对象拆分为多个部分,将小型对象合并为一个对象,拆分或合并整个端点。而且,我们可以做到所有这些,而不必担心我们忘记对客户端或服务器代码进行适当的更改。

这是一个真实的例子。我最近花了大约20个小时来重新设计API Execute程序,花了四天的时间。 API的整体结构已更改。将新的客户端和服务器代码与旧的代码进行比较时,记录了数万行的更改。我重新设计了服务器端的路由代码(如上handleGet)我重写了API的所有类型声明,使它们中的许多结构发生了巨大变化。而且,此外,我重写了客户端中调用已更改的API的所有部分。在此过程中,更改了292个源文件中的246个。

在大部分工作中,我仅依靠类型系统。在这20个小时的案例的最后一个小时,我开始运行测试,大多数情况下,这些测试成功完成了。最后,我们进行了完整的测试,发现了三个小错误。

这些全都是逻辑错误:偶然导致程序到达错误位置的条件。通常,类型系统无助于发现此类错误。解决这些错误花了几分钟。重新设计的API已于几个月前部署。当你读一些东西我们的网站 -就是发布相关材料的API。

这并不意味着静态类型系统可以保证代码始终正确。该系统不允许没有测试。但这极大地简化了重构。

我会告诉您有关自动代码生成的信息。即,我们使用模式从数据库的结构生成类型定义。系统连接到Postgres数据库,分析列类型,并将相应的TypeScript类型定义写入.d.ts应用程序使用的常规文件

每次启动时,迁移脚本都会使具有数据库架构类型的文件保持最新状态。因此,我们不必手动支持这些类型。模型使用数据库类型的定义来确保应用程序代码正确访问数据库中存储的所有内容。没有丢失的表,没有丢失的列或null不支持的列中的条目null。我们记得null在列支持中正确处理null。所有这些都在编译时进行了静态检查。

所有这些共同创建了可靠的静态类型的信息传输链,从数据库扩展到前端中React组件的属性:

  • , ( API) , .
  • API , API, ( ) .
  • React- , API, .

在处理此材料时,我无法回忆起与通过编译的API相关联的代码中出现不一致的一种情况。我们没有出现生产失败的情况,因为与API相关的客户端和服务器代码对数据形式有不同的想法。而这并不是自动化测试的结果。对于API本身,我们不编写测试。

这使我们处于一个非常愉快的位置:我们可以专注于应用程序中最重要的部分。我花很少的时间进行类型转换。远不及我花时间去弄清混淆错误的原因,这些错误会渗透到用Ruby或JavaScript编写的代码层中,然后在离错误源很远的地方引起奇怪的异常。

这是将后端转换为TypeScript后项目的外观。如您所见,自过渡以来已经编写了许多代码。我们有足够的时间评估该决定的后果。


在项目的前端和后端都使用TypeScript。

在这里,我们没有提出此类出版物的常见问题,即不是通过打字而是通过使用测试来获得相同的结果。仅使用测试无法获得这样的结果。我们很有可能会对此进行更多讨论。

亲爱的读者们!您是否将其他语言编写的项目转换为TypeScript?


All Articles