
PostHog Monorepo 目录布局全解从 Django 单体到垂直切片的产品化演进【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog本篇技术指南以 docs/internal/monorepo-layout.md 为核心骨架系统讲解 PostHog 仓库的高层目录结构与组织哲学posthog/遗留单体、products/垂直切片产品、services/独立服务、packages/共享库、common/过渡暂存区、tools/开发者工具与devenv/开发环境配置各自承担什么职责、边界如何划分、代码应该落在哪里。读完你将掌握 PostHog 的代码归属判定标准代码放哪个目录、产品Product的完整形态与隔离机制、pnpm workspace 包的注册与晋升规则以及基于 intent map 的开发环境启动模型可以直接对照仓库源码逐目录验证。一、目录结构总览一张图看懂仓库分层PostHog 的 monorepo 由 7 个顶层职责域组成它们在 monorepo-layout.md 中被描述为High-level structure高层结构posthog/ # 遗留单体代码Legacy monolith code api/ # DRF views, serializers models/ # Django models queries/ # HogQL query runners ... ee/ # 企业版功能正在迁移到 products/ 和 posthog/ products/ # 产品专属应用布局见 products/README.md product/ backend/ # Django appmodels, logic, api/, presentation/, tasks/, tests/ frontend/ # Reactscenes, components, logics manifest.tsx # Routes, scenes, URLs package.json services/ # 可选该产品部署的服务见 What a product can own packages/ # 可选该产品拥有的库/CLI services/ # 不属于任何单一产品的独立服务 llm-gateway/ # LLM proxy service mcp/ # Model Context Protocol service oauth-proxy/ # OAuth proxy (Cloudflare Worker) stripe-app/ # Stripe integration app packages/ # 被多个产品/服务共享的库如 quill common/ # 共享代码——临时暂存区holding pen不是目的地目标是收缩它 hogql_parser/ # HogQL parser tools/ # 开发者/CI 工具但有一个例外见下文 hogli/ # 开发者 CLI 框架可发布到 PyPIuv workspace 成员 hogli-commands/ # PostHog 专属的 hogli 命令通过 hogli.yaml 消费 owners/ # owners.yaml 解析器posthog_owners——也是运行时依赖 devenv/ # 开发者环境配置intent map、进程模型对照当前仓库根目录上述结构全部与源码一一对应posthog/ 下确实存在 api/、models/ 等遗留单体目录ee/ 的企业功能正逐步迁移products/ 已发展出 40 个垂直切片产品如 products/feature_flags、products/experiments、products/session_replay。需要注意文档中的posthog/queries/HogQL query runners在当前仓库中已演化为独立的 posthog/hogql_queries/ 目录说明目录布局本身也在持续演进——这正是该文档强调保持高层结构清晰的原因。二、tools/ 的例外owners 同时是运行时依赖tools/默认全部是开发者与 CI 工具但其中有一个目录例外tools/owners会被安装进生产虚拟环境production venv。文档给出的原因是stamphog 的 digest摘要/审查报告通过posthog_owners解析一个团队的 Slack 频道而不是自己重新解析owners.yaml。仓库中的 tools/owners/posthog_owners/ 包含resolver.py、matcher.py、schema.py等模块即为该解析器实现tools/owners/pyproject.toml定义了它的打包配置。除此之外tools/owners还会以源码形式被复制进生产镜像与 stamphog 的审查引擎一起被放入运行时审查沙箱review sandbox。该引擎位于 products/stamphog/packages/pr-approval-agent/已在仓库中确认存在。沙箱内的路径即契约文档强调了一个容易被忽视的部署契约在沙箱内引擎被写到checkout/tools/pr-approval-agenttools/owners就放在它旁边这个放置位置是契约a contract而非遗留物——引擎通过从自身文件向上逐级查找来定位仓库根目录因此该路径决定了它读取哪份策略文件下游仓库也以完全相同的排列 vendor 这两个目录。也就是说引擎查找 repo root 的方式walking up from its own file决定了tools/下的相对排列不能随意改动否则下游仓库的 vendoring 和策略解析都会失效。这是路径即接口的一个典型工程实践。三、Products面向用户的垂直切片Product产品的定义拥有自己的后端Django app和前端React的用户可见功能。典型例子Feature Flags、Experiments、Session Replay。核心特征垂直切片Vertical slices每个产品拥有自己的 models、logic、API 和 UI隔离Isolated产品之间不导入彼此的内部实现工具支撑用Turbo做选择性测试用tach强制导入边界。文档指向的两份配套文档也已核实products/README.md 讲解如何创建产品products/architecture.md 讲解隔离设计原则DTO、facade、隔离规则。其中 products/README.md 给出了产品的标准目录形态products/ product_name/ # Turborepo package boundary manifest.tsx # 描述产品的 featuresroutes, scenes, urls package.json # 在 Turborepo 中定义产品包 backend/ # Django appmodels, logic, routes.py, facade/, presentation/, tasks/, tests/ frontend/ # Reactcomponents, scenes, hooks, logics, generated/ mcp/ # MCP 工具定义tools.yaml和 UI apps——大多数产品都有 skills/ # 面向 agent 的技能——许多产品都有 services/ # 可选该产品部署的服务 packages/ # 可选该产品拥有的库/CLI所有产品的 manifest 文件在构建时会被合并进frontend/src/products.tsx和frontend/src/products.json。3.1 产品可以拥有什么What a product can own多数产品是Django app React scenes但多数产品其实还携带更多一个mcp/目录存放 MCP 工具定义常常还有一个skills/目录存放 agent 技能。文档的原则是只要可归属于该产品的东西运行时和工具链皆可——把它嵌套在产品目录下而不是散落到顶层目录products/product/mcp/— MCP 工具定义tools.yaml和 UI 应用大多数产品products/product/skills/— 该产品的 agent 技能许多产品products/product/services/svc/— 该产品部署的服务或 workerproducts/product/packages/lib/— 该产品拥有的库或 CLIdev/CI/backfill 脚本、benchmarks、audits、fixtures、dummy-data 生成器、独立 console——同样的道理。顶层services/、packages/、tools/、cli/只留给没有任何单一产品拥有的东西。包名posthog/name与所在位置解耦——pnpm 按名称解析包因此日后移动位置只是一次路径搬迁不会引起 import 变更。嵌套的核心理由工具边界会变成路径作用域path-scoped——products/product/**可以直接用于 CODEOWNERS、CI 过滤器和 lint而不再需要手工同步product-*前缀。文档给出了一条经验法则一个前缀在做文件夹的活就是应该嵌套的信号A prefix doing a folders job is the signal to nest。3.2 例外products/desktop 是嵌套的独立工作区唯一不符合Django React形态的产品是 products/desktop——PostHog 桌面应用Electron外加移动端和 web host从 PostHog/code 仓库导入。它是一个嵌套的独立 pnpm workspace有自己的 lockfile、Node 版本和 Biome 工具链被刻意排除在根 pnpm workspace 之外有自己的desktop-*CI。这一点在根 pnpm-workspace.yaml 中有明确佐证packages: - products/* # products/desktop is a nested standalone workspace (own lockfile, catalog, # overrides, Node version); the root install must not absorb it. - !products/desktop日常通过hogli desktop:*命令驱动或cd products/desktop后直接用 pnpm。其架构细节见它的AGENTS.md。3.3 新产品的隔离要求Products 的隔离不是可选项。products/isolation_baseline.txt列出所有尚未隔离的产品hogli product:lint --all会在目录树与该文件不一致无论哪个方向时失败不在清单上的新产品必须隔离某个产品完成封口sealed后就从清单中移除。hogli product:bootstrap脚手架会直接生成一个已封口的产品——包含真正的 facade、contracts、tach[[interfaces]]、backend:contract-check、收窄的turbo.json。封口后重新生成基线bin/hogli product:lint --regenerate-baseline该基线文件由 DevEx 在.github/CODEOWNERS中拥有——因为把一个产品加入豁免清单意味着它每次变更都要跑完整 Django 后端测试套件代价显著。3.4 隔离架构与 monorepo 的衔接虽然 products/architecture.md 是独立的设计文档但它是 monorepo 布局中products/目录得以成立的核心机制简述如下Facade门面backend/facade/api.py是其他产品/core 唯一允许导入的入口facade/contracts.py用frozen dataclasses定义跨产品传递的稳定数据结构tach在tach.toml中以模块为单位强制 Python 导入边界任何绕过expose模式的导入都会被拒绝Turbo 选择性测试其他产品只依赖某产品的契约文件facade/contracts.py、facade/enums.py及 wiring locations契约未变则下游产品无需重测实现文件logic.py、models.py变更只触发本产品重测。other_product tests | depends on visual_review contracts (facade/contracts.py, facade/enums.py) | does NOT depend on visual_review impl (logic.py, models.py)文档明确指出跳过完整测试套件必须在构造上保证健全sound by construction而不是靠检查presentation 层必须保持薄、只通过 facade 触达内部model 表面backend/models/和backend/migrations/始终留在backend:contract-check输入中——因为模型可以被apps.get_model()无导入地触达tach 看不见。四、Packagespnpm workspace 包的归属规则这一节只针对 pnpm workspace 包JS/TS。Python 和 Rust 不同——对它们而言位置和导入名直接相关顶层 Python 包甚至可能遮蔽 stdlib 模块这正是没有顶层platform/目录的原因所以以下规则不适用。对 pnpm 包而言位置不限制谁能导入它pnpm 按名称解析因此位置是所有权信号ownership signal而非访问控制。按当前所有权放置只被一个产品拥有 →products/product/packages/name/默认选择——保持产品自包含真正被多个产品/服务共享 → 顶层packages/name/例如packages/quill/嵌套 → 顶层的晋升只在第二个消费者真的依赖它时才发生——基于实际使用而非意图。由于包名稳定这只是一次路径重命名无 import 变更所以不要提前为共享付费。4.1 注册路径pnpm-workspace.yaml 的显式 globs文档特别提醒一个实操陷阱pnpm-workspace.yaml的 globs 是显式的products/*、packages/quill等目前并不匹配嵌套的products/product/packages/*或新的顶层packages/name/——所以添加包时必须把它的路径注册进去否则workspace:*依赖、filters 和 scripts 都无法解析。核实当前 pnpm-workspace.yaml 中的 globspackages: - . - common/esbuilder - common/hogvm/typescript - common/plugin_transpiler - common/storybook - common/replay-shared - common/replay-headless - common/tailwind - packages/llm-normalizer - packages/quill - packages/quill/apps/* - packages/quill/packages/* - frontend - playwright - nodejs - products/* # products/desktop is a nested standalone workspace ... - !products/desktop - services/agent-proxy - services/integration-service - services/oauth-proxy - services/mcp - services/stripe-app - tools/* - !tools/hedgebox-dummy - rust/common/hogvm/node - rust/replay-anonymizer-node可以观察到products/*只匹配产品顶层packages/quill/apps/*与packages/quill/packages/*以子 glob 的方式覆盖了 quill 内部子包而顶层packages/llm-normalizer、packages/quill是逐一列出的——正好印证了新增包必须显式注册的提醒。同文件还包含 React 版本统一的overrides、patchedDependencies、onlyBuiltDependencies需 preinstall 脚本的依赖白名单与catalog共享依赖版本目录是 monorepo 依赖治理的完整样例。五、Services独立服务的判定标准顶层services/中的服务必须同时满足是它们自己的部署are their own deployment可能拥有自己的域名包含不属于任何特定产品的逻辑不是共享基础设施arent shared infrastructure不是跨领域胶水arent cross-cutting glue不是面向前端的产品arent frontend-facing products。文档特别辨析了两个不是它们不是胶水因为胶水是适配其他系统的。 它们不是产品因为没有人把它们当作面向用户的功能来交互。当前仓库的 services/ 正好对应文档列举的四项llm-gatewayLLM 代理、mcpModel Context Protocol 服务仓库中规模最大1474 个文件、oauth-proxyCloudflare Worker 形态的 OAuth 代理、stripe-appStripe 集成应用另有 agent-proxy 与 integration-service 两个后续加入的独立服务——它们也都出现在 pnpm-workspace.yaml 的 globs 中。六、Common过渡暂存区不是目的地common/是先于更好归属而存在的共享代码的暂存区——不是目的地目标是收缩它而不是扩张它。文档对万能 common给出了犀利的警告一个来者不拒的 common 必然腐烂成杂物抽屉junk drawer无范围、无强制、被一切导入——一个边界比原来更糟的第二个单体。common 这个名字本身就是坏味道The name itself is the smell按上下文命名的归属才是解药。与products/*受 tach turbo 保护不同没有任何机制机械地守卫落入 common/ 的代码——只有约定而约定如果不被难以违反就会被侵蚀。因此新代码应当先进入有真实边界的地方products/name/、tools/、services/或packages/一个干净、发布风格叶子——packages/quill是模板。只有当以上都不合适、且因为仍导入应用模块lib/*、scenes/*而暂时无法成为干净叶子时才落入common/此时应把它当作已跟踪的技术债tracked debt并指明毕业目标。一旦某块代码变成干净叶子就把它提升到packages/或所属产品并从common/删除。这些规则在 common/AGENTS.md 中有面向 agent 的完整版本其中明确写出了毕业成功路径quill走的就是这条路quill 从 common 迁往 packages/quill。当前 common/ 目录下确实以hogql_parser为典型成员同时已经出现了具备清晰归属的esbuilder、hogvm、replay-shared、replay-headless、storybook、tailwind等叶状子目录——与 pnpm-workspace.yaml 中逐个注册的 globs 对应说明这部分正在向干净叶子 显式注册的方向收敛。七、Tools开发工具与它的运行时例外默认情况下tools/存放开发者工具CLI尤以hogli/框架 hogli-commands/为要、linters、formatters、代码生成器、脚手架脚本、CI 自动化。不被运行时代码导入——只作为构建期、CI 或开发者工作流产物。仓库中的佐证tools/hogli 与 tools/hogli-commands 是文档点名的 CLI 框架与 PostHog 专属命令集根目录的 hogli.yaml 负责消费这些命令如product:bootstrap、product:lint、product:crossings其余工具涵盖 tools/ownersowners.yaml 解析器如前所述是运行时例外、tools/infra-scripts、tools/openapi-codegen、tools/query-performance-ai、tools/traffic-sim 等。八、Dev environment基于 intent map 的开发环境devenv/存放本地开发环境的配置。其核心是意图/能力模型intent/capability model驱动hogli dev:setup意图Intent开发者在做什么例如我在做 error tracking能力Capability稳定的抽象event_ingestion、replay_storage等进程定义Process definitions位于bin/mprocs.yaml每个进程通过capability字段声明自己提供哪种能力。文档提到的devenv/在仓库中落实为 devenv/intent-map.yaml其头部注释明确了领域模型Intent: What the developer is working on (products like error_tracking, session_replay)Capabilities: Stable abstractions (event_ingestion, replay_storage, etc.)Process definitions (which processes provide which capability) are in bin/mprocs.yaml.8.1 能力Capabilities稳定抽象层能力通过requires传递式解析依赖通过docker_profiles激活对应的 docker compose profile见 docker-compose.profiles.yml。能力本身不列举进程——进程在 mprocs.yaml 中声明自己的能力。示例capabilities: core_infra: description: Essential infrastructure (docker, postgres, clickhouse) docker_profiles: [] event_ingestion: description: Kafka-based event pipeline for event capture requires: [core_infra, personhog] docker_profiles: [] replay_storage: description: Session replay blob storage and capture requires: [event_ingestion] docker_profiles: [replay] llm_analytics_sentiment: description: Sentiment classification model for AI observability (installs torch, optimum ~2GB) requires: [temporal_workflows] docker_profiles: [] uv_groups: [sentiment]仓库中已定义 30 种能力覆盖事件管线event_ingestion、property_definitions、重放replay_storage、recording_rasterizer、错误跟踪error_symbolication、error_notifications、AIllm_gateway、ai_capture、embedding_service、opensearch_search、Node.js 消费组nodejs_cdp、nodejs_session_replay、nodejs_logs、nodejs_metrics、nodejs_traces以及personhog、observability、duckgres等基础设施能力。注意llm_analytics_sentiment这类重型能力通过uv_groups声明额外安装组torch 约 2GB按需启用。8.2 意图Intents开发者视角的入口意图把产品/功能域映射到一组能力上。例如intents: error_tracking: description: Track and analyze application errors capabilities: [ event_ingestion, error_symbolication, error_notifications, embedding_service, property_definitions, property_values, nodejs_cdp, nodejs_error_tracking, ] session_replay: description: Record and replay user sessions capabilities: [ event_ingestion, replay_storage, property_definitions, property_values, celery_workers, temporal_workflows, nodejs_session_replay, recording_rasterizer, ] feature_flags: description: Feature flag management and evaluation capabilities: [flag_evaluation, coordination, celery_workers, celery_scheduler, nodejs_feature_flags]always_required段则定义了无论何种意图都必需的进程backend、frontend、ngrok、typegen、docker-compose、各类 migrate、celery-worker、capture、feature-flags、personhog 等构成任何开发场景的基础栈。bin/mprocs.yaml已在仓库确认存在则是进程模型的实际定义处。这套意图 → 能力 → 进程 → docker profile的映射使得hogli dev:setup能按开发者声明的意图精确启动所需服务避免为单个功能启动整个堆栈。九、实操速查代码归属判定清单综合文档与仓库配套文档新增代码的归属优先级如下归属某个产品→ 嵌套进products/product/下的对应位置backend / frontend / mcp / skills / services / packages工具边界自动变为products/product/**路径作用域是开发/CI 工具→tools/运行时不得导入tools/owners是唯一例外是独立部署、无产品归属→services/被多个产品/服务共享的 JS/TS 库→ 顶层packages/name/并在pnpm-workspace.yaml显式注册 glob若暂时只有一个消费者先放products/product/packages/等到第二个消费者真实出现再晋升以上都不合适且尚不能成为干净叶子→ 才放入common/当作已跟踪技术债指明毕业目标并在变成干净叶子后及时提升、删除。配套的常用命令来自 products/README.md均在仓库的 bin/hogli 入口下# 脚手架一个新产品生成已封口的隔离结构 bin/hogli product:bootstrap your_product_name # 校验产品结构是否符合约定backend:test 存在、无伪 no-op 脚本等 bin/hogli product:lint your_product_name # 封口后重新生成隔离基线 bin/hogli product:lint --regenerate-baseline # 查看某产品模型类的跨产品使用情况 bin/hogli product:crossings product # 运行所有产品后端测试 / 只跑指定产品 / 查看将执行的任务 pnpm turbo run backend:test pnpm turbo run backend:test --filterposthog/products-visual_review pnpm turbo run backend:test --dry-runjson十、总结PostHog 的 monorepo 布局不是静态的目录清单而是一套持续演进的归属治理体系posthog/ee/承载历史遗留单体是企业功能向products/迁移的起点products/是当前架构的主角——每个产品是 Django React 的垂直切片通过 facade frozen contracts tach Turbo 实现可验证的隔离与选择性测试同时允许产品拥有 mcp/skills/services/packages 等一切可归属资产services/、packages/、tools/分别收纳无产品归属的独立部署、跨产品共享库与开发工具common/是明确标注应收缩的暂存区其 AGENTS.md 与毕业路径如 quill 的迁移展示了如何把暂存代码推向干净归属devenv/的 intent map 则把开发环境本身建模成意图 → 能力 → 进程的可配置系统。对正在阅读这份布局的开发者而言最有价值的心法是两条原则代码放进有真实边界的归属而不是放进 common以及用路径表达所有权让工具边界自动作用域化。前者防止第二个单体后者消除手工维护的前缀约定——这也是整个布局文档一以贯之的设计哲学。【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考