l

2011年8月23日 星期二

原來這就是 internal DSL

August 23 20:31~23:54

幾個禮拜前 Teddy 看了一點 DSL (Domain Specific Languages) 的書,後來有學弟問 Teddy 使用過 external DSL 還是 internal DSL,Teddy 答曰:『只用過 external DSL』。沒想到 好死不死 好巧不巧,最近 Teddy 把 Quartz 從 1.8.x 版升級到 2.0.x 版,居然在新版的 Quartz 就有用到 internal DSL 的設計,幸運地讓  Teddy 找到了一個活生生的 飯粒 範例。

看一小段從 What's New In Quartz Scheduler 2.0 節錄下來的文字:

The most obvious differences with version 2.0 are the significant changes to the API. These changes have aimed to:
modernize the API to use collections and generics, remove ambiguities and redundancies, hide/remove methods that should not be public to client code, improve separation of concerns, and introduce a Domain Specific Language (DSL) for working with the core entities (jobs and triggers).


講到這邊先稍微介紹一下 Quartz,這是一個用 Java 開發的開放原始碼工作排程系統(job scheduling system),這樣的系統有什麼用途呢?假設鄉民們所開發的軟體需要定期的執行許多工作,例如,
  • 每一,三,五的晚上三點半執行備份檔案工作。
  • 每天早上七點叫你起床順便泡一杯咖啡。 
  • 隨時(每 5 秒)幫你監看硬碟中的檔案是否有異動。
  • 每個月第一個禮拜四提醒你去繳信用卡卡費。
  • 每隔兩週的週四下午一點幫你發 sprint demo meeting 的開會通知。
以上種種狀況,只要是牽涉到『在某個特定時間點執行某項工具』就很適合用 Quartz 來幫忙處理工作排程。Quartz 的主要物件/介面有:
  • Scheduler:用來排程工作與觸發工作的物件。Scheduler 可以設定 thread pool size,用以決定同時間有多少個 thread 可以執行 job。
  • Job:要執行的工作邏輯定義在你自己的 Job 物件上面(要實做 Quartz 的 Job interface,如下圖所示)。基本上 Job interface 就類似 GoF 裡面的 Command pattern,只定義了一個 execute() method。

  • Trigger:用來紀錄『觸發時間』的物件,一個 trigger 物件必須要關聯到一個 job 才會發生作用,但是一個 job 卻可以有很多個 trigger。例如,你希望每天早上八點和下午八點自動執行資料備份的程式,那麼你就可以產生兩個 trigger 物件,把他們的 start time 分別設定為早上八點和下午八點,然後再將這兩個 triggers 與備份資料的 job 建立關聯並使用 Scheduler 來排程這兩個 triggers。如此一來 triggers 設定時間一到 Quartz 就會自動執行這個 job。請看以下範例(Quartz 的 CronTrigger 物件有支援使用 Cron expression 來設定觸發時間,會用之後還滿方便的)。


  • JobDetail:雖說 Quartz 『概念上』是將 trigger 關聯到一個 job 身上,但實際上在 Quartz 的實做中 trigger 是關聯到一個稱之為 JobDetail 的物件上。JobDetail 首先會關聯到某個 job,此外 JobDetail 還有一個 Map 物件,可以用來『夾帶』任何需要傳給 job 的資料,如下圖所示。

***

以上扯了這麼多,都沒還講到 Quartz 的 internal DSL。上面的程式碼是 Quartz 1.8.x 版的程式碼,鄉民們可以發現 Quartz 是採用 getter/setter 的方式來操作 JobDetail 與 Trigger 物件。到了 Quartz 2.0.x 版之後,Quartz 改用了 internal DSL 的方式來產生 JobDetail 與 Trigger。請看範例:

這是 Quartz 1.8.x 版的範例


這是 Quartz 2.0.x 版的範例

兩相比對之下就很容易看出不同之處。套用 Martin Fowler 在書中(Domain-Specific Languages,p. 16)的講法,Quartz 1.8.x 版使用的是 command-query API,而 Quartz 2.0.x 版則改用 fluent interface

***
雖然剛開始不習慣,但 Quartz 的 fluent interface 用久了還滿好用的說...時間不早,該睡了,明天 8:30 還要開會....


***

友藏內心獨白:今天有點拼...XD。  

5 則留言:

  1. 其實在書上也有個類似的例子,
    但我看到時有個疑惑,
    為了讓Java程式看起來像Internal DSL,
    讓method回傳自己或是某個物件,
    這算是一種talk to strangers的smell嗎?

    回覆刪除
  2. 這跟jquery的機制算是一樣的嬤? 看起來好像是這麼一回事

    回覆刪除
  3. jQuery不熟
    不過當物件的每個method都回傳自己時
    就可以這樣串XD

    回覆刪除
  4. 可是 ... 實在不知道這有什麼好處?

    回覆刪除
    回覆
    1. 之前Quartz用了三年多的時間,大部分的時間都是用傳統的 setXXX() 的方式來設定 Quartz 排程所需的物件。改用 DSL 之後,剛開始不太習慣,覺得有點卡卡的。但是習慣之後,覺得語法比原先的 setXXX() 方式容易讀、寫很多,

      刪除