I'm trying to follow the example from this blog. I understand the example but having trouble implementing it.
trait Database {
// ...
}
trait UserDb {
this: Database =>
// ...
}
trait EmailService {
this: UserDb =>
// Can only access UserDb methods, cannot touch Database methods
}
The example mentions that the full Database functionality will be hidden from the EmailService - this is what i'm after but don't know how to implement these traits correctly.This is what i tried to implement:
trait Database {
def find(query: String): String
}
trait UserDb {
this: Database =>
}
trait EmailService {
this: UserDb =>
}
trait MongoDatabase extends Database {
}
trait MongoUserDb extends UserDb with MongoDatabase{
}
class EmailServiceImpl extends EmailService with MongoUserDb {
override def find(query: String): String = {
"result"
}
}
It looks weird to me becasue MongoDatabase trait didn't asked for find implementation and when i implemented EmailService i was then prompted for find implementation,although the example mentioned this will be hidden from the EmailService. What am i missing here?
After reading your comments, I'm trying to implement what i'm being trying to understand on an example that is closer to what i'm actually trying to do.
The first snippet won't compile, but the second one will...
At the end of the day i want to have different Repository implementations where i can switch between the Databases they rely on, am i close with one of the snippets below?
trait Database {
def find(s: String): String
}
trait Repository {
this: Database =>
}
class UserRepository extends Repository {
def database = new MongoDB
class MongoDB extends Database {
def find(s: String): String = {
"res"
}
}
}
trait Repository {
def database: Database
trait Database {
def find(s: String): String
}
}
trait UserRepository extends Repository {
def database = new MongoDB
class MongoDB extends Database {
def find(s: String): String = {
"res"
}
}
}
As mentioned MongoUserDB will not ask for an implementation as its a trait. However since EmailServiceImpl extends the trait it needs to provide an implementation.
What you are looking for could be done by adding another abstraction. I do it using the service and DAO architecture.
Below is a working example that you may use to see if it suits you.
//All future versions of DAO will extend this
trait AbstractDAO{
def getRecords:String
def updateRecords(records:String):Unit
}
//One concrete version
trait concreteDAO extends AbstractDAO{
override def getRecords={"Here are DB records"}
override def updateRecords(records:String){
//Actual DB calls and operations
println("Updated "+records)
}
}
//Second concrete version
trait concreteDAO1 extends AbstractDAO{
override def getRecords={"DB Records returned from DAO2"}
override def updateRecords(records:String){
//Actual DB calls and operations
println("Updated via DAO2"+records)
}
}
//This trait just defines dependencies (in this case an instance of AbstractDAO) and defines operations based over that
trait service{
this:AbstractDAO =>
def updateRecordsViaDAO(record:String)={
updateRecords(record)
}
def getRecordsViaDAO={
getRecords
}
}
//Test Stub
object DI extends App{
val wiredObject = new service with concreteDAO //injecting concrete DAO to the service and calling methods
wiredObject.updateRecords("RECORD1")
println(wiredObject.getRecords)
val wiredObject1 = new service with concreteDAO1
wiredObject1.updateRecords("RECORD2")
println(wiredObject1.getRecords)
}
EDIT ---
Here is the code you might want to implement,
trait Database {
def find(s: String): String
}
trait MongoDB extends Database{
def find(s:String):String = { "Test String" }
}
trait SQLServerDB extends Database{
def find(s:String):String = { "Test String2" }
}
trait Repository {
this: Database =>
}
class UserRepository extends Repository with MongoDB{ // UserRepository is injected with MongoDB here
find("call MongoDB") //This call will go to the find method in MongoDB trait
}
class UserRepository1 extends Repository with SQLServerDB{ // UserRepository is injected with SQLServerDB here
find("call SQLServerDB") //This call will go to the find method in SQLServerDB trait
}
Database is hidden from EnailService, but not from EmailServiceImpl. The latter is a subclass of MongoUserDB, obviously, it has access to it.
MongoUserDB does not "ask" for find implementation, because it is a trait, and traits can have abstract methods. You should still implement it there, even without being asked ;)
Related
I'm using phantom to connect cassandra in play framework. Created the first class following the tutorial. Everything works fine.
case class User(id: String, page: Map[String,String])
sealed class Users extends CassandraTable[Users, User] {
object id extends StringColumn(this) with PartitionKey[String]
object page extends MapColumn[String,String](this)
def fromRow(row: Row): User = {
User(
id(row),
page(row)
)
}
}
abstract class ConcreteUsers extends Users with RootConnector {
def getById(page: String): Future[Option[User]] = {
select.where(_.id eqs id).one()
}
def create(id:String, kv:(String,String)): Future[ResultSet] = {
insert.value(_.id, id).value(_.page, Map(kv)).consistencyLevel_=(ConsistencyLevel.QUORUM).future()
}
}
class UserDB(val keyspace: KeySpaceDef) extends Database(keyspace) {
object users extends ConcreteUsers with keyspace.Connector
}
object UserDB extends ResourceAuthDB(conn) {
def createTable() {
Await.ready(users.create.ifNotExists().future(), 3.seconds)
}
}
However, when I try to create another table following the exact same way, play throws the exception when compile:
overriding method session in trait RootConnector of type => com.datastax.driver.core.Session;
How could I build create another table? Also can someone explain what causes the exception? Thanks.
EDIT
I moved the connection part together in one class:
class UserDB(val keyspace: KeySpaceDef) extends Database(keyspace) {
object users extends ConcreteUsers with keyspace.Connector
object auth extends ConcreteAuthInfo with keyspace.Connector
}
This time the error message is:
overriding object session in class AuthInfo; lazy value session in trait Connector of
type com.datastax.driver.core.Session cannot override final member
Hope the message helps identify the problem.
The only problem I see here is not to do with connectors, it's here:
def getById(page: String): Future[Option[User]] = {
select.where(_.id eqs id).one()
}
This should be:
def getById(page: String): Future[Option[User]] = {
select.where(_.id eqs page).one()
}
Try this, I was able to compile. Is RootConnector the default one or do you define another yourself?
It took me 6 hours to figure out the problem. It is because there is a column named "session" in the other table. It turns out that you need to be careful when selecting column names. "session" obviously gives the above exception. Cassandra also has a long list of reserved keywords. If you accidentally use one of them as your column name, phantom will not throw any exceptions (maybe it should?). I don't know if any other keywords are reserved in phantom. A list of them will be really helpful.
Given a class like:
class MyClass {
protected object MyObj { ... }
}
is it possible to write a trait that will permit exposing MyObj. E.g. with inheritance I could do the following:
class TestMyClass extends MyClass {
val getMyObj = MyObj
}
but I want to do this via a trait, something like the following which doesn't typecheck:
trait ExposeMyObj {
val getMyObj = MyObj // super.MyObj/this.MyObj don't work
}
and use it like:
class TestMyClass extends ExposeMyObj
Is it possible to reproduce the functionality in TestMyClass into a trait to expose the protected object, and if so how?
If you know that your trait will always be mixed in to an instance of MyClass (or a subclass), you can enforce the expectation with a self-type, and then access the object:
trait ExposeMyObj {
self: MyClass =>
val getMyObj = MyObj
}
Edit: an example of using this trait:
class TestMyClass extends MyClass with ExposeMyObj
val test = new TestMyClass
test.getMyObj // accesses MyObj defined in MyClass.
Edit 2: attempting to address #jbrown's comment (re: testing queries within repos) - I would look at doing something like the following - first, in each repo's file, add a trait for each repo holding the queries for that repo:
trait UserQueries { // you could look at making this protected, if you like
protected def query1(param: String) = List(param) // very silly implementation, but hopefully enough to make the point
... // other queries
}
class UserRepo extends UserQueries // Has (internal) access to those queries
Then in the test class file for a given repo:
class UserQueriesTester extends UserQueries with ScalaTest { // or whatever test framework you are using
// (public) tests to run - eg:
def testQuery1 = query1("test") should be (List("test"))
}
I have the following construct, where I have a
trait DataServiceLocalImpl extends DataService {
override lazy val dataService = DataComponentLocalImpl
}
object DataComponentLocalImpl extends DataComponent {
def getData(element:String):String = GetStuffFromFile(element)
}
trait DataService {
val dataService: DataComponent
}
trait DataComponent {
def getData(element:String):String
}
The GetStuffFromFile reads a file from disk once (I only want this once, hence the object), creates a map and then returns the value for element.
This is all done in an Play Framework 2.3 surrounding and the app works as well, but when I use it in a test as an implicit I get the following error:
java.lang.NoClassDefFoundError: Could not initialize class DataComponentLocalImpl
Test suite:
class AutoCompleteSpec extends PlaySpec with Mockito with OneAppPerSuite {
val resource = new DataServiceLocalImpl {}
implicit val dataService = resource.dataService
}
If I remove the implicit it works...
You should create an object with the service overriden.
object FakeImpl extends DataServiceLocalImpl {
override dataService = //Fake or test data service here
}
You then create an anonymous class definition that allows you to test the trait.
I am trying to create an abstraction for a SearchService using the Cake pattern. This is what I have currently:
trait SearchServiceComponent{
val searchService:SearchService
trait SearchService{
def searchForSomething(..):List[String]
def updateIndex(..):Boolean
}
}
Lets say I have a DbSearchServiceComponent and LuceneSearchServiceComponent as follows:
trait DbSearchServiceComponent extends SearchServiceComponent{
class DbSearchService extends SearchService{
//Initialize the db client
//Implement all the trait methods
}
}
Similarly...
trait LuceneSearchServiceComponent extends SearchServiceComponent{
class LuceneSearchService extends SearchService{
//Initialize the lucene client
//Implement all the trait methods
}
}
The issue I have with the above snippet is that
I have initialized instances of lucene client and the db client in the Service implementations.
Ideally I would want to "mix-in" a "Client" base type that can be either a Db client or a Lucene client but I am pretty confused as to how to inject a polymorphic client type here.
Can somebody point out how I may be able to refactor the code so that I can inject different versions of the client to my implementations of the SearchService trait?
Not sure if I interpret your question correctly, but that's how you could use the cake pattern for this:
trait SearchServiceComponent {
val searchService: SearchService
trait SearchService {
def searchForSomething(...): List[String]
def updateIndex(...): Boolean
}
}
trait DbSearchServiceComponent extends SearchServiceComponent {
override val searchService = new SearchService {
// Initialize client, implement methods
}
}
trait trait LuceneSearchServiceComponent extends SearchServiceComponent {
override val searchService = new SearchService {
// Initialize client, implement methods
}
}
and upon instantiation:
val myLucenceApp = new Whatever with LuceneSearchServiceComponent
val myDbApp = new Whatever with DbSearchServiceComponent
where Whatever would typically be something along the lines of
class Whatever { this: SearchServiceComponent =>
// ... use `searchService` and do lots of other things
}
I've been following this article which describes how to achieve dependency injection in Scala via the Cake Pattern:
http://jonasboner.com/real-world-scala-dependency-injection-di/
I'm kind of new to Scala and I admit some of it went over my head, so far I've got the following working:
// Setup the component and interface
trait AccountRepositoryComponent {
val accountRepository: AccountRepositoryInterface
trait AccountRepositoryInterface {
def message: String
}
}
// An implementation
trait MyAccountRepositoryComponent extends AccountRepositoryComponent {
object AccountRepository extends AccountRepositoryInterface {
def message: String = "Hello"
}
}
// Object to configure which implementations to use and retrieve them
object ComponentRegistry extends MyAccountRepositoryComponent {
val accountRepository = AccountRepository
}
// Example service using the above
object AccountService {
val repo = ComponentRegistry.accountRepository
def say: String = repo.message
}
println(AccountService.say)
What I'm failing to understand is how I would now pass in a fake repository to Account Service, say to change the output to "Test" rather than "Hello"?
There are various ways this could be modified to achieve a workable result, depending on what counts as a workable result for your situation. I'll go through a simpler possibility here.
First, the ComponentRegistry needs to become a trait, so it can be mixed in to the AccountService:
// Trait to configure which component implementations to use and retrieve them
object ComponentRegistry extends MyAccountRepositoryComponent {
val accountRepository = AccountRepository
}
// Example service using the above
object AccountService extends ComponentRegistry {
def say: String = accountRepository.message
}
println(AccountService.say)
This should print "Hello" as before. To set up a test case, add the following:
// Test implementation
trait TestAccountRepositoryComponent extends AccountRepositoryComponent {
object AccountRepository extends AccountRepositoryInterface {
def message: String = "Test"
}
}
// trait to configure test component implementations
trait TestComponentRegistry extends TestAccountRepositoryComponent {
val accountRepository = AccountRepository
}
Now we can set up a service that uses the test components:
// Example service using the above
object AccountService extends TestComponentRegistry {
//val repo = ComponentRegistry.accountRepository
def say: String = accountRepository.message
}
println(AccountService.say)
This should print "Test".
Note that you would probably want your AccountService to define its functionality in terms of other mixins/traits, which would expect the appropriate components to be available (layered into the "cake"), but wouldn't know which implementation was in use. Eg:
trait CustomerApi {
self: AccountRepositoryComponent => // Expects an implementation of AccountRepositoryComponent to be mixed in
def say: String = accountRepository.message
}
Now the method say is implemented without knowing what version of AccountRepository it will interact with, but knowing one must be provided (checked at compile time). So we can write:
object AccountService extends CustomerApi with ComponentRegistry
object TestAccountService extends CustomerApi with TestComponentRegistry
Calling println(AccountService.say) will generate "Hello", while calling println(TestAccountService.say) will generate "Test".
This post provides a succinct example of that (followed by an interesting alternative).