关于 layered-rails
layered-rails 是面向应用已超出简单模式的 Rails 开发者的指导技能。它说明四层架构、代码职责分配和抽取信号,同时建议较小应用保持简洁。
主要功能
- 01Rails 四层架构与依赖规则
- 02代码分层归属指导
- 03代码抽取信号与回调评分
- 04模式选择及相关技能索引
- 05模型组织与复杂度指导
适用场景
- 01判断新增 Rails 代码应放在哪一层
- 02识别分层违规和重构目标
- 03评估回调并抽取副作用
- 04为复杂 Rails 领域增加结构
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ majesticlabs-dev/ majestic-marketplace --skill layered-rails安装后在 Claude Code 中输入/layered-rails确认已加载。
附带文件references/extraction-signals.mdreferences/pattern-catalog.md
详细介绍
分层 Rails 架构
受众: 在应用已不适合单文件模式后继续开发的 Rails 开发者。 目标: 了解代码应属于哪个层、何时进行提取,以及由哪个现有技能负责实现。
四层架构
Presentation → Application → Domain → Infrastructure
(HTTP/UI) (Orchestration) (Business) (Persistence/APIs)
核心规则: 低层绝不能依赖高层。数据只能自上而下流动。
各层职责
| 层 | 负责 | 不负责 |
|---|---|---|
| 表现层 | HTTP 相关逻辑、参数、渲染、通道、邮件发送器 | 业务逻辑、直接查询数据库 |
| 应用层 | 跨模型编排、授权、表单验证 | 持久化细节、渲染 |
| 领域层 | 业务规则、验证、关联、值对象 | HTTP 上下文、请求对象、Current.* |
| 基础设施层 | ActiveRecord、外部 API、文件存储、缓存 | 业务规则、表现层 |
常见的分层违规
| 违规 | 错误原因 | 修复方法 |
|---|---|---|
模型中使用 Current.user | 领域层依赖表现层上下文 | 将用户作为显式参数传递 |
服务中使用 request 参数 | 应用层依赖表现层 | 调用服务前提取所需值 |
| 控制器中计算定价 | 业务逻辑位于表现层 | 移至模型方法或服务 |
| 所有逻辑都放在服务中,模型贫血 | 领域层形同虚设 | 将领域逻辑保留在模型中;服务负责编排 |
| 模型直接发送邮件 | 领域层依赖基础设施副作用 | 仅将回调用于数据转换;将发送操作提取出去 |
规格测试
用于诊断代码放置不当:
- 列出该对象处理的每项职责
- 对每项职责询问:“这是否属于该层的主要关注点?”
- 如果不是 → 提取到适当的层
示例: 一个负责处理身份验证、头像处理、通知偏好设置和活动日志记录的 User 模型。
- 身份验证 → 领域层(保留)
- 头像处理 → 基础设施层(提取到服务/作业)
- 通知偏好设置 → 领域层(作为关注点保留)
- 活动日志记录 → 基础设施层(提取到观察器/事件)
完整方法请参阅 references/extraction-signals.md。
回调评分
为每个回调打 1–5 分。将得分为 1–2 分的回调提取出去。
| 得分 | 类型 | 示例 | 操作 |
|---|---|---|---|
| 5 | 转换器 | before_validation :normalize_email | 保留 |
| 4 | 规范化器 | before_save :strip_whitespace | 保留 |
| 4 | 实用工具 | after_create :update_counter_cache | 保留 |
| 2 | 观察器 | after_save :notify_admin | 考虑提取 |
| 1 | 操作 | after_create :send_welcome_email, :provision_account | 提取 |
经验法则: 如果移除回调会破坏模型自身的数据完整性 → 保留。如果它会触发外部副作用 → 提取。
模式选择
“这段代码应该放在哪里?”
| 情况 | 模式 | 层 | 技能 |
|---|---|---|---|
| 复杂的多模型表单 | 表单对象 | 表现层 | — |
| 请求参数筛选 | 筛选器对象 | 表现层 | — |
| 视图专属格式化 | Presenter / ViewComponent | 表现层 | viewcomponent-coder |
| 授权规则 | 策略对象 | 应用层 | action-policy-coder |
| 业务操作(一次性) | 服务 / Interaction | 应用层 | active-interaction-coder |
| 多模型编排 | 服务对象 | 应用层 | active-interaction-coder |
| 状态生命周期管理 | 状态机 | 领域层 | aasm-coder |
| 复杂的可复用查询 | 查询对象 | 领域层 | — |
| 不可变概念(Money、DateRange) | 值对象 | 领域层 | — |
| 共享的模型行为 | Concern | 领域层 | — |
| 类型化配置 | 配置对象 | 基础设施层 | anyway-config-coder |
| 领域事件 / 审计跟踪 | 事件溯源 | 基础设施层 | event-sourcing-coder |
| JSON 支持的属性 | Store Model | 领域层 | store-model-coder |
决策树
Is it about HTTP/params/rendering?
YES → Presentation layer
Multi-model form? → Form Object
Filtering params? → Filter Object
Formatting for view? → Presenter or ViewComponent
NO ↓
Is it authorization?
YES → Policy Object (action-policy-coder)
NO ↓
Does it orchestrate multiple models/services?
YES → Application layer
One-time operation? → Service/Interaction (active-interaction-coder)
Needs typed inputs? → ActiveInteraction (active-interaction-coder)
NO ↓
Is it a business rule about a single model?
YES → Domain layer (keep in model or concern)
Has state transitions? → AASM (aasm-coder)
Reusable query? → Query Object
Immutable value? → Value Object
NO ↓
Is it about persistence/external APIs/caching?
YES → Infrastructure layer
将服务视为候车室
app/services/ 是一个临时中转区,不是永久归属地。
- 留存下来的服务最终应体现出它们所代表的真正抽象
- 如果服务只封装单个模型操作 → 它可能应该放在模型中
- 如果服务协调 3 个或更多模型 → 它是合理的编排器
- 如果服务变得复杂 → 查找其中隐藏的表单对象、策略或查询对象
异味测试: 如果 app/services/ 中有 50 个或更多文件,却没有子目录,那么候车室已经变成了永久存储区。
提取信号
何时从现有位置提取代码:
| 信号 | 阈值 | 操作 |
|---|---|---|
| 方法长度 | > 15 行 | 提取方法或对象 |
| 模型中的外部 API 调用 | 任何情况 | 提取到服务/网关 |
| 上帝对象 | 高变更频率 × 高复杂度 | 拆分(参见 references/extraction-signals.md) |
| 规范超出层的职责范围 | 规范测试失败 | 提取到合适的层 |
| 回调评分 | 1-2/5 | 提取到服务或事件处理程序 |
| 重复的查询逻辑 | 2 个或更多位置 | 提取查询对象 |
模型中的 Current.* | 任何使用情况 | 将其作为显式参数传递 |
完整方法请参见 references/extraction-signals.md。
模型组织
建议的模型文件内部排序:
class Order < ApplicationRecord
# 1. Extensions/DSL (has_secure_password, acts_as_*)
# 2. Associations
# 3. Enums
# 4. Normalizations
# 5. Validations
# 6. Scopes
# 7. Callbacks (transformers/normalizers only — score 4-5)
# 8. Delegations
# 9. Public methods
# 10. Private methods
end
何时采用分层风格,何时采用 DHH 风格
此技能是对 dhh-coder 的补充,而非替代。
| 情况 | 使用 |
|---|---|
| 小型/中型应用、标准 CRUD | dhh-coder — 保持简单 |
| 复杂领域、多个限界上下文 | layered-rails — 增加结构 |
| 超出简单检查的授权 | 通过分层指南使用 action-policy-coder |
| 超过 500 行的臃肿模型 | layered-rails 提取信号 |
| 标准控制器操作 | dhh-coder — 7 个 REST 操作 |
| 多步骤业务操作 | 通过分层指南使用 active-interaction-coder |
默认选择简单方案。 仅在复杂度需要时采用分层模式。
成功检查清单
- 没有反向依赖(低层不引用高层)
- 模型不访问
Current属性 - 服务不接受请求/控制器对象
- 控制器仅包含 HTTP 相关逻辑
- 领域逻辑位于模型中,不外泄到服务中
- 所有回调评分均为 4 分或以上(或已提取)
- Concern 按行为分组,而非按产物类型分组
- 每个抽象恰好属于一个层
Cross-References
| Need | Skill |
|---|---|
| Authorization policies | action-policy-coder |
| Typed business operations | active-interaction-coder |
| State machines | aasm-coder |
| Operations + state routing | business-logic-coder |
| ViewComponents | viewcomponent-coder |
| Typed configuration | anyway-config-coder |
| Event sourcing / audit | event-sourcing-coder |
| JSON attributes | store-model-coder |
| Refactoring execution | rails-refactorer |
| DHH-style simplicity | dhh-coder |
| Pattern catalog details | references/pattern-catalog.md |
| Extraction methodology | references/extraction-signals.md |
评论
支持 Markdown