import sbt._
import Keys._
object Build extends Build {
val PhantomVersion = "2.0.0"
val sharedSettings: Seq[Def.Setting[_]] = Seq(
organization := "com.demo",
version := "0.1.0",
scalaVersion := "2.10.5",
resolvers ++= Seq(
Resolver.typesafeRepo("releases"),
"Java.net Maven2 Repository" at "http://download.java.net/maven/2/",
Resolver.bintrayRepo("websudos", "oss-releases")
),
unmanagedSourceDirectories in Compile <<= (scalaSource in Compile)(Seq(_)),
scalacOptions ++= Seq(
"-language:postfixOps",
"-language:implicitConversions",
"-language:reflectiveCalls",
"-language:higherKinds",
"-language:existentials",
"-deprecation",
"-feature",
"-unchecked"
)
) ++ Defaults.coreDefaultSettings
lazy val root = Project(
id = "root",
base = file("."),
settings = sharedSettings
).aggregate(
db
)
// Let's assume you have a DB module in your application
lazy val db = Project(
id = "db",
base = file("db"),
settings = sharedSettings
).settings(
libraryDependencies ++= Seq(
"com.outworkers" %% "phantom-dsl" % PhantomVersion,
"com.outworkers" %% "phantom-testkit" % PhantomVersion
)
)
}
// Now let's assume we are creating a table module.
// First we start by defining our database connection.
// This is just one definition, created to specify the "production" configuration.
// Phantom makes it trivial to define any number of concurrent connections,
// feature mainly used to create a production database and a test database quite easily, as we are about to demonstrate.
package com.demo.db
import com.outworkers.phantom.dsl._
// Connecting to the database is the first thing
object Defaults {
// The local connector is pointing to a local Cassandra installation on port 9042.
val connector = ContactPoint.local.keySpace("demoapp")
// It is also possible to connect to any host using the following syntax.
// The IPs you provide here should point to the seed node(s) in your cluster.
val connector = ContactPoints(Seq("1.1.1.1", "1.1.1.1"))
}
class AppDatabase(override val connector: KeySpaceDef) extends Database[AppDatabase] {
object users extends ConcreteUsers with connector.Connector
object people extends PeopleTable with connector.Connector
}
// Now let's define our first table.
// The first example we are going to pick is a users table.
package com.demo.db
import org.joda.time.DateTime
import com.outworkers.phantom.dsl._
// Modelling in phantom usually starts with a case class definition.
case class User(
id: UUID,
email: String,
name: String,
passwordHash: String,
salt: String,
registration: DateTime
)
abstract class Users extends CassandraTable[ConcreteUsers, User] {
object id extends UUIDColumn(this) with PartitionKey[UUID]
object email extends StringColumn(this)
object name extends StringColumn(this)
object passwordHash extends StringColumn(this)
object salt extends StringColumn(this)
object registration extends DateTimeColumn(this)
}
// Now using this class we define the custom methods on our table.
// The connector allows the methods inside the class to compile
// While we define the actual session and keyspace in use at a later stage.
abstract class ConcreteUsers extends Users with Connector {
// A very common thing to do is to have a store method.
// There is a bit of boilerplate to type and there is currently no way around it.
// The upcoming enterprise version of phantom provides advanced generation mechanisms
// that automatically create this table with all methods directly from the above case class.
def store(user: User): Future[ResultSet] = {
insert.value(_.id, user.id)
.value(_.email, user.email)
.value(_.name, user.name)
.value(_.passwordHash, user.passwordHash)
.value(_.salt, user.salt)
.value(_.registration, user.registration)
}
// It's a much better idea to define queries in here,
// as this way we encapsulate everything in one object
// If we don't do this, we will need to ensure other objects correctly propagate
// the implicit session and keyspace definition inside the connector we use.
def getById(id: UUID): Future[Option[User]] = {
select.where(_.id eqs id).one()
}
def deleteById(id: UUID): Future[ResultSet] = {
delete.where(_.id eqs id).one()
}
}
import java.util.UUID
import scala.concurrent.Future
import org.joda.time.DateTime
import com.outworkers.phantom.dsl._
import com.outworkers.phantom.example.basics.Recipe
/**
* In this example we will create a table storing recipes.
* This time we will use a composite key formed by name and id.
*/
// You can seal the class and only allow importing the companion object.
// The companion object is where you would implement your custom methods.
// Keep reading for examples.
sealed class AdvancedRecipes extends CassandraTable[ConcreteAdvancedRecipes, Recipe] {
// First the partition key, which is also a Primary key in Cassandra.
object id extends UUIDColumn(this) with PartitionKey[UUID] {
// You can override the name of your key to whatever you like.
// The default will be the name used for the object, in this case "id".
override lazy val name = "the_primary_key"
}
object name extends StringColumn(this)
object title extends StringColumn(this)
object author extends StringColumn(this)
object description extends StringColumn(this)
// Custom data types can be stored easily.
// Cassandra collections target a small number of items, but usage is trivial.
object ingredients extends SetColumn[ConcreteAdvancedRecipes, Recipe, String](this)
object props extends MapColumn[ConcreteAdvancedRecipes, Recipe, String, String](this)
object timestamp extends DateTimeColumn(this) with ClusteringOrder[DateTime]
}
abstract class ConcreteAdvancedRecipes extends AdvancedRecipes with RootConnector {
def insertRecipe(recipe: Recipe): ScalaFuture[ResultSet] = {
insert.value(_.id, recipe.id)
.value(_.author, recipe.author)
.value(_.description, recipe.description)
.value(_.ingredients, recipe.ingredients)
.value(_.name, recipe.name)
.value(_.props, recipe.props)
.value(_.timestamp, recipe.timestamp)
.future()
}
// Like in the real world, you have now planned your queries ahead.
// You know what you can do and what you can't based on the schema limitations.
def findById(id: UUID): ScalaFuture[Option[Recipe]] = {
select.where(_.id eqs id).one()
}
}
https://github.com/outworkers/phantom
@alexflav23
@outworkers_uk
import java.util.UUID
import scala.concurrent.Future
import org.joda.time.DateTime
import com.outworkers.phantom.dsl._
import com.outworkers.phantom.udt._
@Udt case class Location(location: Long, latitude: Long)
@Udt case class Address(id: UUID, location: Location, postcode: String)
case class Person(
id: UUID,
email: String,
address: Address,
previous_addresses: List[Address]
)
class PeopleTable extends CassandraTable[ConcretePeopleTable, Person] {
object id extends UUIDColumn(this) with PartitionKey[UUID]
object email extends StringColumn(this) with PartitionKey[UUID]
object addresses extends UDTColumn[ConcretePeopleTable, Person, Address](this)
object previous_addresses extends UDTListColumn[ConcretePeopleTable, Person, Address](this)
}
class ConcretePeopleTable extends PeopleTable with RootConnector {
// ..
}