Hacker News 中文摘要

RSS订阅

重新思考数据库编程 -- Rethinking Database Programming

文章摘要

作者尝试将Elm等语言的类型系统优势引入SQL,目标包括:更精确的类型支持、编译器验证的数据库迁移、友好的错误提示,以及端到端的类型安全,以提升现代程序员使用数据库的便捷性和安全性。

文章总结

过去几年,我一直致力于将“Elm这类语言”的优势引入SQL领域。我们拥有从SQLite到PostgreSQL等出色的数据库实现,我希望让它们对现代程序员更加便捷。我的目标包括:

  • 更精确的类型。如今许多语言支持“自定义类型”,但将它们存入数据库并不容易。Rust有枚举,Elm有自定义类型,Haskell有代数数据类型等。为什么我必须手动将精确且富有表现力的类型转换成某种奇怪的二进制布局?或者转换成JSON?又或者组合使用可空列?

  • 经过验证的迁移。我发现SQL迁移非常可怕。当操作实时数据库时,我的身体会高度警觉。如果我运行的命令稍有偏差怎么办?如果我的预演环境略有不同怎么办?我感觉灾难近在咫尺。为什么会这样?我们知道列的类型,也知道我们想要它们变成什么类型。为什么不让编译器事先验证迁移呢?

  • 友好的错误信息。我在Elm中投入了大量精力来制作有用的错误信息,这启发了Rust等语言的类似努力。错误信息可以很友好!数据库查询的错误信息也应该如此!

  • 端到端的类型。我希望能够在客户端、服务器和数据库之间共享类型。如果我更改了表列的类型,我希望在Elm代码中看到高质量的错误信息。

最终成果终于可以作为公开Alpha版本分享了!二进制文件可在此处下载,你可以浏览文档和示例以获取指导。希望你能尝试一下!

什么是Acadia?

最好的概述方式是看它的实际应用:

你定义表,定义端点,然后在服务器和客户端代码中使用它们!目前我们支持Elm和Haskell集成,并计划添加更多语言。添加语言相当容易,所以请告诉我们你感兴趣的语言!我们希望在公开Alpha期间根据反馈确定优先级。

如屏幕录像所示,表定义看起来像这样:

``` type alias Food = { id : FoodID , name : String }

type FoodID = FoodID UInt64

foods : Table Security.Unrestricted Food foods := Table.table { primary = .id , security = Security.unrestricted , indexes = [] , constraints = [] } ```

你指定主键、行级安全策略以及任何你想在表中强制使用的索引和约束。然后开始定义端点。典型的查询会像任何函数式程序一样使用mapfilter

getFood : Cookies -> FoodID -> Transaction String getFood _ id = access foods Security.Unrestricted |> filter (\f -> f.id == id) |> map .name |> select

这会在编译时编译成SQL。因此,这个特定端点会变成类似这样的内容:

SELECT f.name FROM "Foods.foods" AS f WHERE f.id = $1

我投入了大量精力确保生成的SQL质量很高。Acadia会打印出它正在运行的SQL,这样你可以在开始实验时自行评估质量。编译器会自动进行许多优化。

你还可以使用“bind”语法构建更复杂的事务。它的工作方式有点像async/await语法。以下是我网站数据库的一个例子。当你重置Acadia密码时,我会向你的邮箱发送一个秘密UUID,你需要将其返回给我以确认身份。因此,我的passwordResetInit端点会生成一个新的UUID,获取当前时间,并将这些信息插入到resetSessions表中:

``` type EmailSecret = EmailSecret Uuid.Uuid

passwordResetInit : Cookies -> Email -> Purpose -> Transaction EmailSecret passwordResetInit _ email purpose = let secret := Uuid.generate EmailSecret created := Time.now

() :=
  insert resetSessions (EmailInit email)
    { secret = secret
    , email = email
    , created = created
    , purpose = purpose
    }

in Transaction.succeed secret ```

:=符号称为let绑定,它让你能够以简洁的方式提取Transaction的结果。这个let的结果是一个单一事务,只有在每一步都成功时才会提交。这使得在多个地方使用像secret这样的值变得更加容易。(相比之下,在SQL中你需要使用RETURNING来传递值。)

如果你想尝试Acadia,编译器可在此处下载,你可以根据文档和示例开始实验。还有一个Discord频道可以寻求额外指导。

背景故事

早在2017年,我就在探索Elm服务器端渲染的设计。我需要从数据库加载数据,然后为这些特定数据渲染HTML。但由于与SQL表的类型不匹配,所有设计最终都令人不满意。“我知道数据在那里,但并不能保证它一定在那里。”没有简单的方法来确保我的客户端代码和数据库代码保持一致……Elm中JSON处理的情况也类似。到2019年,我开始从许多使用Elm的公司那里听到同样的反馈。我问他们在实践中遇到了什么问题,他们通常会说:“Elm代码基本没问题。X或Y可能更方便一些,但我们主要的工程挑战在后端代码上。”越来越多地,我们都遇到了同一个根本问题。数据库中的类型并不完全是我们想要的,而在基于这些类型构建的每一层上,我们都在做大量容易出错的工作,毫无意义地在不同格式之间转换数据。

与此同时,我还在应对《编程语言经济学》中概述的组织挑战,并试图找到《重新思考我们的采纳策略》中描述的前进希望之路。

因此,在2020年,我和Tereza开始探索“存储过程”是否是一个好的编译目标。我最初的设计基本上是带有更现代类型系统的SQL语法。它有点笨拙,但也许能行。我把初稿给Tereza看,她立刻说:“我以为它会像Elm一样,使用mapfilter……”一个优雅得多的设计!

我决定像“研究生”一样安静地从事这个项目的一个重要原因是,我认为它可能无法实现。迁移如何工作?如何防止1+N查询?“没有递归函数的函数式语言”在SQL中是否总是有明确的含义?对于SQLite和PostgreSQL,答案会不同吗?等等。我还怀疑完成这个项目所需的时间可能比我预期的要长……

经历了许多起伏之后,最终一切皆有可能!设计比我预期的要好得多,看到人们在私有Alpha期间使用它真的很有趣。

下一步计划

我试图在这里和主页上对Acadia进行高层概述,但许多主题需要进一步解释。如何完全避免1+N查询?表迁移如何处理旧客户端?它如何避免对象关系映射(ORM)中常见的问题?(没有对象!)等等。我计划在未来几周内撰写一些关于这些主题的独立文章,所以请告诉我你对哪些方面最感兴趣!

我还试图保持Acadia相对精简,以便最终能够发布。这个版本是“公开Alpha”,因为还有许多功能需要添加或改进,所以请在你实验时记住这一点!应该可以用Acadia编写大多数典型查询,但更高级的功能如窗口函数或自定义聚合函数并未包含在最小可行产品中。它们将来应该可以在Acadia中表达,而且由于底层只是普通的SQLite,你随时可以根据需要降级到SQL。我将加快发布周期,所以请在Discord中提问,并在实验时分享你的项目。这将帮助我决定如何确定优先级!

自从2012-2014年与Elm共事以来,我从未对自己的语言工作如此热情,我很期待看到你用这个早期版本的Acadia创造出什么!

感谢

感谢Tereza评估并完善了Acadia中的几乎每一个想法。希望一切顺利,我们能够全职从事Elm和Acadia的工作。感谢所有在项目进展期间坚持使用Elm的程序员。我很高兴能走出这个“重建阶段”,终于能够回到语言开发的公开方面。我准备了许多博客文章,并且在最终确定Acadia版本的同时,一直在开发Elm 0.19.3。感谢所有参与私有Alpha的人!你们提供了极其高质量的反馈,设计因此变得更加完善。

最后,感谢在公开Alpha期间尝试Acadia的人们!希望你们能有一个愉快的体验!

评论总结

根据评论内容,总结主要观点如下:

正面评价
- 由Elm作者Evan Czaplicki开发,语言设计精良,可复用函数和管道编译为SQL,支持sum types,令人期待。
- “Having reusable functions and pipelines compiling to SQL sounds amazing.” (mjaniczek)
- “Evan is extremely thoughtful about design; I know he’s put a huge amount of work into this.” (gbjcantab)

负面评价
- 代码可读性差,将SQL一行代码变成六行难以理解的代码。
- “So this is capable of turning a one-liner of SQL into six lines of barely readable code?” (pelagicAustral)
- 许可证限制严格,订阅制且终止后可能丢失数据,风险高。
- “You may lose access to any data or content created with or stored in the Software.” (dwohnitmok)
- 与SQL共存的说法存疑,sum types自定义编码可能阻碍互操作性,类似ORM的阻抗不匹配。
- “I’m wary of languages that seek to own the database... the claimed interop... really more of a temporary stopping point.” (dwohnitmok)
- 功能有限,无法覆盖数据库高级特性(如分区、压缩),且文档不足。
- “The issue with defining schemas in a non-SQL programming language is they always lag behind what the underlying database can do.” (mike_hearn)
- “Needs proper docs... stuff like 'The endpoint keyword' just gets a mention on the front page.” (anentropic)

中立/比较性观点
- 与Haskell的Selda等已有方案类似,并非革命性创新。
- “Haskell has had stuff like this for more than a decade, Selda is probably the one closest to Acadia.” (jeremyjh)
- 更适合作为嵌入式DSL,但当前与HTTP服务器耦合,且Lamdera等替代方案在前后端融合上更优。
- “Acadia seems like it would be great as an embedded DSL... something like Lamdera seems a better fit.” (dwohnitmok)

总体印象:评论者对Acadia的潜力认可,但对其许可证、可读性、互操作性和功能覆盖度存在显著担忧,认为其定位模糊,与现有工具相比优势不突出。