Show HN:构建SaaS应用,用户可自主控制数据存储位置

Hacker News Top 工具

摘要

LinkedRecords是一个NoSQL数据库,让开发者能够构建用户自主控制数据存储位置的SaaS应用,无需后端代码即可直接从单页应用连接。

Hello HN,<p>我想向大家介绍 linkedrecords.com——这是一个我开发了一段时间的开源后端即服务(BaaS)。你可以把它看作 Firebase 或 Convex 的替代品,但有一个有趣的创新点。</p><p>2018年,我需要在 Google Docs 中编写大型软件需求/架构文档。虽然当时我被 Google Docs 的各种限制所困扰(图片无法添加标题、没有自动标题编号、文档大了之后速度缓慢等),但我仍然被它的实时协作功能所吸引。于是,我开始探索其工作原理,并着手实现一个 Google Docs 的替代品。</p><p>我深信这种实时协作是未来的趋势,因此我花了很多心思思考如何让这个功能尽可能通用,以便在我未来构建的所有工具中都能使用。</p><p>与此同时,我还在尝试使用 Firebase(令人惊讶的是,用 Firebase 构建 Google Docs 替代品并不容易,因为它的实时协作只支持 JSON 的合并,而不支持文本合并)。那时,我同样坚信后端即服务是正确的方向。我认为我们仍然需要编写自定义后端代码的最重要原因之一就是授权问题。</p><p>在尝试让后端尽可能通用时,我还面临另一个问题:实体之间的关系也是领域特定的。例如,一篇文档可以有多条评论。</p><p>幸运的是,2018年我还被另一个概念所吸引,它叫做 Web 3.0。当时这个概念与加密货币毫无关系。它被用来指代语义网及其标准之一的资源描述框架(RDF)。有一些现成的 RDF 实现可供复用,但它们都是基于 XML 且主要基于 Java 的。我需要一个轻量级的方案。于是,我没有自己实现一个 RDF 产品,而是借鉴了 RDF 三元存储的思想,并提出了自己的阐释。</p><p>利用三元存储和读取时模式等概念,我构建了一个后端中不含任何业务逻辑的系统。在开发 Google Docs 替代品的过程中,我逐渐爱上了这个系统,因为我发现了一些最初未曾预料到的特性:</p><p>- 在 React 中处理全局状态变得非常简单。感觉就像在浏览器中使用 SQL 客户端一样,所有查询都是响应式的,并且始终保持最新。编写查询时,你无需考虑授权问题,因为它已完全内置于系统中。</p><p>- 由于后端完全不含领域特定代码,你可以将你的单页应用指向任何 LinkedRecords 部署。</p><p>- 你永远不需要编写后端代码。</p><p>- 在使用 AI 代理时也非常高效。</p><p>体验它的最佳方式是按照这个小教程操作:<a href="https://linkedrecords.com/getting-started/" rel="nofollow">https://linkedrecords.com/getting-started/</a></p><p>掌握它需要一些时间,所以请保持开放的心态。</p><p>我非常期待看到你的反馈。</p>
查看原文
查看缓存全文

缓存时间: 2026/06/25 05:15

wolfoo2931/linkedrecords

来源:https://github.com/wolfoo2931/linkedrecords

LinkedRecords

LinkedRecords 是一种 NoSQL 数据库,你可以直接从单页应用连接它——无需后端代码。


📚 快速入门

想用 LinkedRecords 构建应用? 请查阅文档网站:

  • 简介 (https://linkedrecords.com) —— 了解 LinkedRecords 是什么以及它的工作原理
  • 快速入门指南 (https://linkedrecords.com/getting-started) —— 构建首个应用的分步指南

性能

下图展示了数据库增长时核心操作的性能。每次合并到主分支后,图表会自动更新。

负载测试模拟了一个真实的文档管理场景。每次 createDocument 操作都会创建一个包含 8 个属性(7 个 KeyValueAttribute + 1 个 LongTextAttribute)的蓝图,包括文档内容、协作者/读者组、评论、引用和配置。fetchDocuments 操作用于列出用户的所有文档,而 fetchDocument 则检索单个文档及其所有关联属性。

负载测试性能图表

负载测试的工作原理

测试模拟了一个多租户环境,包含三个用户:

  • 用户 1 不断创建文档(当前测试配置为 5000 次迭代)。
  • 用户 2 是“被测试用户”,每当用户 1 创建 10 个文档,该用户就创建 1 个文档,最多创建 300 个文档。达到 300 个文档后,该用户的文档创建停止。
  • 用户 3 偶尔创建文档(每 1000 次迭代创建一次)

x 轴表示 数据库中的文档总数(所有用户拥有的文档)。y 轴表示响应时间(毫秒)。

测量内容:

操作描述
createDocument创建一个包含所有相关属性的新文档所需的时间(内容、配置、评论、引用、协作者/读者组)。可以看到 createDocument 与数据库中的文档总数以及用户可见的文档数量无关。
fetchDocuments获取用户 2 的文档列表(最多 300 个可见文档)所需的时间。可以看到该时间取决于用户可见的文档数量,但与文档总数无关(图表在大约 3000 个文档时趋于平稳)。
fetchDocument获取单个文档及其所有相关数据所需的时间(内容、评论、组、活动状态、引用)。可以看到该时间取决于用户可见的文档数量,但与文档总数无关(图表在大约 3000 个文档时趋于平稳)。

概念

你可以将 LinkedRecords 看作一个桶,任何人都可以注册并向其中插入数据。只要你不将这些数据共享给其他用户或组,只有你能访问你写入的数据。

理论上,任何用户都可以直接使用 LinkedRecords API 写入和检索数据。但这并不方便——就像你不会期望用户编写 SQL 查询一样,你也不会期望他们与 LinkedRecords API 交互。LinkedRecords 应用是一个专门的前端,它隐藏了 API,并提供了方便的用户界面来完成任务。

在传统的 SQL 世界中,不方便并不是不让你直接访问数据库的唯一原因——授权问题更是一个重要原因。使用 LinkedRecords,这不再是问题:授权直接内置于 API 中。这需要一点思维转变:不再在后端为所有记录定义通用授权规则,而是由插入数据记录的用户指定谁可以读取它。

对于 LinkedRecords API,简单性、灵活性和解耦架构是我们追求的主要品质。

  • 简单性:API 不应有很多端点或方法;而是由少数几个基本构建块组成。
  • 灵活性:通过组合少数可用的端点,可以支持多种用例。内置的授权模型应允许实现不同的授权用例(如 RBAC 等)。
  • 解耦:LinkedRecords 应与使用它作为数据存储的单页应用解耦。

可以将其视为可以直接从 React 应用调用的 SQL,而无需担心权限;它比 SQL 更易读,并提供实时更新。

配置

LinkedRecords 通过环境变量配置。请参见下表。

环境变量名示例描述
PGHOSTlocalhostPostgreSQL 服务器的主机名。
PGUSERlinkedrecordsPostgreSQL 用户名。
PGPASSWORDxxxxPostgreSQL 密码。
PGDATABASExxxxPostgreSQL 数据库名。
CORS_ORIGIN[“https://app.example.com”, “https://app.example.app”]CORS 起源头部内容。如果未提供,将使用 FRONTEND_BASE_URL 的值。
SERVER_BASE_URLhttp://localhost:6543LinkedRecords 服务器的公共 URL。
DEFAULT_STORAGE_SIZE_QUOTA500默认存储大小配额(MB)。
QUOTA_COUNT_KV_ATTRIBUTESfalseKeyValue 属性的存储空间是否从账户所有者配额中扣除。
QUOTA_COUNT_LT_ATTRIBUTESfalseLongText 属性的存储空间是否从账户所有者配额中扣除。
ENABLE_AUTH_RULE_CACHEfalse启用授权查找缓存。可能需要大量内存。
SHORT_LIVED_ACCESS_TOKEN_SIGNINGxxxx此配置可选,但可以减少数据库负载,因为当客户端订阅属性更改时,将使用短期访问令牌进行访问检查。

机密客户端模式

如果提供了“公共客户端模式”部分描述的配置,则本节中的环境变量均为可选。

如果 LinkedRecords 以机密客户端模式运行,则会将会话令牌存储在 HttpOnly cookie 中。从安全角度来看,这被认为是推荐方法。但是,如果 LinkedRecords 服务器和前端不在同一域下,则无法实现此模式。跨域时,cookie 会成为第三方 cookie,因此无法使用此模式。

环境变量名示例描述
FRONTEND_BASE_URLhttp://localhost:3001前端的基本 URL。它将用于 Access-Control-Allow-Origin HTTP 头部,并且也是 OpenID Connect 重定向所必需的。
AUTH_ISSUER_BASE_URLhttps://xxx.us.auth0.com/OIDC 发行者的 URL。可以是任何符合 OpenID Connect 规范的身份提供者(例如 Auth0、Okta)。
AUTH_CLIENT_ID客户端 ID。可以从身份提供者获取。
AUTH_CLIENT_SECRET客户端密钥。可以从身份提供者获取。
AUTH_IDP_LOGOUTtrue设置为 true 时,用户会话将在应用和身份提供者中同时销毁。
AUTH_COOKIE_SIGNING_SECRETxxxx用于签名 cookie 的密钥。

公共客户端模式

如果单页应用托管在与 LinkedRecords 服务器不同的域上,则单页应用必须将访问令牌存储在浏览器中。在此场景下,需要配置以下环境变量。

环境变量名示例描述
ALLOW_HTTP_AUTHENTICATION_HEADERtrue允许公共客户端通过 HTTP 身份验证头部提供访问令牌来发起请求。
AUTH_ISSUER_BASE_URLhttps://xxx.us.auth0.com/OIDC 发行者的 URL。可以是任何符合 OpenID Connect 规范的身份提供者(例如 Auth0、Okta)。
AUTH_TOKEN_AUDIENCEyour-audience-idLinkedRecords 将检查 JWT 承载令牌中指定的 audience,并与该字段指定的值进行匹配。
AUTH_CLIENT_ID客户端 ID。可以从身份提供者获取。

单页应用需要按如下方式初始化 LinkedRecords SDK:

import LinkedRecords from './src/browser_sdk';

const oidcConfig = {
  client_id: 'your-client-id',
  redirect_uri: window.location.origin + '/callback',
};

// 实例化 LinkedRecords 将自动处理 OIDC 重定向回调
const lr = new LinkedRecords(new URL('https://your-backend.com'), oidcConfig);

// 检查用户是否已认证:
// const isAuth = await lr.isAuthenticated();

// 开始登录流程(例如,在按钮点击时):
// lr.login();

可选配置

S3

如果配置了 S3,则将使用它来存储 blob 属性值。如果未配置,则它们将存储在 PostgreSQL 数据库中。建议配置 S3。

环境变量名示例描述
S3_COPY_FROM_BL_ATTRIBUTE_TABLEfalse用于从 PostgreSQL 迁移 blob 存储到 S3。
S3_ENDPOINTs3.system.svc.cluster.localS3 端点的主机名。
S3_BUCKETlinkedrecords-blobs存储桶名称。该存储桶必须已存在。
S3_ACCESS_KEYxxx用于上传 blob 到 S3 的访问密钥 ID。
S3_SECRET_KEYxxx用于上传 blob 到 S3 的秘密密钥 ID。
S3_USE_SSLfalse上传/下载到 S3 时不要使用 TLS。

Paddle

环境变量名示例描述
PADDLE_NOTIFICATION_SECRETxxxx如果使用 Paddle 升级配额,则需要将此值设为通知密钥,以验证 webhook 内容的签名。
PADDLE_API_URLhttps://sandbox-api.paddle.comPaddle API 的 URL。
PADDLE_API_KEYxxxPaddle API 密钥。

相似文章

Helicone 允许用户直接向共享 ClickHouse 编写 SQL

Hacker News Top

Helicone 实现了一项名为 HQL 的功能,允许客户针对共享的 ClickHouse 数据库编写原始 SQL 查询。他们利用 ClickHouse 的行策略和自定义设置来实施多租户隔离,确保每个组织只能看到自己的数据。