What's new in phantom 2.0.0

Features in 2.0.0

  • Reflection completely removed in favour of Scala macros.
     
  • Performance improvements in multi tenancy environments.
     
  • Enhanced first class support for collections, and tuples.
     
  • Transparently use collections and other complex types as first class citizens with implicit macros.
     
  • Support for UDTs and UDFs.
     
  • Automated table migrations.

Major benefits

  • Added a more advanced connectors interface to allow for trivial usage of any external Cassandra cluster.
  • Using our custom diesel engine to power queries, now completely macro based.
  • Added database objects to facilitate automated generation of entire databases and complex layered initialisation for automated initialisation of custom types: UDF, UDF etc.
  • Added ability to create complete database objects that can operate over different keyspaces, multi-tenancy support.

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three
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
    )
  )

}

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three
// 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
}

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three
// 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()
   }
}

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

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

Q&A

https://github.com/outworkers/phantom

@alexflav23

@outworkers_uk

Title Text

  • Bullet One
  • Bullet Two
  • Bullet Three

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 {
  // ..
}

What's new in phantom 2.0.x

By Flav Alex

What's new in phantom 2.0.x

  • 662