Metode transaksional di Scala Play dengan Slick (mirip dengan Spring @Transactional, mungkin?)

Saya tahu scala, sebagai bahasa fungsional, seharusnya bekerja secara berbeda dari bahasa OO umum, seperti Java, tapi saya yakin harus ada cara untuk menggabungkan sekelompok perubahan database dalam satu transaksi, memastikan atomisitas sebagai serta setiap properti ACID lainnya.

Seperti yang dijelaskan dalam dokumen apik (http://slick.lightbend.com/doc/3.1.0/dbio.html), DBIOAction memungkinkan pengelompokan operasi db dalam transaksi seperti ini:

val a = (for {
  ns <- coffees.filter(_.name.startsWith("ESPRESSO")).map(_.name).result
  _ <- DBIO.seq(ns.map(n => coffees.filter(_.name === n).delete): _*)
} yield ()).transactionally

val f: Future[Unit] = db.run(a)

Namun, kasus penggunaan saya (dan sebagian besar contoh dunia nyata yang dapat saya pikirkan), saya memiliki struktur kode dengan Pengontrol, yang memaparkan kode untuk titik akhir REST saya, pengontrol tersebut memanggil beberapa layanan dan setiap layanan akan mendelegasikan operasi basis data ke DAO.

Contoh kasar dari struktur kode saya yang biasa:

class UserController @Inject(userService: UserService) {
  def register(userData: UserData) = {
    userService.save(userData).map(result => Ok(result))
  }
}

class UserService @Inject(userDao: UserDao, addressDao: AddressDao) {
  def save(userData: UserData) = {
    for {
      savedUser <- userDao.save(userData.toUser)
      savedAddress <- addressDao.save(userData.addressData.toAddress)
    } yield savedUser.copy(address = savedAddress)
  }
}

class SlickUserDao {
  def save(user: User) = {
    db.run((UserSchema.users returning UserSchema.users)).insertOrUpdate(user)
  }
}

Ini adalah contoh sederhana, namun sebagian besar memiliki logika bisnis yang lebih kompleks di lapisan layanan.

Saya tidak ingin:

  1. DAO saya memiliki logika bisnis dan memutuskan operasi database mana yang akan dijalankan.
  2. Kembalikan DBAction dari DAO saya dan ekspos kelas persistensi. Hal ini sepenuhnya menggagalkan tujuan penggunaan DAO dan membuat pemfaktoran ulang lebih lanjut menjadi jauh lebih sulit.

Tapi saya pasti ingin transaksi di seluruh Pengontrol saya, untuk memastikan bahwa jika ada kode yang gagal, semua perubahan yang dilakukan dalam eksekusi metode itu akan dibatalkan.

Bagaimana saya bisa mengimplementasikan transaksionalitas pengontrol penuh dengan Slick di aplikasi Scala Play? Sepertinya saya tidak dapat menemukan dokumentasi apa pun tentang cara melakukan itu.

Juga, bagaimana saya bisa menonaktifkan komit otomatis di slick? Saya yakin ada jalan dan saya hanya melewatkan sesuatu.

Sunting:

Jadi membaca lebih banyak tentang hal itu, saya merasa sekarang saya lebih memahami bagaimana apik menggunakan koneksi ke database dan sesi. Ini sangat membantu: http://tastefulcode.com/2015/03/19/modern-database-access-scala-slick/.

Apa yang saya lakukan adalah menulis di masa depan dan, berdasarkan artikel ini, tidak ada cara untuk menggunakan koneksi dan sesi yang sama untuk beberapa operasi semacam itu.

Masalahnya adalah: Saya benar-benar tidak bisa menggunakan komposisi lain. Saya memiliki banyak logika bisnis yang perlu dieksekusi di antara pertanyaan.

Saya kira saya dapat mengubah kode saya untuk memungkinkan saya menggunakan komposisi tindakan, tetapi seperti yang saya sebutkan sebelumnya, hal itu memaksa saya untuk mengkodekan logika bisnis saya dengan mempertimbangkan aspek-aspek seperti transaksionalitas. Itu seharusnya tidak terjadi. Ini mencemari kode bisnis dan membuat tes menulis menjadi lebih sulit.

Adakah solusi untuk masalah ini? Adakah proyek git di luar sana yang menyelesaikan masalah ini yang saya lewatkan? Atau, yang lebih drastis, adakah kerangka persistensi lain yang mendukung hal ini? Dari apa yang saya baca, Anorm mendukung ini dengan baik, tetapi saya mungkin salah memahaminya dan tidak ingin mengubah kerangka kerja untuk mengetahui bahwa ternyata tidak (seperti yang terjadi dengan Slick).


person redwulf    schedule 06.07.2016    source sumber


Jawaban (1)


Tidak ada yang namanya anotasi transaksional atau sejenisnya di slick. Kata "tidak mau" kedua Anda sebenarnya adalah cara yang harus dilakukan. Sangat masuk akal untuk mengembalikan DBIO[User] dari DAO Anda yang tidak menggagalkan tujuannya sama sekali. Begitulah cara kerja yang apik.

class UserController @Inject(userService: UserService) {
  def register(userData: UserData) = {
    userService.save(userData).map(result => Ok(result))
  }
}

class UserService @Inject(userDao: UserDao, addressDao: AddressDao) {
  def save(userData: UserData): Future[User] = {
    val action = (for {
      savedUser <- userDao.save(userData.toUser)
      savedAddress <- addressDao.save(userData.addressData.toAddress)
      whatever <- DBIO.successful(nonDbStuff)
    } yield (savedUser, savedAddress)).transactionally

    db.run(action).map(result => result._1.copy(result._2))
  }
}

class SlickUserDao {
  def save(user: User): DBIO[User] = {
    (UserSchema.users returning UserSchema.users).insertOrUpdate(user)
  }
}
  • Tanda tangan save di kelas layanan Anda masih sama.
  • Tidak ada hal terkait db di pengontrol.
  • Anda memiliki kendali penuh atas transaksi.
  • Saya tidak dapat menemukan kasus di mana kode di atas lebih sulit untuk dipertahankan/difaktorkan ulang dibandingkan dengan contoh asli Anda.

Ada juga pembahasan yang cukup mendalam yang mungkin menarik bagi Anda. Lihat Blok Slick 3.0 withTransaction diperlukan untuk berinteraksi dengan perpustakaan.

person Roman    schedule 06.07.2016
comment
Sebenarnya saya dapat menemukan beberapa hal yang salah dengannya: membuat layanan saya secara eksplisit bergantung pada kelas yang apik, jadi tidak ada gunanya memiliki DAO (tidak ada abstraksi yang mungkin dilakukan di lapisan DB). Itu berarti logika bisnis dan akses db saya berada pada level yang sama. Membuatnya sangat sulit untuk diuji dan diejek. Selain itu, jika saya ingin mengubah implementasi akses db saya, saya harus mengubah layanan saya, sehingga refactor apa pun akan menyusahkan. Ini juga mencemari kode bisnis saya dengan masalah lintas sektoral seperti membatasi transaksi yang, sekali lagi, mungkin akan membuat pengujian lebih sulit. Tidak bisa menjadi satu-satunya cara untuk melakukannya. Terima kasih - person redwulf; 06.07.2016
comment
Saya setuju bahwa diinginkan untuk menghindari ketergantungan eksplisit pada slick, tetapi AFAIK tidak ada solusi ATM lain jika Anda ingin bekerja dengan transaksi. Saya tidak mengatakan ini adalah solusi yang pasti, bahkan saya akan sangat senang jika ada yang membuktikan bahwa saya salah. Mengenai perubahan kerangka persistensi: Karena slick tidak seperti ORM biasa, itu mungkin akan menjadi PITA. Dan sejujurnya, berapa kali Anda mengganti kerangka persistensi dalam sistem live? :) Bagaimanapun, saya akan mengikuti thread ini, mungkin seseorang memberikan solusi yang lebih baik. - person Roman; 06.07.2016
comment
Eheh Saya baru saja mengubah kerangka persistensi dari Mongo dan ReactiveMongo ke Postgres dan Slick (jangan tanya...) dan saya yakin dalam waktu dekat, kita harus mengubah sebagiannya kembali ke Mongo atau dokumen db lainnya. Saya mencoba menghindari pemfaktoran ulang besar-besaran lainnya KETIKA hal itu terjadi. Transaksi tidak terlalu penting untuk sebagian besar refactor kedua, tetapi pekerjaan refactor itu sendiri pasti akan berpengaruh. Menulis tes unit juga tampaknya sedikit lebih sulit. Bagaimanapun, semoga orang lain punya jawaban alternatif. - person redwulf; 06.07.2016
comment
Setelah banyak penelitian dan upaya yang gagal, tampaknya tidak mungkin untuk sepenuhnya mengisolasi lapisan persistensi di slick 3, seperti yang saya jelaskan, jadi Anda benar ketika mengatakan ini adalah cara untuk menggunakan slick. Saya masih merasa ini adalah arsitektur yang buruk dan saya mungkin tidak akan pernah menggunakan Slick lagi di proyek lain sampai saya melihat batasan ini teratasi. Namun, saya harus memberikan hadiah tersebut dan, meskipun jawaban Anda BUKAN SOLUSI untuk masalah saya, jawaban tersebut BENAR dan dapat mencegah orang lain membuang banyak waktu untuk mencoba melakukan sesuatu yang tidak mungkin dilakukan. - person redwulf; 18.07.2016
comment
Perlu dicatat juga bahwa Anda akan dapat mengoordinasikan tindakan beberapa panggilan DAO dari satu metode lapisan Layanan, namun pada lapisan Aplikasi Anda cenderung mengoordinasikan beberapa metode Layanan untuk menyelesaikan suatu tugas. Bagaimana Anda bisa memastikan semua metode Layanan berpartisipasi dalam transaksi bersama? Anda tidak ingin mengembalikan tipe Slick dari lapisan Layanan Anda... - person ThaDon; 29.03.2017
comment
saya hanya mendapatkan contoh yang berfungsi jika saya juga melakukan impor berikut di DAO dan Layanan: protected val dbConfig = dbConfigProvider.get[JdbcProfile] import dbConfig._ import profile.api._ Apakah tidak masalah jika DAO dan Layanan menggunakan dbConfigProvider sendiri? Jika Layanan tidak berisi impor tersebut, maka akan terjadi kesalahan kebocoran karena DAO mengembalikan Objek DBIO - person rnd; 18.10.2020