Tidal Lock
"In orbital mechanics, tidal locking occurs when a body's orbital period matches its rotational period — it can only face one direction. In software, tidal locking occurs when two components can only change together. They've lost the ability to face any other direction."
What It Does
Every software architecture diagram shows clean boxes with clean arrows. Reality is messier. Over time, components develop gravitational relationships — shared assumptions, implicit contracts, synchronized change requirements — that make them unable to evolve independently.
Tidal Lock detects these gravitational bonds before they become permanent.
The Physics of Code Coupling
In astrophysics, tidal locking is caused by gravitational gradient forces. In codebases, it's caused by coupling gradient forces:
| Physical Force | Code Equivalent | Example |
|---|
| Gravitational pull | Shared data structures | Two services reading the same DB table |
| Tidal friction |
Synchronized deploys | Service A fails if Service B isn't deployed first |
| Orbital resonance | Matching change frequency | Every commit to module X requires a commit to module Y |
| Roche limit | Merger threat | Components so coupled they should just be one module |
| Lagrange points | Stable mediators | A third component that exists only to translate between two locked ones |
The Five Degrees of Lock
Degree 0: Independent Orbit
Components are genuinely independent. Changing one has zero effect on the other. This is the ideal for components that shouldn't be related.
Degree 1: Gravitational Awareness
Components know about each other through well-defined interfaces. Changes to internals are isolated. Changes to the interface require coordination. This is healthy coupling.
Degree 2: Orbital Resonance
Components change at correlated frequencies. Not every change to A requires a change to B, but many do. The interface between them has become too wide or too leaky.
CODEBLOCK0
Degree 3: Synchronous Rotation
Components MUST change together. Every modification to A requires a corresponding modification to B. They share data structures, timing assumptions, or implicit contracts that make independent evolution impossible.
CODEBLOCK1
Degree 4: Tidal Lock (Critical)
Components have lost their individual identity. They are one system pretending to be two. The boundary between them is a fiction maintained by the directory structure but violated by every data flow.
CODEBLOCK2
Degree 5: Roche Limit (Structural Failure)
The coupling is so severe that the components are actively tearing each other apart. Bugs in one appear as symptoms in the other. Changes cascade unpredictably. The architecture is in structural failure.
Detection Methodology
CODEBLOCK3
Decoupling Strategies by Force Type
| Coupling Force | Decoupling Strategy |
|---|
| Shared DB tables | Introduce owned views or dedicated read models per service |
| Shared data types |
Define contracts at the boundary, allow internal divergence |
|
Deploy ordering | Add graceful degradation and version negotiation |
|
Change correlation | Extract the shared concern into its own module |
|
Circular imports | Introduce an interface/protocol layer; invert one dependency |
|
Shared mutable state | Event-driven communication; each component owns its state |
|
Timing assumptions | Explicit synchronization; remove implicit ordering |
Output Format
CODEBLOCK4
When to Invoke
- - Before splitting a monolith into services (find out what's actually independent)
- After microservice adoption (verify you didn't build a distributed monolith)
- When deploys keep breaking unrelated services
- When "simple changes" take weeks because of cascading modifications
- During architecture reviews to assess modularity health
Why It Matters
The promise of modularity is independent evolution. When components become tidally locked, that promise is broken — you have the overhead of multiple components with the rigidity of a monolith. The worst of both worlds.
Tidal Lock shows you where independence has been surrendered, so you can decide: decouple them, or honestly merge them. Either is better than the fiction of modularity with the reality of lock.
Zero external dependencies. Zero API calls. Pure structural and historical analysis.
潮汐锁定
在天体力学中,当一个天体的轨道周期与其自转周期相匹配时,就会发生潮汐锁定——它只能朝向一个方向。在软件中,当两个组件只能一起变化时,就会发生潮汐锁定。它们已经失去了面向其他方向的能力。
功能概述
每个软件架构图都展示着干净的方框和清晰的箭头。现实却要混乱得多。随着时间的推移,组件之间会发展出引力关系——共享的假设、隐式契约、同步变更需求——使它们无法独立演进。
潮汐锁定能在这些引力束缚变得永久之前检测到它们。
代码耦合的物理学
在天体物理学中,潮汐锁定由引力梯度力引起。在代码库中,它由耦合梯度力引起:
| 物理力 | 代码等价物 | 示例 |
|---|
| 引力牵引 | 共享数据结构 | 两个服务读取同一个数据库表 |
| 潮汐摩擦 |
同步部署 | 如果服务B没有先部署,服务A就会失败 |
| 轨道共振 | 匹配的变更频率 | 每次对模块X的提交都需要对模块Y的提交 |
| 洛希极限 | 合并威胁 | 耦合度如此之高,以至于它们应该合并为一个模块 |
| 拉格朗日点 | 稳定中介 | 一个仅用于在两个锁定组件之间进行转换的第三方组件 |
锁定的五个等级
等级0:独立轨道
组件真正独立。更改一个对另一个完全没有影响。这是不相关组件的理想状态。
等级1:引力感知
组件通过定义良好的接口相互感知。内部变更相互隔离。接口变更需要协调。这是健康的耦合。
等级2:轨道共振
组件以相关频率变更。并非每次对A的变更都需要对B的变更,但很多情况下确实如此。它们之间的接口变得过于宽泛或过于泄漏。
症状:
├── 拉取请求经常同时涉及两个组件
├── 代码审查中出现别忘了更新另一边
├── 它们之间的集成测试比单元测试更容易失败
└── 部署顺序有时很重要
等级3:同步旋转
组件必须同时变更。对A的每次修改都需要对B进行相应的修改。它们共享数据结构、时序假设或隐式契约,使得独立演进变得不可能。
症状:
├── 不部署B就无法部署A
├── 更改A的内部会破坏B的测试
├── 共享的可变状态(数据库表、全局配置)
├── 手动同步复制粘贴的类型定义
└── 集成错误数量超过所有其他错误类型
等级4:潮汐锁定(严重)
组件已失去各自的独立性。它们是一个系统假装成两个。它们之间的边界是一个由目录结构维持但被每个数据流打破的虚构概念。
症状:
├── 循环依赖(A导入B,B导入A)
├── 共享内部状态(不仅仅是共享接口)
├── 不全面理解另一个就无法推理其中一个
├── 我们真的应该合并这些已被多次提及
└── 新开发者无法分辨一个在哪里结束,另一个在哪里开始
等级5:洛希极限(结构失效)
耦合如此严重,以至于组件正在相互撕裂。一个组件中的错误表现为另一个组件中的症状。变更不可预测地级联。架构处于结构失效状态。
检测方法
阶段1:引力调查
├── 映射所有显式依赖(导入、API调用、数据库访问)
├── 映射所有隐式依赖(共享配置、环境变量、时序)
├── 映射变更相关性(Git共同变更分析)
├── 映射部署依赖(X必须在Y之前/之后部署吗?)
└── 映射故障相关性(当X失败时,Y也会失败吗?)
阶段2:轨道分析
├── 对于每个组件对,计算:
│ ├── 变更耦合分数:它们一起变更的频率?
│ ├── 接口宽度:连接它们的表面积有多大?
│ ├── 数据引力:存在多少共享状态?
│ ├── 时序耦合:它们是否依赖于顺序?
│ └── 故障耦合:它们是否一起失败?
├── 综合分数 → 锁定等级(0-5)
└── 趋势分析:耦合度在增加还是减少?
阶段3:锁定制图
├── 生成整个系统的耦合图
├── 识别锁定集群(相互锁定的组件组)
├── 识别锁定链(A→B→C级联耦合)
├── 计算系统级耦合健康评分
└── 标记接近洛希极限的组件
阶段4:轨道力学报告
├── 对于每个锁定对,解释:
│ ├── 是什么力导致了锁定
│ ├── 锁定存在了多久(Git历史分析)
│ ├── 锁定是在增加还是稳定
│ └── 解耦策略(如果解耦是合理的)
├── 系统级耦合拓扑
└── 优先级排序:首先打破哪些锁定
按力类型划分的解耦策略
| 耦合力 | 解耦策略 |
|---|
| 共享数据库表 | 为每个服务引入拥有的视图或专用读取模型 |
| 共享数据类型 |
在边界定义契约,允许内部差异 |
|
部署顺序 | 添加优雅降级和版本协商 |
|
变更相关性 | 将共享关注点提取到自己的模块中 |
|
循环导入 | 引入接口/协议层;反转一个依赖 |
|
共享可变状态 | 事件驱动通信;每个组件拥有自己的状态 |
|
时序假设 | 显式同步;消除隐式顺序 |
输出格式
╔══════════════════════════════════════════════════════════════╗
║ 潮汐锁定分析 ║
║ 系统耦合健康度:64/100 ║
╠══════════════════════════════════════════════════════════════╣
║ ║
║ 锁定严重程度分布: ║
║ ├── 等级0(独立):12对 ████████████ ✓ ║
║ ├── 等级1(感知):8对 ████████ ✓ ║
║ ├── 等级2(共振):5对 █████ ⚠ ║
║ ├── 等级3(同步):2对 ██ ⚠⚠ ║
║ ├── 等级4(锁定):1对 █ 🔴 ║
║ └── 等级5(洛希):0对 ✓ ║
║ ║
║ 严重锁定: ║
║ ┌─────────────────────────────────────────────────────┐ ║
║ │ UserService ←→ BillingService │ ║
║ │ 等级:4(潮汐锁定) │ ║
║ │ 力:共享数据库(users表),循环导入 │ ║
║ │ 持续时间:14个月(正在增加) │ ║
║ │ 共同变更率:89%的提交同时触及两者 │ ║
║ │ 策略:提取UserProfile作为共享契约; │ ║
║ │ 给Billing自己的客户表 │ ║
║ └─────────────────────────────────────────────────────┘ ║
║ ║
║ 锁定链: ║
║ Auth → Users → Billing → Invoicing(4组件链) ║
║ 对Auth的任何变更都会级联到3个下游服务 ║
║ ║
║ 趋势:系统耦合度在上个季度增加了12% ║
╚══════════════════════════════════════════════════════════════╝
何时调用
- - 在将单体拆分为服务之前(找出哪些是真正独立的)
- 在采用微服务之后(验证你没有构建分布式单体)
- 当部署不断破坏不相关的服务时
- 当简单的变更因为级联修改而需要数周时间时
- 在架构审查期间评估模块化健康状况
为何重要
模块化的承诺是独立演进。当组件被潮汐锁定时,这个承诺就被打破了——你拥有了多个组件的开销和单体的僵化。两全其恶。
潮汐锁定向你展示了独立性在何处被放弃,这样你就可以决定:解耦它们,或者诚实地合并它们。任何一种选择都比模块化的虚构加上锁定的现实要好。
零外部依赖。零API调用。纯粹的结构和历史分析。