iT邦幫忙

2021 iThome 鐵人賽

DAY 26
1
Modern Web

Kotlin 怎麼操作資料庫?談談 Kotlin Exposed 框架系列 第 26

[Day 26] review 一下我們的程式,談談 DSL 和 DAO 的差異

前面講了很多 Kotlin Exposed 框架使用的方式。

今天來講點觀念性的東西,談談 Exposed 框架內 DAO 和 DSL 的差異。

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))

要從叫做 Caroluser 取出所有對應的 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 也是不可或缺的。

總結來說,兩種資料庫的存取方式,隨著專案本身的大小以及屬性,隨著共同維護人員的經驗,甚至包含到資料庫目前的狀況,可能最適用的做法會有所不同。

以作為後端工程的我們來說,應該要想辦法根據實際情況,挑選最適合目前團隊與專案的做法,來讓程式碼變得更好維護。


上一篇
[Day 25] 如果我們不想 mock Clock 怎麼辦呢?談依賴反轉
下一篇
[Day 27] 沒有 connection pool 支援怎麼辦?談 HikariCP
系列文
Kotlin 怎麼操作資料庫?談談 Kotlin Exposed 框架30

尚未有邦友留言

立即登入留言