2013年12月30日

使用 Grails 開發應用系統之感

ihower 的一篇 FAQ 開頭中提到 [Rails 發明人... : Rails Is Not For Beginners], 不知這自問自答式的內容是否也同意了 Rails 發明人的說法?! 但, 卻又像傳教士般的引導閱者進入 RoR 的世界之中?!

lyhcode 的 [程式設計師小心別被框架給「框」住了...] 一篇也提到 [... 如果一位「資深」的 Java 程式設計師,在過去 3-5 年間在專案中應用這些技術,可是不深入瞭解這些框架解決了甚麼問題、用什麼方法實作、底層如何運作以及如何擴充或調校,就很難有真正屬於自己的 Know-how。...], 文中意在說明"用過"(而非"適當運用")一些 framework 時存在的風險。

當個人近幾年發展的一些小案專使用 Grails 時, 也意識到了一點點以上的狀況: 需要溝通並傳達一些基礎與概念, 讓其他同仁放下排斥的意念並願意花時間來接受, 同時也須避免被它給"框"住了。

譬如: Grails 1.1.x 時代, GORM 並不支援 enum, 如果要使用 inList constraints 並活用在 GSP viewer 中, 確實要花一點技巧才能達到; 雖然在 v1.2.1 以後就支援了, 但當下專案驗收卻是不會等人的。

又如: legacy RDB 與 GORM 結合, 其實談的不是 ORM 而是 R-->O mapping; 因為資料庫是 DBA 在管的, 不是 AP 開發人員想怎樣就怎樣, 恣意使用了 object ID 而挷架了 DB。因此, adopt 欄位時定義了諸如下列的 domain class:
class Department {
    String id // 不使用內定的 long type
    String code
//    ...
    String description
 
    static transients = ['code']

//  ...

    static constraints = {
        id(maxSize: 3)
        code(nullable: false, blank: false, size: 2..3, unique: true)
//        ...
    }
 
    static mapping = {
        id generator: 'assigned'

        columns {
            id column: 'code' // code 即 id
//            ...
        }
    }

//    ...

// 以 code 代替 id
    void setCode(String code) {
        this.id = code.toUpperCase() // 轉 upper case
    }

    String getCode() {
        this.id
    }
}

但, 在到了 v2.3.x 之後卻不能正常運作了, 因為 code 欄位並不是真正的欄位 (尚不知是 Hibernate 版本的問題, 還是 Grails 版本的問題); 如果真的無法增加 id 欄位而只能修改的話, 應該會如下:
class Department {
// ...(略)...
 
    static constraints = {
        id nullable: false, unique: true, maxSize: 3
        code blank: false, size: 2..3
//        ...
    }

    static mapping = {
        id column: 'code', generator: 'assigned'
//        ...
    }

 
//    ...(略)...
 
    String getId() {
        this.id ?: '' // 防止 null value 造成 retrieving 問題
    }

    void setCode(String code) {
        setId(this.code = code?.toUpperCase())
    }
}
除了喪失了對 code 欄位進行 unique constraint 作用, 不料輸入相同重複的 code(即id) 之後, 即使在 controller 中加了 rejectValue() 控制:
def save(Department departmentInstance) {
//    ...

    try {
        departmentInstance.save flush:true
    } catch (e) {
        if (ExceptionUtils.getRootCause(e) instanceof NonUniqueObjectException) {
            departmentInstance.errors.rejectValue('code', 'default.not.unique.message',
                ['code', Department.class.name, departmentInstance.code] as Object[], '')
            respond departmentInstance.errors, view:'create'
        }
        return
    }
//    ...
}
仍無法阻止資料的存檔( create失敗 卻變成 update成功 )!
一時之間找不到解法, 腦袋中頓時出現 "框" 住的感覺。

不過, 正如過去長官的提點: 做事情要有中心思想。如果, 不該"重新造輪子"、"站在巨人的肩膀上"... 角度思考來使用 Grails, 理應快速開發客戶所需的應用系統。畢竟它背後有 SpringSouce(spring.io) 及眾多的 user group 在支撐。

沒有留言: