PostHog 的 products 垂直切片架构:产品边界、Facade 契约与 Turbo 选择性测试实战指南

PostHog 的 products 垂直切片架构:产品边界、Facade 契约与 Turbo 选择性测试实战指南 PostHog 的 products 垂直切片架构产品边界、Facade 契约与 Turbo 选择性测试实战指南【免费下载链接】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/posthogPostHog 将每个产品如 feature_flags、experiments、visual_review组织为products/product_name/下的垂直切片——一个切片同时包含后端 Django 应用、前端 React/TypeScript 代码与可选共享代码且整个产品目录被当作一个 Turborepo 包。本文以 products/README.md 与 products/architecture.md 为主体结合仓库源码系统讲解产品目录结构、Django 后端与前端约定、hogli脚手架建品流程、独立产品数据库含熔断器、以及基于冻结数据类契约的隔离测试体系帮助读者在 PostHog 大型 Django monolith 中新增、迁移与维护一个自洽可独立演进的产品。一、核心思想一个产品 一个垂直切片 一个 Turborepo 包PostHog 的产品化改造目标不是简单分目录而是建立自包含的演进单元垂直切片每个产品包含自己的后端Django app、前端React/TypeScript可选地包含共享代码确保功能自洽、可独立演进Turborepo 包边界整个products/product_name/目录被视为一个 Turborepo 包后端与前端是该包的两个子部分通过package.json声明脚本与依赖选择性测试只有受改动影响的产品测试会被执行从而降低 CI 成本、保持开发速度详见后文「Turbo 选择性测试」。这一方向背后的架构动因冻结数据类、facade、隔离测试的详细论证记录在 architecture.md 中下文相关小节会深入展开。二、目录结构约定一个标准产品目录如下节选自 products/README.mdproducts/ __init__.py product_name/ # Turborepo package boundary __init__.py # 允许 products.product.backend.* 导入 manifest.tsx # 描述产品的功能scenes、routes、urls 等 package.json # 定义产品在 Turborepo 中的包 backend/ # Django app __init__.py apps.py models.py logic.py # 业务逻辑 routes.py # API 路由register_routes(routers)从 INSTALLED_APPS 自动发现 migrations/ facade/ # 跨产品 Python 接口 __init__.py api.py # facade 方法 contracts.py # 冻结数据类 枚举 enums.py # 可选导出的枚举/共享类型 presentation/ # DRF 视图/序列化器 __init__.py views.py serializers.py urls.py tasks/ # Celery 任务 __init__.py tasks.py tests/ conftest.py test_*.py frontend/ components/ scenes/ hooks/ logics/ generated/ # OpenAPI 生成的 TypeScript 类型 mcp/ # MCP 工具定义tools.yaml与 UI 应用——多数产品都有 skills/ # 产品的 agent skills——许多产品都有 services/ # 可选该产品部署的服务 packages/ # 可选该产品拥有的库/CLI仓库中以 products/visual_review/ 为最完整的参考实现其backend/下包含facade/api.py、contracts.py、enums.py、tasks.py、logic/约 24 个业务模块、presentation/、tasks/、management/commands/、migrations/与tests/顶层还有frontend/、mcp/、skills/、cli/、manifest.tsx、turbo.json、package.json。这正是一个产品还能拥有什么见第五节的实际样板。命名规则顶层产品文件夹必须使用下划线命名under_score因为连字符dash在部分语言如 Python中难以导入用bin/hogli product:bootstrap name即可按此结构快速脚手架一个新产品详见第六节。三、后端约定每个 backend 都是真正的 Django Appbackend/目录是一个真实的 Django 应用必须通过AppConfig注册进INSTALLED_APPS。以 products/visual_review/backend/apps.py 为真实样例from django.apps import AppConfig class VisualReviewConfig(AppConfig): default_auto_field django.db.models.BigAutoField name products.visual_review.backend label visual_review注意label是短名visual_review而非products.visual_review这保证了迁移与应用标签的稳定。导入约定✅始终使用真实 Python 路径导入from products.feature_flags.backend.models import FeatureFlag✅ 关系字段使用字符串应用标签class Experiment(models.Model): feature_flag models.ForeignKey( feature_flags.FeatureFlag, on_deletemodels.CASCADE, )❌不要从posthog.models导入模型也不要创建类似products.feature_flags.models的再导出re-export。这样约定是为了避免循环导入并保持迁移与应用标签稳定。在 posthog/settings/web.py 中可以看到真实的PRODUCTS_APPS列表它把每个产品的AppConfig依次铺开最终合并进INSTALLED_APPS。API 路由register_routes 自动发现产品的 API 路由集中在backend/routes.py以register_routes(routers)函数形式暴露一旦产品进入PRODUCTS_APPSposthog/api/__init__.py便会自动发现并调用它无需改动 core。可用的路由器句柄projects/environments/organizations/root定义在 posthog/api/routing.py 的RouterRegistry中。真实示例见 products/visual_review/backend/routes.pydef register_routes(routers: RouterRegistry) - None: visual_review_repos_router routers.projects.register( rvisual_review/repos, RepoViewSet, project_visual_review_repos, [project_id] ) visual_review_repos_router.register( rsnapshots, SnapshotViewSet, project_visual_review_snapshots, [project_id, repo_id] ) ...四、前端约定与共享代码每个产品的frontend/目录是该产品的前端应用与后端处于同一个 Turborepo 包下前后端工具链可以彼此独立requirements.txt与package.json但共享同一个包边界Jest 单元测试放在frontend/tests/或与文件同目录的*.test.ts/*.spec.tsPlaywright 端到端测试放在frontend/e2e/下的*.spec.ts由 playwright.config.ts 自动发现并由 E2E CI 工作流运行共享 fixture 通过playwright-utils/*与playwright-pages/*别名导入细节见 playwright/README.md。若前后端需要共享 schema、校验器或常量放在产品下的shared/目录中但应保持最小化避免过度耦合。前端必读要点manifest.tsx描述产品的前端scenes、routes、urls、文件系统类型与项目树navbar项构建时所有 manifest 会被合并进 frontend/src/products.tsx 与frontend/src/products.json由于products.tsx不复制 import新增图标需手动更新frontend/src/products.tsx的导入只需一次若要在旧的 pre-project-tree navbar 加链接手动改 frontend/src/layout/navigation-3000/navigationLogic.tsx每个 scene 可以默认导出 React 组件或导出export const scene: SceneExport { logic, component }同时导出 logic 与 component——这样离开页面时 logic 仍保持挂载避免每次进入 scene 都重载。五、一个产品还能拥有什么backend/与frontend/是核心但产品的归属远不止这些。除backend/frontend/manifest.tsxpackage.json外多数产品还携带mcp/— MCP 工具定义tools.yaml与 UI 应用大多数产品会暴露skills/— 产品的 agent skills许多产品具备。以及任何可归因于单一产品的东西都应嵌套在产品目录内而非放进顶层目录services/svc/— 产品部署的服务或 workerpackages/lib/— 产品拥有的库或 CLIdev/CI/回填脚本、基准测试、审计、fixtures 与虚拟数据生成器。协同存放让工具边界CODEOWNERS、CI 过滤器、lint统一落在products/product/**路径上而不是手工同步product-*前缀。顶层tools/、services/、packages/、cli/仅保留给没有任何单一产品拥有的东西。完整论证与先嵌套再提升nest-then-promote规则见 docs/internal/monorepo-layout.md 的What a product can own一节。六、新建产品用 hogli 脚手架最简单的方式是使用 hogli仓库根目录 hogli.yaml 定义其配置bin/hogli product:bootstrap your_product_name该命令会生成完整结构apps.py、package.json、facade、contracts、tach[[interfaces]]、backend:contract-check、收窄的 turbo.json 等且脚手架出的产品**自首个提交起就是 sealed已密封隔离**的。校验产品结构是否遵循约定bin/hogli product:lint your_product_nameproduct:lint会验证Presence应有性backend:test必须存在隔离产品还必须具备backend:contract-checkAbsence应无性非隔离产品或存在 legacy interface leakscore 仍导入内部实现的产品不得声明backend:contract-check——turbo-discover正是用这个键来判定产品是否隔离其存在会导致该产品变更时跳过完整 Django 测试套件Legacy leakstach.toml中带 TODO legacy leak 块的产品会在 tach 边界检查中显示⚠警告脚本内容针对backend:test不允许|| true或|| exit 0会吞掉 CI 测试失败backend/内含真实测试文件时不允许 no-op 脚本如echo No backend tests命令中引用的 pytest 路径必须真实存在且含可发现的测试。[!NOTE] 若要把已有产品迁移到完全隔离facade contracts 选择性测试使用isolating-product-facade-contractsskill目标架构详见 products/architecture.md。新产品必须隔离products/isolation_baseline.txt 列出了所有尚未隔离的产品bin/hogli product:lint --all会在树与该文件双向不一致时失败。不在名单上的新产品必须是隔离的产品完成密封后则从名单中移除。由于product:bootstrap脚手架出的产品已自带 facade、contracts 与完整隔离配置新产品永远不应该需要在 baseline 中占一行。往名单里加行等于豁免产品隔离、让完整 Django 套件在其每次变更时都全量运行因此该文件由 DevEx 通过.github/CODEOWNERS持有。产品密封后重新生成 baselinebin/hogli product:lint --regenerate-baseline注意baseline 是机械输出禁止手工编辑——手工编辑会把棘轮ratchet退化成精心策划的白名单。七、手动搭建流程不借助脚手架7.1 目录与前端创建products/your_product_name保持下划线命名创建manifest.tsx描述产品的scenes、routes、urls、文件系统类型与项目树项创建package.json包名保持posthog/products-your-product-name必须包含posthog/products-前缀在全局 frontend/package.json 的dependencies中加入新包场景按正确路径接入后即可工作。7.2 后端与接入若产品有 Python 后端创建__init__.py与backend/__init__.py后者把 backend 目录标记为 Python 包/Django app用AppConfig注册label name而非products.name修改 posthog/settings/web.py把新产品加入PRODUCTS_APPS修改 tach.toml为产品添加新块tach 用于追踪 Python 应用间的跨依赖在backend/routes.py编写register_routes(routers)如routers.projects.register(rmy_thing, MyThingViewSet, project_my_thing, [team_id])接入后自动被发现。7.3 结构样例visual_review 的 package.json真实产品的脚本定义参考 products/visual_review/package.json{ name: posthog/products-visual-review, scripts: { backend:test: pytest -c ../../pytest.ini --rootdir ../.. backend/tests -v --tbshort, backend:test:unit: pytest -c ../../pytest.ini --rootdir ../.. backend/tests -v -m not integration --tbshort, backend:test:integration: pytest -c ../../pytest.ini --rootdir ../.. backend/tests -v -m integration --tbshort, backend:lint: ruff check --config ../../pyproject.toml backend ruff format --config ../../pyproject.toml --check backend, backend:contract-check: echo Contract files unchanged }, peerDependencies: { posthog/brand: catalog:, posthog/icons: catalog:, posthog/react: *, react: * } }其 turbo.json 定义了backend:contract-check的收窄输入contract 输入{ extends: [//], tasks: { backend:contract-check: { inputs: [ backend/facade/**, backend/presentation/**, backend/routes.py, backend/tasks/**, backend/models.py, backend/migrations/** ], outputs: [], cache: true } } }八、添加或迁移后端模型与迁移在产品的backend/下创建/迁移模型使用真实路径直接导入如from products.experiments.backend.models import Experiment使用字符串外键避免循环导入如models.ForeignKey(posthog.Team, on_deletemodels.CASCADE)创建products/your_product_name/backend/migrations目录运行python manage.py makemigrations your_product_name -n initial_migration。热表外键Hot Table FK安全一个ForeignKey若指向热表posthog.Team、posthog.User、posthog.Organization、posthog.Project含settings.AUTH_USER_MODEL即使在同库内、即使是全新的CreateModel也不安全构建 FK 约束会对被引用父表加SHARE ROW EXCLUSIVE锁写入流量下可能卡住部署。CI 中的HotTableAlterPolicy会拦截这种情况。两种合规做法声明db_constraintFalse完全不锁父表仅应用层强制或用两阶段辅助函数AddForeignKeyNotValid/ValidateForeignKey添加真实约束。这一约束与后文的跨数据库 FK 限制是两个不同问题——后者涉及跨独立产品数据库的 FK。移动旧模型SeparateDatabaseAndState如果把模型从旧的posthog/models/目录移出需要额外步骤确保模型Meta设置了db_table old_table_name且managed True运行python manage.py makemigrations posthog -n remove_old_product_name生成的迁移会DROP TABLE旧表并CREATE TABLE新表这不是我们想要的应在两个迁移中都使用migrations.SeparateDatabaseAndState把操作全部放入state_operations []保持database_operations []为空参考示例posthog/migrations/0548_migrate_early_access_features.py 与 products/early_access_features/migrations/0001_initial_migration.py合并前务必多次运行与测试——数据丢失不可逆。九、独立产品数据库数据库隔离是产品隔离架构的一部分论证见 products/architecture.md产品之间通过 facade 与冻结数据类契约通信绝不共享 ORM 查询或跨产品 join独立数据库在基础设施层面强制这一点——你的产品够不到别人的表就不可能意外耦合。新产品默认拥有独立 Postgres 数据库hogli product:bootstrap会自动添加路由。退出机制从 products/db_routing.yaml 删除该产品条目一切回落到default。但这会削弱隔离——产品内一次坏迁移或流量尖峰可能影响整个应用且无法阻止意外的跨产品 ORM 查询。可接受的退出理由产品没有模型或处于早期原型阶段尚未遵循 facade 模式。9.1 工作原理products/db_routing.yaml 中的一条路由声明哪个 app label 拥有独立数据库routes: - app_label: visual_review database: visual_review声明后会自动注册visual_review_db_writer与visual_review_db_reader两个 Django 数据库别名通过ProductDBRouter路由visual_reviewapp 的所有读写通过bin/migrate调用migrate_product_databasesmanagement command执行迁移通过 Postgres init 脚本在本地 Docker 中创建数据库。本地DEBUG1会自动连接 localhost 上的posthog_visual_review生产环境由基础设施自动处理 env vars 与连接。若 env var 缺失路由被静默跳过。9.2 新增产品数据库在 products/db_routing.yaml 添加路由当前已有 stamphog、visual_review、warehouse_sources_queue 三条请#team-infrastructure提供数据库——集群、凭据与连接管道由他们负责。9.3 团队作用域必选每个存储租户数据的模型必须有team_id字段——这是 PostHog 主要的租户隔离边界没有它就无法强制一个团队看不到另一个团队的数据。产品数据库请用ProductTeamModel作为基类它提供team_id外加一个fail-closed manager在无团队上下文时发起查询会抛TeamScopeError。完整 API 见 posthog/models/scoping/README.md。from posthog.models.scoping.product_mixin import ProductTeamModel class MyModel(ProductTeamModel): name models.CharField(max_length255) # team_id 已继承 —— BigIntegerField、带索引、无 FK9.4 跨数据库约束Postgres 不支持跨库外键。产品数据库上的模型不得对主库模型Team、User 等使用ForeignKey改用普通整数字段# 这样做 —— 普通整数无 FK 约束 team_id models.BigIntegerField(db_indexTrue) # 不要这样做 —— 无法引用另一数据库中的表 team models.ForeignKey(posthog.Team, on_deletemodels.CASCADE)同一产品数据库内部的 FK 没问题。这与 facade 模式一致产品需要 Team/User 数据时通过 facade 用 ID 获取而不是直接 join 表。由此带来的后果跨库无select_related/prefetch_related—— 使用 facade 或手工批量获取主库无ON DELETE CASCADE—— 在应用代码或后台任务中处理清理无跨库transaction.atomic()—— 为跨边界最终一致性eventual consistency设计。9.5 韧性熔断器独立数据库只有在产品宕机不会拖垮整个应用时才算真正隔离故障。两层机制保证这一点每个产品别名connect_timeout3——连接不可达主机 3 秒即失败而不是阻塞在 OS TCP 默认值60–120 秒上fail-fast 熔断器posthog/db_circuit_breaker.py位于自定义数据库后端posthog.db_backends.failopen上。产品库不可达时熔断器在几次连接失败后打开open随后在连接时微秒级直接抛错而非等待超时——这释放了 worker 去服务其他请求单个产品库宕机不会耗尽共享 worker 池、不会让整个应用离线。熔断器状态存于Redis因此一个 worker 触发熔断会被所有 pod 同时看到。熔断是按产品别名的打开期间只有该产品的端点失败快速返回OperationalError其余一切不受影响。冷却期后单个探针请求测试恢复并在成功后关闭熔断器。若 Redis 本身不可用熔断器失败安全保持关闭——它永远不会成为把健康数据库拖下线的元凶。没有到default的 fail-open 重定向——产品表根本不存在于default重定向只会得到另一个错误。隔离的收益是快速、受控的失败而非静默降级。可用PRODUCT_DB_CIRCUIT_BREAKER_*环境变量调参测试中默认禁用。十、Facade 契约产品间唯一的公共接口products/architecture.md 定义了产品隔离的目标架构每个产品通过backend/facade/暴露 facade——这是 core 与其他产品唯一允许导入的地方tach 强制。api.py持有数据能力接受并返回契约的函数。10.1 契约contracts.py冻结数据类产品在backend/facade/contracts.py中用冻结数据类定义公共接口这是唯一能跨产品边界的数据结构。规则无 Django 导入不可变frozenTrue小巧、可哈希、稳定facade 以它们为输入、以它们为输出。数据类口味选择stdlibdataclasses.dataclass是基线pydantic.dataclasses.dataclass是首选升级——保留完整数据类语义通过is_dataclass()、兼容DataclassSerializer、相同 kwargs 构造、frozenTrue、field(default_factory...)仅 1 行 import 即获得 Pydantic 运行时类型校验。仅当契约真正需要数据类没有的特性字段别名如 camelCase wire/snake_case Python、schema 中暴露的计算字段、自定义 validator、判别联合才用pydantic.BaseModel否则会失去基于is_dataclass()的工具链。示例from pydantic.dataclasses import dataclass dataclass(frozenTrue) class Artifact: id: UUID project_id: int content_hash: str storage_path: str width: int height: int size_bytes: int created_at: datetimeDTO 校验是尽力而为不是 HTTP 校验——不可信输入的契约由 DRF 序列化器或 HTTP 边界的 Pydantic schema负责Pydantic 数据类校验捕获的是后端内部的构造期错误。注意 Pydantic v2 数据类会做无歧义强制转换string→UUID/datetime、int→str如需严格类型可逐契约dataclass(frozenTrue, configConfigDict(strictTrue))。契约不应依赖Django 模型、DRF 序列化器、Request 对象输入输出形状一致时复用同一数据类。10.2 Facadeapi.py薄而稳定的公共接口Facade 的职责接受冻结数据类参数 → 调用业务逻辑logic.py→ 返回前把 Django 模型转为冻结数据类 → 必要时强制事务 → 保持薄且稳定。禁止实现业务逻辑、导入 DRF/HTTP/序列化器、暴露 Django 模型或返回 ORM 实例。class ArtifactAPI: staticmethod def create(params: CreateArtifact) - Artifact: instance logic.create_artifact(params) return _to_artifact(instance)真实实现可见 products/visual_review/backend/facade/api.py794 行包含 run metadata 消毒、GitHub 集成、各逻辑模块调用等其文件头明确写着This is the ONLY module other apps are allowed to import.为什么需要显式 mapperfacade 通过 mapper 函数把 ORM 模型转为冻结数据类看起来 1:1 复制很啰嗦但价值在于internal 变 external只有一个地方显式边界内部字段不会意外泄漏、转换点计算字段、展平关系、重命名、漂移吸收模型与暴露数据类分叉时由 mapper 吸收。直接返回 ORM 对象的替代方案能用直到不能用——届时只能在压力下补做隔离。10.3 隔离规则速览禁止直接导入其他产品的models.py/logic.py/视图/序列化器facade 返回 ORM 对象。允许导入其他产品的backend.facade使用 facade 返回的冻结数据类同产品内调用业务逻辑presentation 调用本产品 facade。正确与错误示例来自 products/architecture.md# 正确调用 facade拿到冻结数据类 from products.data_warehouse.backend.facade import DataWarehouseAPI tables DataWarehouseAPI.list_tables(team_idteam_id) # 错误直接导入其他产品的模型 from products.data_warehouse.backend.models.table import DataWarehouseTable tables DataWarehouseTable.objects.filter(team_idteam_id)10.4 两层边界工具tach 与 import-lintertach处理模块间边界什么能跨产品边界全局[[interfaces]]块在 tach.toml 中声明产品暴露的路径所有模块含 core 的posthog、ee都在单一modules层接口强制对一切生效import-linter配置在 pyproject.toml处理产品内架构presentation 层不得直接导入任何后端内部模块只能到达facade与本产品其他 presentation 模块routes.py只能导入presentation。任何新增内部模块cache、helpers 等都会被自动拦截。10.5 跨产品外键与 wiring couplings跨产品边界的 FK 会创建隐式反向依赖自动生成的 reverse accessor、reverse query name、迁移依赖、加载顺序依赖任何 import 检查器都看不见它。规则所有跨边界的关联字段FK、O2O、M2M声明related_name且不要设置显式related_query_name。需要反向访问时添加 facade 读函数而非遍历 ORM。仓库不变量会冻结每个跨边界 reverse accessor 为reverse-accessor(...)行见 products/model_crossing_uses_baseline.txt集合只许缩小。另外core 有时需要产品的行为而非数据查询运行器、Temporal 工作流、Max 工具、Celery 任务——这些以类形式跨界仅当同时满足三条规则经批准的接口core 拥有的基类如 posthog/hogql_queries/query_runner.py 的QueryRunner、ee/hogai/tool.py 的MaxTool、Temporal 的workflow.defn/activity.defn、Celery 的shared_task、指定位置backend/hogql_queries/、backend/max_tools.py、backend/temporal/、backend/tasks/、注册点运行时校验参考 ee/hogai/registry.py 的子类自动注册。Django 模型永远不跨界两个以模型类身份为键的注册表是明确特例。十一、Turbo 选择性测试产品使用 Turborepo 做选择性测试——只有受你改动影响的测试会运行# 运行所有产品测试 pnpm turbo run backend:test # 运行特定产品测试 pnpm turbo run backend:test --filterposthog/products-visual_review # 干跑查看将执行什么 pnpm turbo run backend:test --dry-runjson # 契约检查隔离产品专用 pnpm turbo run backend:contract-check11.1 契约输入 vs 实现输入契约输入backend:contract-check使用backend/facade/contracts.py、backend/facade/enums.py、wiring 位置backend/hogql_queries/、backend/max_tools.py、backend/temporal/、backend/tasks/。其他产品只依赖一个产品的契约文件实现输入backend:test使用所有backend/**/*.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)场景只改了visual_review/logic.pyvisual_review backend:test→ 重跑实现文件变了visual_review backend:contract-check→ 缓存命中契约文件没变other_product backend:test→ 跳过只依赖契约未变。11.2 什么让跳过是可靠的跳过完整套件意味着断言产品内部改动只会破坏产品自身测试。tach 证明了 import 的一半——没有外部代码能越过facade.*/presentation.views.*但它证明不了另一半产品的 HTTP API 由测试进程内驱动Django 测试客户端在同一进程分派到视图栈而非真实 socket权限、schema、activity-log 等横切测试按 URL 触达端点——零 import的耦合tach、lint-imports和任何 import 图审计都看不见。因此没有 importer只是必要条件该通道通过构造而非检查来关闭presentation 层保持薄只经 facade 触达内部使每个可观察行为要么在 facade边界内被测要么在序列化器形状OpenAPI schema 变化本就会强制全量套件行为测试保留在产品内模型面backend/models/与backend/migrations/保留在backend:contract-check输入中——模型可用apps.get_model(label, Class)无 import 触达模型或迁移变更总是重跑全量套件hogli product:lint会阻止把模型面移出收窄输入。视图仍持有业务逻辑的产品即使无人 import 也不可可靠跳过。这也是进程内无调用者所以不需要 facade是错误判据的原因。十二、总结PostHog 的products/架构把单体 Django 拆成一组自包含、可独立演进、可选择性测试的产品切片核心要素可归纳为Django-idiomatic 布局products/name/backend/是真实 Django appfacade/、presentation/、routes.py是固定命名的边界位置冻结数据类契约backend/facade/contracts.py是产品间唯一流通的数据结构无 Django/DRF 依赖、不可变、稳定薄 facadebackend/facade/api.py是唯一公共接口只做接参 → 调 logic → 转契约返回不实现业务逻辑、不导入 DRF、不返回 ORM 实例业务逻辑隔离backend/logic/或logic.py持有校验、计算、业务规则与 ORM 查询实现改动不影响其他产品的测试DRF presentation 与核心逻辑解耦presentation 只允许导入 facade 与本产品 presentation 模块import-linter 强制独立产品数据库db_routing.yaml声明即得独立库ProductTeamModel保证租户隔离熔断器保证故障被限制在产品自身Turbo 任务缓存 tach import 边界契约文件未变则下游不重测import 边界由 tach 的[[interfaces]]强制基线棘轮isolation_baseline.txt、model_crossing_uses_baseline.txt只许缩小新耦合必须手改文件并留注释进 diff——保证隔离不可逆、只减不增。这套架构以降低耦合、启用选择性测试、随规模增长保持开发速度为直接目标是 PostHog 从巨型 Django monolith走向可独立演进产品集合的落地路径。【免费下载链接】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),仅供参考