Я знаю, что scala как функциональный язык должен работать иначе, чем обычный объектно-ориентированный язык, такой как Java, но я уверен, что должен быть способ обернуть группу изменений базы данных в одну транзакцию, гарантируя атомарность, поскольку а также любое другое свойство ACID.
Как поясняется в справочной документации (http://slick.lightbend.com/doc/3.1.0/dbio.html), DBIOAction позволяет группировать операции БД в транзакции следующим образом:
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)
Тем не менее, мой вариант использования (и большинство реальных примеров, которые я могу придумать), у меня есть структура кода с контроллером, который предоставляет код для моей конечной точки REST, этот контроллер вызывает несколько служб, и каждая служба делегирует операции с базой данных DAO.
Грубый пример моей обычной структуры кода:
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)
}
}
Это простой пример, но большинство из них имеют более сложную бизнес-логику на уровне службы.
Я не хочу:
- Мои DAO должны иметь бизнес-логику и решать, какие операции с базой данных выполнять.
- Верните DBAction из моих DAO и выставьте классы постоянства. Это полностью противоречит цели использования DAO и значительно усложняет дальнейший рефакторинг.
Но я определенно хочу транзакцию вокруг всего моего контроллера, чтобы гарантировать, что в случае сбоя какого-либо кода все изменения, сделанные при выполнении этого метода, будут отменены.
Как я могу реализовать полную транзакцию контроллера с помощью Slick в приложении Scala Play? Я не могу найти документацию о том, как это сделать.
Кроме того, как я могу отключить автоматическую фиксацию в slick? Я уверен, что есть способ, и я просто что-то упускаю.
РЕДАКТИРОВАТЬ:
Поэтому, прочитав об этом немного больше, я чувствую, что теперь лучше понимаю, как slick использует соединения с базой данных и сеансами. Это очень помогло: http://tastefulcode.com/2015/03/19/modern-database-access-scala-slick/.
То, что я делаю, - это случай создания в будущем, и, основываясь на этой статье, нет возможности использовать одно и то же соединение и сеанс для нескольких операций такого рода.
Проблема в том, что я действительно не могу использовать никакую другую композицию. У меня есть значительная бизнес-логика, которую необходимо выполнять между запросами.
Думаю, я могу изменить свой код, чтобы позволить мне использовать композицию действий, но, как я уже упоминал ранее, это вынуждает меня кодировать мою бизнес-логику с учетом таких аспектов, как транзакционность. Этого не должно быть. Это загрязняет бизнес-код и значительно усложняет написание тестов.
Любое обходное решение этой проблемы? Какой-нибудь проект git, который разбирает это, что я пропустил? Или, что более радикально, любой другой фреймворк персистентности, поддерживающий это? Из того, что я читал, Anorm прекрасно поддерживает это, но я могу неправильно понять это и не хочу менять фреймворк, чтобы узнать, что это не так (как это произошло со Slick).