【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

使用 Ruby DSL 实现敏捷素材管理

  • 2007-09-24
  • 本文字数:4048 字

    阅读完需:约 13 分钟

领域特定语言(Domain Specific Language,简称 DSL)是一个面向语言的工具,用于解决某个特定领域的编程任务。DSL 的一般语言特征和它所被用到的问题领域,关系是非常密切的,并且在一个非常高的抽象层面上起作用。Martin Fowler 在他介绍 DSL 的文章中,将 DSL 划分为 _ 外部 DSL_ 和 _ 内部 DS_L 两类(参见原文链接)。外部DSL 是一门需要编译或者解释运行的编程语言,而内部DSL 则构建于一门通用编程语言(general-purpose programming language)之内。实际上,内部DSL 对于其宿主通用编程语言来说,就是它的一套层次非常高的API。本文讲述了了在PLANET ARGON 公司的一个开发项目中,用Ruby 实现的一个内部DSL 如何给项目带来巨大贡献。

问题的提出

我在PLANET ARGON 公司近期的一个开发项目的目标是,使用Ruby on Rails 构建一个一次性的内容管理系统,这个系统要支持18 种语言,并且管理大约1000 个的图像文件。这些文件中有许多都是专业摄影图片,每个文件大小都超过1MB。而另外的图片文件则是一些细碎的线条艺术,图片里面显示的就是许多不同的国旗。这些图片所具备的相同特点,就是它们都还不能直接投入产品使用。

我们的应用程序中包含一个 <strong>Image</strong>模型,该模型使用我们内部编写的一个文件上传插件来持久化图片数据。我们已经使用了<strong>after_save</strong>钩子来管理图形转换操作,而这些钩子在我们的模型内部使用了<strong>RMagic</strong>图形操作 API。这样看来应该就够了,但实际上我们的需求一直在不断被调整。有些图片需要进行四次变换才能投入使用;而其它图片只需要两次;其中一些图片必须被上传到一个第三方的贷款提供商那里;而又有些时候,我们把图片的尺寸弄错了。我们曾经一度编写了一个批处理脚本来上传这些原始图像,接着<strong>after_save</strong>钩子就可以重新处理这些图像了。便捷情况很快就凸现了出来,于是我们回调代码中就七荤八素地塞满条件判断语句。你可能已经猜到了,这个麻烦很快就成了往事。我们需要一个可以帮助我们以一种可持续发展的方式达到客户需求的工具。

解决方案

有一天,我们的客户又向我们的 Basecamp 发过来一个参考图片,以及他们如何在 PhotoShop 中创建这个图片的一系列指示。我抓狂了。我开始考虑使用一个更加优雅的方案来解决这个问题,我想起了 Jason Watkins 曾经跟我说起过,当他在电子游戏这一行工作的时候,他用过一类称为“素材编译器(asset compiler)”的工具。素材编译器其实是一个构建系统,将一系列媒体文件转换成用于电子游戏最终构建版用到的媒体文件。最近我用 Rake 自动化了不少重复性的任务(Rake 是一个用纯 Ruby 来实现的构建系统),所以用 Rake 搞出一套素材编译器,对我来说很有吸引力。如果创建产品图像的职责可以委派给一个外部的工具,那么不管我们客户的需求在什么时候发生改变,我们的应用程序都无须进行变更。更有意思的是,这个工具可以使得我们在需求发生变化的时候,使用一条命令就 _ 重新构建 _ 出所有的产品级图像。

拿着 Jim Weirich(Rake 的作者)的 RDoc 任务库(task library),我开始上手干活了。RDoc 任务库以模板的形式随 Rake 一起被分发。Jim 为 Rake 创建的用法模式之一就是一个实例化的类,里面带着描述了需用于自动化构建的 Rake 任务集合的合适选项。比如说,这里就给出了一个范例,展示如何在<strong>Rakefile</strong>使用 RDoc 任务库为 Rails 应用及其使用的插件,还有整个 Rails 框架构建文档。

Rake::RDocTask.new('my_docs') do |rdoc|<br></br> # configuration<br></br> rdoc.rdoc_dir = 'doc/my_docs'<br></br> rdoc.main = "doc/README_FOR_APP"<br></br> rdoc.title = 'Comprehensive Documentation'<p> # app documentation</p><br></br> rdoc.rdoc_files.include('doc/README_FOR_APP')<br></br> rdoc.rdoc_files.include('app/**/*.rb')<br></br> rdoc.rdoc_files.include('lib/**/*.rb')<p> # plugins documentation</p><br></br> rdoc.rdoc_files.include('vendor/plugins/*/lib/**/*.rb')<p> # framework documentation</p><br></br> # ...snip...<br></br> end按照这个范例在你的Rakefile中实例化一个<strong>Rake::RDocTask</strong>对象将会建立一系列 Rake 任务,用于创建、删除及重新构建你的文档。被<strong>Rake::RDocTask.new</strong>使用<strong>rdoc</strong>这个 block 参数,实际上就是正被实例化的<strong>Rake::RDocTask</strong>对象,而正被初始化的选项以及<strong>rdoc</strong>的属性则是用<strong>attr_accessors</strong>定义在<strong>Rake::RDocTask</strong>中的。

我开始将构建产品级图像的代码从我们的Image模型中抽取出来,放进一个 Rake 任务库中。在建立了一个操作起来类似于 RDoc 任务库的可工作任务库之后,我捣鼓出来的代码就像下面这样:

ImageTask.new :bronze_thumbnail do |t|<br></br> t.src_files = image_src 'images/*.jpg'<br></br> t.build_path = build 'greyscale_thumbnail'<br></br> t.remote_dirs << REMOTE_DIR[:greyscale_thumbnail]<br></br> t.transformation do |img|<br></br> # apply bronze image effect<br></br> img = img.quantize 256, Magick::GRAYColorspace<br></br> img = img.colorize 0.25, 0.25, 0.25, '#706000'<br></br> # crop to thumbnail size<br></br> ...snip...<br></br> end<br></br> end上面的代码定义了以下 Rake 任务,用<strong>rake -T</strong>可以得到以下描述:

rake assets:bronze_thumbnail:build # Build the bronze_thumbnail files<br></br> rake assets:bronze_thumbnail:clobber # Remove bronze_thumbnail files<br></br> rake assets:bronze_thumbnail:rebuild # Force a rebuild of the bronze_thumbnail files<br></br> rake assets:build # Build all assets<br></br> rake assets:clobber # Clobber all assets<br></br> rake assets:rebuild # Rebuild all assets我意识到,如果描述这些图像变换的代码与客户给我们的 PhotoShop 指令非常相似的话,这个工具将变得极具灵活性——代码就成了为图形变化定义 Rake 任务的内部 DSL 了。

我为我的任务库创建了一些单元测试,以保证任务可以被正确地创建出来,并且以测试驱动重构迈开第一步。由于我创建的是一个内部 DSL,它在我这个例子里是一个 Ruby API,这套 DSL 的创建完全可以遵循测试驱动的实践。我开始将我单元测试中初始化任务库各个类的范例代码替换成我尚未实现的 DSL 的范例代码,并且修改了数次,直到我满意为止。

define_image_transformation 'thumbnailize' do<br></br> crop_to '62x62', :north<br></br> end<p> define_image_transformation 'bronze' do</p><br></br> greyscale<br></br> lighten<br></br> # r g b tint<br></br> tint 0.25, 0.25, 0.25, '#706000'<br></br> end<p> image_task 'bronze_thumbnail' do</p><br></br> from images 'images/*.jpg'<br></br> to build 'greyscale_thumbnail'<br></br> remote_dirs << REMOTE_DIR[:greyscale_thumbnail]<br></br> transformation do<br></br> bronze<br></br> thumbnailize<br></br> end<br></br> end我开始运行我的单元测试,然后看见一连串错误消息跳将出来。这是件好事——现在我知道了我要的是什么,还有我需要哪些步骤才能实现我的目标。接着,我重命名了几个方法(比如说,把<strong>src</strong>改成了<strong>from</strong>,然后创建了一些辅助方法(helper methods)来辅助初始化任务库的类。此外,我还将一个低层的 RMagick 代码包装进一个类库之中,类库使用了对设计师友好的方法命名。随后我创建了一个<strong>define_image_transformation</strong>,该方法通过聚合那些 RMagick 的包装方法,定义了针对应用程序的高级图形变换。通过把类似于 Photoshop 的图形工具可以做到的事情和我们的客户需要我们做到的事情区别对待,我就可以保证我的工具能被抽取出来,并且重用在今后的项目之中——高层的图形变换代码是存放在我应用程序中<strong>lib/tasks</strong>目录的,而低层的包装器代码则存放在<strong>vendor/asset_compiler</strong>目录之下,和任务库分开存放。

成果

目前,我们的<strong>lib/tasks/assets.rake</strong>文件包含对<strong>image_task</strong>的 12 个调用,包括上面展示的实例。尽管对于外行程序员(lay programmers)来说,这门语言不一定合适(Fowler 在文章中说,外行程序员就是非专业的程序员,但他们可以使用简单的编程工具来自动化业务任务),但是它和我们客户的语言是同步的,而且优雅地将他们的想法和目标建模出来。

因为 Ruby DSL 就是 Ruby 的代码,所以我强烈建议要遵循我在这篇文章中描述的测试驱动过程:编写你希望使用的 Ruby 内部 DSL 的一个范例代码,然后编写为这个范例的期望结果编写测试用例,接下来还是编写代码,直到所有的测试都能通过为止。此外,在一开始时就要考虑你 DSL 的范围。由于我强调了图像转换功能和我们应用程序的需求之间的分离,在我们的 DSL 基础上构建出一个强大、可重用而又可扩展的框架就是一件自然而然的事情了。我认为这是一个正确的决定,因为我知道很快我又将把这套类库用于其它项目。如果你的需求更加特别,那么你就不需要多层的抽象了。如果你打算重用你的内部 DSL,那么请确定没有任何针对应用程序的业务逻辑掺杂其中,而是提供一个机制来俘获业务逻辑。

最后,告诉你应当如何使用哪种语言创建出一门内部 DSL 的圣经,是不存在的。在这个主题上我所读到的所有文章中,绝大多数都认为你必须边做边学。留意观察客户和领域专家如何沟通他们的需求,并且专注于使用可执行的代码来捕捉这些需求。即便你无法将你的领域专家转变成一个外行程序员,他们应当可以在一次代码评审或者一个结对编程过程中明白清楚地看到他们的需求,而你的目标则应当是,在你得到他们需求的同时就马上捕捉下来。

查看英文原文: Agile Asset Management with Ruby DSLs

2007-09-24 23:301868
用户头像

发布了 117 篇内容, 共 14.9 次阅读, 收获喜欢 0 次。

关注

评论

发布
暂无评论
发现更多内容

【云管平台】三大云管平台厂商详细介绍

行云管家

云计算 云管平台 云堡垒机 云厂商

轻量级兼顾本地体验,PWA应用到底有多卷?

鼎道智联

MySQL事务并发带来的问题以及其解决方案分析

乌龟哥哥

4月月更

LSM-Tree - LevelDb了解和实现

懒时小窝

LSM树 LSM-Tree

桌面运维工程师需要会哪些技能?主要是做什么的?

行云管家

云计算 运维 运维工程师 IT运维 云运维

项目中如何优雅的消除if-else

Rubble

4月日更 4月月更

当 API 成为服务,我们将连接一切!

鼎道智联

鲸智WhaleBI 平民化数据消费还业务以真正的“数据自由”

鲸品堂

方法论 数据 数据治理

java高级用法之:JNA中的Structure

程序那些事

Java 程序那些事 JNA 4月月更

华为云物联网高级攻城狮的4年配置中心实践分享

华为云开发者联盟

开源 DevOps 配置中心 Apollo 业务配置

什么时候需要使用CRM系统?

低代码小观

CRM 客户关系管理 低代码开发 CRM系统 客户关系管理系统

TiDB 在连锁快餐企业丨海量交易与实时分析的应用探索

TiDB 社区干货传送门

一起来试玩!在线可编程交互的实时音视频 Web SDK 入门教程

声网

音视频 教程

CrashSight异常崩溃管理解决方案

WeTest

人人都可以学会的产品手册制作方法

小炮

产品宣传手册

GPU底层技术、全球市场格局分析(中)

Finovy Cloud

人工智能 云计算 云服务器 GPU服务器 GPU算力

关于 WordPress 你了解多少?

海拥(haiyong.site)

4月月更

基于LAXCUS开发分布式视频转码应用

LAXCUS分布式操作系统

分布式计算 分布式应用 视频转码

组合式应用新利器?SaaS新时代事件网格如何解决集成标准化问题

华为云开发者联盟

Serverless 无服务器 事件网格 组合式应用

火山引擎 MARS X 今日头条 | 大型App高效协同开发,实现研发流程自动化

字节跳动终端技术

今日头条 字节跳动 研发 火山引擎MARS

龙蜥大讲堂:如何利用硬件SIMD指令提升Java程序的性能?|第14期

OpenAnolis小助手

Java simd arm sig 龙蜥大讲堂

DDD实战(8):冲刺1战术之聚合设计

深清秋

DDD 软件架构 生鲜电商系统 4月月更

小程序运行时+SAAS级服务,提升研发降本增效作用

Speedoooo

flutter APP开发 SaaS平台 小程序容器 小程序运行时

大咖说·智篆商业|颜杰华:数字经济时代,不确定性中的确定性

大咖说

阿里巴巴 数字经济数字时代 数智化 存量时代

TASKCTL 变量的函数表达式运算

TASKCTL

批量任务 调度引擎 ETL 自动化运维 调度任务

2022年中国茶饮产业洞察

易观分析

茶饮 产业洞察

Flink整合ElasticSearch详细指南及踩坑记录

五分钟学大数据

4月月更

NFT数字藏品交易平台系统开发搭建

薇電13242772558

NFT 数字藏品

TASKCTL 调度设计器作业属性的应用

TASKCTL

分布式 元数据区 ETL 自动化运维 任务调度器

增长270%!PWA 在 Chrome 平台迎来井喷式增长

鼎道智联

《数字经济全景白皮书》Z世代用户洞察篇(2)重磅发布!

易观分析

Z世代

使用Ruby DSL实现敏捷素材管理_Ruby_Jeremy Voorhis_InfoQ精选文章