dry-rb 入门:把函数式编程带进 Ruby 应用
dry-rb 是一组模块化 Ruby 库,提供类型约束、数据校验、Struct 和函数式模式。本文讲解 dry-types、dry-struct、dry-validation 三大核心组件的用法与集成方式。
dry-rb 是一系列可独立使用的小型 gem 集合,核心解决三件事——用 dry-types 给数据加类型约束,用 dry-struct 定义不可变值对象,用 dry-validation 在系统边界做声明式校验。它不强迫你换框架,Rails 项目也可以只引入其中一两个组件。
dry-rb 解决什么问题
Ruby 的灵活是把双刃剑:哈希在层间传递,没人知道里面该有什么键;参数校验散落在控制器和模型里;类型错误要到运行时才暴露。dry-rb 的思路是把"数据的形状"显式声明出来,在边界处拦住脏数据,让内部代码只处理可信的结构。
dry-types:给 Ruby 加类型约束
require 'dry-types'
module Types
include Dry.Types()
end
Types::String # 字符串类型
Types::Integer.constrained(gt: 0) # 正整数
Types::Coercible::Integer # 会尝试强制转换的类型
Types::Coercible::Integer['42'] # => 42
Types::String[123] # => "123"
类型对象可以组合(Types::Array.of(Types::String))、加约束、设默认值,为整个 dry-rb 生态提供基础。
dry-struct:定义可靠的数据结构
class CreateUserInput < Dry::Struct
attribute :name, Types::String
attribute :email, Types::String
attribute :age, Types::Coercible::Integer.optional
end
input = CreateUserInput.new(name: 'Alice', email: 'a@b.com', age: '30')
input.age # => 30(自动强转)
input.frozen? # => true(默认不可变)
相比裸 Hash,Struct 的字段一目了然,错拼字段名会立刻报错而不是悄悄变成 nil——重构时这一差别就是生死之别。
dry-validation:声明式校验
class CreateUserContract < Dry::Validation::Contract
params do
required(:name).filled(:string)
required(:email).filled(:string)
required(:age).maybe(:integer, gt?: 0)
end
rule(:email) do
key.failure('邮箱格式不正确') unless value.include?('@')
end
end
result = CreateUserContract.new.call(params)
result.success? # => false
result.errors.to_h # => { email: ['邮箱格式不正确'] }
params 块先做强转(表单传过来的 "30" 会变成整数),rule 块再写业务规则,校验结果结构化输出,非常适合 API 参数与表单校验场景。
生态里还有什么
- dry-monads:Maybe、Result 等单子,用链式组合替代嵌套 if
- dry-container / dry-auto_inject:依赖注入容器
- dry-transaction:把业务流程编排成步骤链
- dry-system:应用级组件管理
每个 gem 都能单独引入,这也是 dry-rb 与"全家桶框架"最大的区别。
与 Rails 集成的现实建议
在 Rails 项目里渐进采用:先从 dry-validation 替代复杂表单对象的校验开始,再在 Service 层用 dry-struct 定义输入输出。不要试图一次性把 ActiveRecord 模型全部换掉——dry-rb 的价值在边界和领域层,ORM 该用还用。
常见问题(FAQ)
Q:dry-validation 和 ActiveModel 校验有什么区别?
ActiveModel 校验绑定在模型生命周期上,适合持久化前的检查;dry-validation 与存储无关,适合边界处(API 参数、消息队列载荷、文件导入)的输入校验,规则表达力也更强。两者可以共存,各管各的层。
Q:dry-rb 的性能如何?
Struct 和校验在热路径上有一定开销,但对 Web 请求量级完全够用。真正要警惕的是过度设计——给一个三字段的内部脚本也上 Contract 就得不偿失了。
Q:学 dry-rb 要先学 Haskell 吗?
不需要。dry-types/struct/validation 都不涉及函数式概念,开箱即用;只有 dry-monads 部分需要理解 Maybe/Result 的心智模型,官方文档的示例足够入门。
参考文档
本文依据公开文档整理,示例需按项目依赖版本验证;不将文档中的性能描述视为本项目实测结果。