Scala Slick Cake Pattern: более 9000 классов?

Я разрабатываю Play! 2.2 приложения в Scala с Slick 2.0, и я сейчас занимаюсь аспектом доступа к данным, пытаясь использовать Cake Pattern. Это кажется многообещающим, но я действительно чувствую, что мне нужно написать огромную кучу классов / черт / объектов, чтобы достичь чего-то действительно простого. Так что я мог бы использовать немного света на этом.

Взяв очень простой пример с User Концепция, как я понимаю, мы должны иметь:

case class User(...) //model

class Users extends Table[User]... //Slick Table

object users extends TableQuery[Users] { //Slick Query
//custom queries
}

Пока это абсолютно разумно. Теперь мы добавляем "Cake Patternable" UserRepository:

trait UserRepository {
 val userRepo: UserRepository
 class UserRepositoryImpl {
    //Here I can do some stuff with slick
    def findByName(name: String) = {
       users.withFilter(_.name === name).list
    }
  }
}

Тогда у нас есть UserService:

trait UserService {
 this: UserRepository =>
val userService: UserService
 class UserServiceImpl { //
    def findByName(name: String) = {
       userRepo.findByName(name)
    }
  }
}

Теперь мы смешиваем все это в объекте:

object UserModule extends UserService with UserRepository {
    val userRepo = new UserRepositoryImpl
    val userService = new UserServiceImpl 
}
  1. Является UserRepository действительно полезно? Я мог бы написать findByName как пользовательский запрос в Users гладкий объект.

  2. Допустим, у меня есть другой набор классов, как это для Customerи мне нужно использовать некоторые UserService особенности в нем.

Я должен делать:

CustomerService {
this: UserService =>
...
}

или же

CustomerService {
val userService = UserModule.userService
...
}

2 ответа

Решение

ОК, это звучит как хорошие цели:

  • Аннотация над библиотекой базы данных (пятно, ...)
  • Сделайте тестируемую единицу признаков

Вы могли бы сделать что-то вроде этого:

trait UserRepository {
    type User
    def findByName(name: String): User
}

// Implementation using Slick
trait SlickUserRepository extends UserRepository {
    case class User()
    def findByName(name: String) = {
        // Slick code
    }
}

// Implementation using Rough
trait RoughUserRepository extends UserRepository {
    case class User()
    def findByName(name: String) = {
        // Rough code
    }
}

Тогда для CustomerRepository Вы могли бы сделать:

trait CustomerRepository { this: UserRepository =>
}

trait SlickCustomerRepository extends CustomerRepository {
}

trait RoughCustomerRepository extends CustomerRepository {
}

И объедините их, основываясь на своих прихотях:

object UserModuleWithSlick
    extends SlickUserRepository
    with SlickCustomerRepository

object UserModuleWithRough
    extends RoughUserRepository
    with RoughCustomerRepository

Вы можете создавать объекты для тестирования юнитов, например, так:

object CustomerRepositoryTest extends CustomerRepository with UserRepository {
    type User = // some mock type
    def findByName(name: String) = {
        // some mock code
    }
}

Вы правильно заметили, что существует сильное сходство между

trait CustomerRepository { this: UserRepository =>
}

object Module extends UserRepository with CustomerRepository

а также

trait CustomerRepository {
    val userRepository: UserRepository
    import userRepository._
}

object UserModule extends UserRepository
object CustomerModule extends CustomerRepository {
    val userRepository: UserModule.type = UserModule
}

Это старый компромисс между наследованием и агрегацией, обновленный для мира Scala. У каждого подхода есть свои преимущества и недостатки. Благодаря смешанным чертам вы будете создавать меньше конкретных объектов, которые легче отслеживать (как показано выше, у вас есть только один Module объект, а не отдельные объекты для пользователей и клиентов). С другой стороны, черты должны быть смешаны во время создания объекта, поэтому вы не можете, например, взять существующий UserRepository и сделать CustomerRepository смешивая это - если вам нужно это сделать, вы должны использовать агрегацию. Также обратите внимание, что при агрегации часто требуется указывать одноэлементные типы, как указано выше (: UserModule.type) для того, чтобы Scala признал, что зависимые от пути типы одинаковы. Еще одна сила, которой обладают черты смешения, заключается в том, что они могут обрабатывать рекурсивные зависимости - как UserModule и CustomerModule может предоставить что-то и требовать что-то друг от друга. Это также возможно с агрегацией с использованием ленивых значений, но это более синтаксически удобно с чертами смешивания.

Ознакомьтесь с моей недавно опубликованной шпаргалкой по архитектуре Slick. Он не абстрагируется от драйвера базы данных, но тривиально изменить его таким образом. Просто заверните это в

class Profile(profile: JdbcProfile){
  import profile.simple._
  lazy val db = ...
  // <- cheat sheet code here
}

Вам не нужен рисунок торта. Просто поместите все это в один файл, и вы обойдетесь без него. Шаблон торт позволяет разбить код на разные файлы, если вы готовы платить за синтаксис. Люди также используют шаблон тортов для создания различных конфигураций, включая различные комбинации сервисов, но я не думаю, что это имеет отношение к вам.

Если повторяющиеся синтаксические издержки для таблицы базы данных вас беспокоят, сгенерируйте код. Генератор кода Slick настраивается именно для этой цели:

Если вы хотите смешать рукописный и сгенерированный код, либо подайте рукописный код в генератор кода, либо используйте схему, где сгенерированный код наследуется от рукописного наоборот.

Чтобы заменить Slick чем-то другим, замените методы DAO запросами, использующими другую библиотеку.

Другие вопросы по тегам