前面講了很多 Kotlin Exposed 框架使用的方式。
今天來講點觀念性的東西,談談 Exposed 框架內 DAO 和 DSL 的差異。
如果讀者跟著這系列文章看到這邊,相信會很明顯看到,範例程式碼裡面基本上都是用 DAO 的方式建立資料。
這也是筆者個人比較偏好的存取方式,個人原因是比較習慣這樣的維護方式。
以比較具象徵性的多對多關係為例。假設資料表如下
object Users : IntIdTable() {
val name = varchar("name", 50)
}
object UserTag : IntIdTable() {
val user = reference("user", Users)
val tag = reference("tag", Tags)
}
object Tags : IntIdTable() {
val name = varchar("name", 50)
}
用 DAO 的存取方式是
class Tag(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Tag>(Tags)
var name by Tags.name
var users by User via UsersTags
}
class User(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<User>(Users)
var name by Users.name
var tags by Tag via UsersTags
}
宣告了這些關聯之後,框架會透過 via
去自動找到資料表之間的關聯性,並組合出需要的 Query。
如果用 DSL 的話,就得自己去構思裏面的 join 邏輯。
假設我們現在資料設置如下:
val carol = User.new {
name = "Carol"
}
val admin = Tag.new {
name = "admin"
}
carol.tags = SizedCollection(listOf(admin))
要從叫做 Carol
的 user
取出所有對應的 user.tags
用 DAO 的方式,並加上 StdOutSqlLogger
協助我們看到 Query 內容
addLogger(StdOutSqlLogger)
val carol = User.find { Users.name eq "Carol" }.first()
carol.tags.forEach{ println(it.name) }
會看到以下的結果
SQL: SELECT USERS.ID, USERS."NAME" FROM USERS WHERE USERS."NAME" = 'Carol'
SQL: SELECT TAGS.ID, TAGS."NAME", USERSTAGS.ID, USERSTAGS.TAG, USERSTAGS."USER" FROM TAGS INNER JOIN USERSTAGS ON TAGS.ID = USERSTAGS.TAG WHERE USERSTAGS."USER" = 1
admin
要用DSL 做出一樣的效果,你需要自己組合出這段 Query
addLogger(StdOutSqlLogger)
val carol = Users.select { Users.name eq "Carol" } .first()
val tags = (Tags innerJoin UsersTags).select{ UsersTags.user eq carol[Users.id]}
tags.forEach { println(it[Tags.name]) }
來得到以下的結果
SQL: SELECT USERS.ID, USERS."NAME" FROM USERS WHERE USERS."NAME" = 'Carol'
SQL: SELECT TAGS.ID, TAGS."NAME", USERSTAGS.ID, USERSTAGS."USER", USERSTAGS.TAG FROM TAGS INNER JOIN USERSTAGS ON TAGS.ID = USERSTAGS.TAG WHERE USERSTAGS."USER" = 1
admin
筆者的角度來看,顯然 DAO 的程式碼更加直觀,也更容易進行後續的開發與維護。不過,當資料關聯越來越複雜時,用 DAO 的方式進行存取,如果沒有對底層的 Query 語法有所認識,很容易會在撰寫時不小心誤用,導致出現了很複雜的存取語法而不自知。
相對來說,DSL 的存取方式,由於需要在撰寫時就理解底層的 Query 語法,相較之下比較不容易在毫無發覺的狀況下,建立出很複雜的存取語法。
另外,隨著專案開始複雜化,存取資料庫的邏輯越發複雜,甚至包含到一些 Stored Procedure 時,為了滿足某些 DAO 可能尚未支援的存取方式,要實現這些存取邏輯,DSL 也是不可或缺的。
總結來說,兩種資料庫的存取方式,隨著專案本身的大小以及屬性,隨著共同維護人員的經驗,甚至包含到資料庫目前的狀況,可能最適用的做法會有所不同。
以作為後端工程的我們來說,應該要想辦法根據實際情況,挑選最適合目前團隊與專案的做法,來讓程式碼變得更好維護。