2014-04-04 11 views
11

Sto sviluppando un gioco! 2.2 Applicazione in Scala con Slick 2.0 e ora sto affrontando l'aspetto dell'accesso ai dati, cercando di usare il Pattern Cake. Sembra promettente, ma mi sento davvero come se avessi bisogno di scrivere un sacco di classi/tratti/oggetti solo per ottenere qualcosa di veramente semplice. Quindi potrei usare un po 'di luce su questo.Scala Slick Cake Pattern: oltre 9000 classi?

Prendendo un esempio molto semplice con un concetto User, il modo in cui ho capito è che dovrebbe avere:

case class User(...) //model 

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

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

Finora è assolutamente ragionevole. Ora aggiungere una "torta 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 
    } 
    } 
} 

Poi abbiamo un UserService:

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

Ora mischiamo tutto questo in un oggetto:

object UserModule extends UserService with UserRepository { 
    val userRepo = new UserRepositoryImpl 
    val userService = new UserServiceImpl 
} 
  1. UserRepository è davvero utile? Potrei scrivere findByName come una query personalizzata nell'oggetto slick Users.

  2. Diciamo che ho un altro set di classi come questo per Customer, e ho bisogno di utilizzare alcune funzioni UserService in esso.

devo fare:

CustomerService { 
this: UserService => 
... 
} 

o

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

Da dove arriva 9000? – Blankman

+0

@Blankman http://knowyourmeme.com/memes/its-over-9000 – user1498572

risposta

10

OK, quelli suono come buoni obiettivi:

  • astratta sopra la libreria di database (slick, .. .)
  • rendere l'unità tratti testabili

Si potrebbe fare qualcosa di simile:

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

Poi per CustomerRepository si potrebbe fare:

trait CustomerRepository { this: UserRepository => 
} 

trait SlickCustomerRepository extends CustomerRepository { 
} 

trait RoughCustomerRepository extends CustomerRepository { 
} 

e di combinarle in base al backend capricci:

object UserModuleWithSlick 
    extends SlickUserRepository 
    with SlickCustomerRepository 

object UserModuleWithRough 
    extends RoughUserRepository 
    with RoughCustomerRepository 

È possibile rendere gli oggetti unit-testabile in questo modo:

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

Lei ha ragione di osservare che v'è una forte somiglianza tra

trait CustomerRepository { this: UserRepository => 
} 

object Module extends UserRepository with CustomerRepository 

e

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

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

Questo è il vecchio compromesso ereditarietà/aggregazione, aggiornato per il mondo di Scala. Ogni approccio ha vantaggi e svantaggi. Con i tratti di miscelazione, creerai meno oggetti concreti, che possono essere più facili da tenere traccia di (come sopra, hai solo un singolo oggetto Module, piuttosto che oggetti separati per utenti e clienti). D'altra parte, i tratti devono essere mescolati al momento della creazione dell'oggetto, quindi non è possibile, ad esempio, prendere uno esistente e creare uno CustomerRepository mescolandolo - se è necessario, è necessario utilizzare l'aggregazione. Si noti inoltre che spesso l'aggregazione richiede di specificare tipi di singleton come sopra (: UserModule.type) affinché Scala possa accettare che i tipi dipendenti dal percorso siano gli stessi. Un'altra potenza che ha i tratti di mixaggio è che può gestire dipendenze ricorsive - sia lo UserModule sia lo CustomerModule possono fornire qualcosa e richiedere qualcosa l'uno dall'altro. Questo è anche possibile con l'aggregazione usando lazy vals, ma è più sintatticamente conveniente con i tratti di miscelazione.

+0

Grazie per la tua risposta che potrebbe essere già migliore.Una astrazione che vorrei è la possibilità di passare da slick a qualcos'altro se necessario, con il minor numero possibile di cambiamenti. Inoltre mi piacerebbe essere in grado di testare facilmente tutto questo, ed è anche per questo che ho pensato a questo modello che aiuta le classi di derisione. – user1498572

+0

Grazie per la grande spiegazione e gli esempi, ora è molto più chiaro. Anche i tipi singleton e le dipendenze ricorsive sono stati problemi che ho riscontrato, quindi ha aiutato anche questo. Ciò che mi infastidisce con l'approccio mixin trait è che i moduli contengono rapidamente un sacco di repository/servizi/qualunque, a causa di tutte le dipendenze misti, quindi quando si usa un modulo si finisce per avere accesso a tutti quei "collaterali" classi anche se non hai necessariamente bisogno/vuoi. – user1498572

4

Controlla il mio Slick architecture cheat sheet recentemente pubblicato. Non si astraggono sul driver del database, ma è banale cambiarlo in questo modo. Basta avvolgerlo in

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

Non è necessario il modello di torta. Metti tutto in un file e te ne vai via senza di esso. Il modello di torta ti consente di dividere il codice in file diversi, se sei disposto a pagare l'overhead della sintassi. Le persone usano anche il modello di torta per creare diverse configurazioni tra cui diverse combinazioni di servizi, ma non penso che questo sia rilevante per te.

Se la ripetizione della sintassi ripetuta per tabella del database ti disturba, generare il codice. Il Slick code-generator is customizable esattamente a tale scopo:

Se si desidera fondere il codice scritto a mano e generato, alimentare il codice scritto a mano nel generatore di codice o utilizzare uno schema, in cui il codice generato eredita dal cor scritto a mano viceversa.

Per sostituire Slick con qualcos'altro, sostituire i metodi DAO con le query utilizzando un'altra libreria.

+0

Buon modello, probabilmente hai ragione che in questo caso particolare il motivo della torta è probabilmente un eccesso, ma questo è un esempio di base che ho preso per semplicità, l'applicazione è in realtà più grande e potrebbe utilizzare una certa modularità. Ho provato il generatore di codice, è bello ma ci sono alcuni problemi di effetti collaterali con le evoluzioni del gioco, ad esempio, so che ci sono soluzioni ma non voglio ancora entrarci. Inoltre, mi piace separare completamente la classe del modello Business (utente della classe caso) da qualsiasi dipendenza DAO e averli in file separati aiuta. Ma userò il tuo modello per la parte liscia. – user1498572

+0

Sì, abbiamo bisogno di fornire e l'integrazione immediata del codice slick gen e dell'evoluzione play (o slick). C'è stato un lavoro in questa direzione qui: http://blog.papauschek.com/2013/12/play-framework-evolutions-slick-2-0-code-generator/ – cvogt

+0

Sì, ho visto questo sembra promettente. Probabilmente finirò per utilizzare quando è stabile e l'app diventa troppo grande, ma per ora il numero di classi di modelli che ho non è così grande e può essere gestito manualmente, in più mi aiuta a capire veramente quello che sto facendo:) – user1498572