Scala Вырезать торт: более 9000 классов?

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

Принимая очень простой пример с концепцией 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
    }
  }
}

Тогда имеем a 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 
}
  • Действительно ли UserRepository полезен? Я мог написать findByName как пользовательский запрос в Users slick-объекте.

  • Скажем, у меня есть еще один набор таких классов для Customer, и мне нужно использовать в нем некоторые UserService.

Должен ли я делать:

CustomerService {
this: UserService =>
...
}

или

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

Ответ 1

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

  • Аннотация по библиотеке базы данных (slick,...)
  • Проверить работоспособность блока признаков

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

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 могут обеспечить что-то и что-то требовать друг от друга. Это также возможно при агрегировании с использованием ленивых vals, но это более синтаксически удобно со смесями.

Ответ 2

Ознакомьтесь с опубликованным недавно опубликованным графическим чит-листом. Это не абстрактно по сравнению с драйвером базы данных, но это тривиально. Просто оберните его в

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

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

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

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

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