Memos 的架构决策记录(ADR)体系:命名规范、状态生命周期与三份核心 ADR 的工程落点 Memos 的架构决策记录ADR体系命名规范、状态生命周期与三份核心 ADR 的工程落点【免费下载链接】memosOpen-source, self-hosted note-taking tool built for quick capture. Markdown-native, lightweight, and fully yours.项目地址: https://gitcode.com/GitHub_Trending/me/memos本文以 Memos 仓库的 ADR 总览文档 为主体完整解析 Memos 如何管理架构决策记录ADR 的索引机制、文件命名约定、状态机Proposed / Accepted / Rejected / Superseded、建议的文档结构与决策流程并结合当前三份 Accepted 状态 ADR标签语法、用户名格式、Space UID对应的仓库源码实现说明决策如何落到可验证的代码事实上。读完本文你将掌握在开源项目中查阅、撰写和维护 ADR 的完整方法并能快速定位每个决策在 Memos 源码中的落点。什么是架构决策记录以及 Memos 为什么需要它ADRArchitecture Decision Records用于记录那些在相关实现工作完成后、其决策理由rationale仍需要保持可查阅的重大技术与产品决策。这一动机在 Memos 中体现得很直接标签识别、用户名校验、Space 标识分配这类规则如果只散落在代码和 Issue 讨论里后续维护者很难回答“当年为什么这样设计”。Memos 的 ADR 目录 就是这类决策的集中存放地配合 领域术语表 形成设计文档体系术语表定义跨文档共享的产品与领域语言而协议、Unicode、解析器层面的专有术语保留在使用它们的 ADR 中。ADR 索引当前仓库中的三份决策记录ADR 总览 维护一张索引表记录每份 ADR 的编号、标题、状态与日期。当前索引如下ADR标题状态日期0001Tag Syntax and RecognitionAccepted2026-08-010002Username Format and ReferencesAccepted2026-08-020003Space UID Allocation and FormatAccepted2026-08-27三份 ADR 分别定义了 Memos 三种核心“标识语言”ADR 0001标签语法与识别。规定#引导的标签在 Memos Markdown 中的词法形式基于 Unicode UAX #31 与 Emoji 17.0 数据钉住在 Unicode 17.0、/层级分隔语义、Markdown 上下文资格判定哪些节点透明、哪些不透明、精确相等不做大小写折叠或 Unicode 规范化的标签身份以及层级展开产生的“隐含祖先标签”语义。ADR 0002用户名格式与引用。规定可写用户名的规范文法1 到 36 个 ASCII 字符字母/数字/连字符首尾必须为字母或数字保留大小写用户名到用户 ID 的解析规则精确 ASCII 字节相等以及 Markdownmention作为用户名引用的第一种源形式。ADR 0003Space UID 分配与格式。规定由第一方客户端生成小写 UUID v4 作为 Space 的公开 UID也可自定义API 字段保持可选以兼容旧客户端以及 UI 上何时需要展示 UID 以区分同名 Space 的规则。每份 ADR 都遵循统一的上下文开头声明Status与Date并链接到 领域术语表 作为共享词源。命名与编号约定ADR 总览 明确了以下硬性约定文件命名为NNNN-short-kebab-case-title.md使用四位数编号且编号永不复用。在 ADR 立项opened时即分配下一个编号——即使更早的 ADR 后来被拒绝或被取代。文档标题使用# ADR NNNN: Title形式日期记录为YYYY-MM-DD。已接受的 ADR 作为历史记录保留。决策变更时用新 ADR 取代而不是改写原始理由。当一份 ADR 取代另一份时在新 ADR 中添加Supersedes: ADR NNNN在旧 ADR 中添加Superseded by: ADR NNNN形成双向交叉链接。“编号永不复用”是关键设计它保证了任何时刻仓库历史中引用的 ADR 编号都能稳定定位到一份文档即使那份文档最终状态是 Rejected 或 Superseded。状态定义ADR 的生命周期由四个状态描述Proposed讨论中可能仍有未解决的问题。Accepted已批准作为要实施和长期维护的决策。Rejected经过考虑但未被选中。Superseded被后续 ADR 取代。当前仓库的三份 ADR 均处于 Accepted 状态因此它们同时是规范性文档——描述的规则是正在实施和维持的决策而不是历史快照。建议结构无需重开讨论即可理解决策每份 ADR 应包含足够的信息使读者不必重建当时的讨论就能理解决策。建议结构如下可选小节在不提供有用上下文时可以不写# ADR NNNN: Title Status: Proposed Date: YYYY-MM-DD ## Context ## Decision drivers ## Decision ## Consequences ## Alternatives considered ## Open questions before acceptance ## References对照 ADR 0001 可以发现这套结构在真实文档中的落地方式Context说明现状问题。ADR 0001 指出 Memos 当时存在四个行为不一致的标签实现Go 解析器、前端 remark 插件、编辑器装饰、编辑器补全并明确“这些差异暴露了实现漂移但不定义目标语言当前行为非规范性”。Decision drivers列出决策驱动力如“对多语言个人笔记自然工作”“让后端、渲染器、编辑器装饰与补全获得相同的值和源跨度”“保持解析在 Go 与 JavaScript 运行时间确定性一致”。DecisionADR 0001 在此给出了完整的形式文法TagCandidate : Introducer TagSourceSpelling等、15 条编号规则、最大前缀扫描算法与 Markdown 上下文判定ADR 0002 给出了用户名文法与 mention 候选识别规则。Consequences分 Positive / Negative 两列。例如 ADR 0001 的负面后果包括“全限定 emoji 匹配需要序列感知数据而非简单字符类”“钉住 Unicode 数据意味着数据更新时需要维护”。Alternatives considered每个被否决的备选方案都有明确理由如“保留 100 code point 上限”被否决的理由是“该值武断、统计的是 code point 而非用户感知字符且当前存在三种不同的溢出行为”。决策流程ADR 总览 规定四步流程用下一个可用编号创建Proposed状态的 ADR并加入索引。与维护者讨论提案随决策演进更新 ADR。结论明确后将状态改为Accepted或Rejected。若已接受的决策发生实质性变更创建新 ADR 并用交叉链接关联被取代的记录。流程与“编号永不复用”“不改写原始理由”两条约定共同保证ADR 目录是只增不减的决策历史任何规则演进的因果链都可追溯。决策的源码落点从 ADR 到可验证的实现ADR 的价值在于决策可以被代码印证。当前仓库中三份 ADR 的核心规则都能在源码中找到对应实现。用户名格式ADR 0002internal/base/username.go 实现了 ADR 0002 的规范文法MaxUsernameLength 36IsValidUsername要求首尾为 ASCII 字母或数字、中间字符由IsUsernameCharacter字母、数字、-构成。ADR 中“完整消费后连续串再整体校验、不截短为合法前缀”的 mention 识别规则对应 internal/markdown/parser/mention.go 的FindMentionMatches先扫描完整字符串再用IsValidUsername整体判定并用IsUsernameCharacter判定左边界。ADR 0002 还规定用户名相等是精确 ASCII 字节相等且这一规则由 schema 而非数据库默认排序保证。仓库中的迁移文件提供了直接证据MySQLstore/migration/mysql/0.31/05__case_sensitive_username.sql 将username列改为COLLATE utf8mb4_binLATEST.sql 中该列声明为VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL UNIQUEPostgreSQLstore/migration/postgres/0.31/05__case_sensitive_username.sql 将其改为TEXT COLLATE CSQLitestore/migration/sqlite/LATEST.sql 中声明username TEXT COLLATE BINARY NOT NULL UNIQUE。三种后端的显式大小写敏感二进制排序与 ADR 中“Alice和alice是不同用户名”的结论逐字对应。标签识别ADR 0001ADR 0001 的“钉住 Unicode 17.0 数据”在 Go 侧有明确落点internal/markdown/parser/tag.go 通过go:generate指令从tagdata生成 tag_unicode_tables.gounicode17XIDContinue、unicode17DefaultIgnorable、unicode17CombiningMarks等表isSegmentStarter、isSegmentContinuation、isApostropheJoiner三个判定函数正是文法中SegmentStarter/ValueUnit/ApostropheJoiner三条产生式的实现matchFullyQualifiedEmoji实现了“最长全限定 emoji 优先”的原子匹配FindTagMatches则按“emoji 优先 → 字符引用跳过 → 转义跳过 →#引导扫描”的候选枚举顺序工作。ADR 中#C、#RD、#foo/́bar等大量规范性示例表即为该实现的验收基准。前端侧对应 web/src/lib/tag.ts 中的标签元数据匹配逻辑findTagMetadata先做精确键匹配再把每个键作为锚定正则^pattern$测试这印证了 ADR 0001 中“标签元数据规则可选择性匹配多个派生值但不改变其身份”的领域语义——元数据只是装饰层标签身份仍由源文本派生。Space UIDADR 0003ADR 0003 规定 Space UID 复用既有的公开资源 UID 文法。该文法在 internal/base/resource_name.go 中以UIDMatcher正则落地^a-zA-Z0-9?$与 ADR 的 BNF 产生式Alphanumeric UIDCharacter{0,34} Alphanumeric1 到 36 字符完全一致。值得注意的是 ADR 0002 特意提醒“通用的资源名规则不是用户名契约不能仅因正则相似就复用作替代”——仓库中UIDMatcher与IsValidUsername正是两套独立实现的直接例证前者允许首尾连字符形态后者严格排除。如何参与 ADR 演进基于上述约定对 Memos 的 ADR 体系可以归纳出对贡献者的清晰路径查阅现有决策先看 docs/adr/README.md 的索引表理解术语查 docs/glossary.md若认为某个已接受决策需要变更正确做法是按 ADR 总览 的流程新建一份ProposedADR、取下一个未复用编号、在索引中登记并在结论明确后标注Supersedes/Superseded by交叉链接——而不是修改旧 ADR 的理由章节。这一机制使 ADR 目录既是决策的“当前事实来源”也是可审计的决策演化史。【免费下载链接】memosOpen-source, self-hosted note-taking tool built for quick capture. Markdown-native, lightweight, and fully yours.项目地址: https://gitcode.com/GitHub_Trending/me/memos创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考