ผมสงสัยเรื่อง configuration ใน grails มาได้พักใหญ่แล้ว
เพราะเห็น syntax มันแปลกประหลาดเหลือเกิน ลองดูตัวอย่างข้างล่างนี้
log4j.appender.stdout = "org.apache.log4j.ConsoleAppender"
log4j.appender."stdout.layout"="org.apache.log4j.PatternLayout"
ทำไมบรรทัดที่สองต้องมี quote ครอบด้วยหล่ะ?
class ที่ทำหน้าที่อ่าน file Config.groovy ของ grails ก็คือ
ConfigSlurperโดยวิธีการใช้งานเจ้า ConfigSlurper ก็คือแบบนี้
def config = new ConfigSlurper().parse(new File('/tmp/x.groovy').toURL())
สิ่งที่เจ้า ConfigSlurper return มาก็คือ Map ตัวหนึ่ง (มันคือ class ConfigObject ที่ extends จาก LinkedHashMap)
ที่นี้มาลองดู nature ของมันบ้าง
เริ่มจาก config ง่ายๆ บรรทัดเดียว
topic1.subtopic1=1
ผลลัพท์ที่ได้จากการ parse จะเป็น map หน้าตาแบบนี้
["topic1":["subtopic1":1]]
ที่นี้ถ้าลองเพิ่ม sub node เข้าไปใต้ subtopic แบบนี้
topic1.subtopic1=1
topic1.subtopic1.subofsubtopic1="foo"
เมื่อลอง parse ดู แทนที่จะได้ผลลัพท์ เรากลับได้ exception แบบนี้แทน
groovy.lang.MissingPropertyException: No such property: subofsubtopic1 for class: java.lang.Integer
at script1226551599145.run(script1226551599145.groovy:2)
at Script9.run(Script9:1)
ดูเหมือนว่าการทำงานภายในของมันก็คือ เมื่อมันต้องการ assign ค่า
topic1.subtopic1.subofsubtopic1
มันจะไป get value โดยใช้ key
topic1.subtopic1
จากนั้นก็พยายามจะ set property
subofsubtopic1
ลงใน value ที่ได้มาก ซึ่งไม่สำเร็จแน่ๆเพราะ value ที่ได้มันไม่ใช่ map อย่างที่ควรจะเป็น
ที่นี้ลอง config แบบนี้บ้าง
topic1.subtopic1.subofsubtopic1="foo"
topic1.subtopic1=1
ผลลัพท์ที่ได้คือ
["topic1":["subtopic1":1]]
อ้าว แล้ว subofsubtopic1 ของเราหายไปไหนหล่ะ
กลายเป็นว่า subofsubtopic1 ของเราถูก เจ้า topic1.subtopic1 override ค่าไปเรียบร้อยแล้ว
ลองให้เหลือ บรรทัดเดียวแบบนี้ ดูบ้าง
topic1.subtopic1.subofsubtopic1="foo"
ผลลัพท์ที่ได้ จะเป็นแบบนี้
["topic1":["subtopic1":["subofsubtopic1":"foo"]]]
สรุปได้ว่า เนื่องจาก ConfigSlurperใช้ nested map เป็น internal structure ในการเก็บผลลัพท์จากการ parse
ทำให้เกิดข้อจำกัดในการเก็บขึ้นมา ดังนั้นถ้าเราต้องการเก็บค่าแบบบ้างบนให้ได้ เราก็เลยต้อง hack มัน โดยการห่อค่าให้เป็น string แบบนี้แทน
topic1.subtopic1=1
topic1."subtopic1.subofsubtopic1"="foo"
ผลลัพท์ที่ได้จากการ map ก็จะเป็นแบบนี้
["topic1":["subtopic1":1, "subtopic1.subofsubtopic1":"foo"]]
อืมม์ไม่สวยเป็นอย่างยิ่ง น่าจะมีคน refactor นะ
สรุปได้ว่า ถ้าเรานำไปใช้งาน configuration ของเรา (ถ้าใช้ grails ก็ต้องเจอแน่ๆ)
เราก็ควรจะออกแบบ tree ของเราดีๆ อย่าให้เกิดกรณีที่ parent node มีค่า value attach อยู่
(ให้มี valueได้เฉพาะ leaf node)
ไม่งั้นอาจจะปวดหัวว่า ทำไมค่า config ถึงหายไป หรือไม่ก็ ทำไมถึง get config ที่ต้องการไม่เจอ