Phantom Tips: Tip #1: Sharing table logic
Flavian Scala Developer
Flavian is a Scala engineer with many years of experience and the author of phantom and morpheus.

Part two of our introduction to using phantom covers a very important topic in building your application, namely structure and usability. Phantom is reasonably opinionated about what structure you can best use to suit the design we hand in mind while building the driver, and the offer you the very best flexibility while coding. 

The moral of this post is: Don't try to avoid code duplication at table level

A lot of phantom users have asked about how to achieve duplication of table implementations, however we feel that trying to have root traits or any other kind of approach towards eliminating seemingly unwanted duplication is not actually beneficial to your application.

Let's explore that further. The reason why you have duplicate tables in the first place is because you need different partition and primary columns for your tables. It's as simple as that, you duplicate to de-normalise and to support different kinds of queries based on different combinations of index columns for the same data.

It's only a natural conclusion that the most sharing you can get is the sharing of a partition key based query, if you have a combination of tables where that can be shared.

Let's assume table1 has 3 keys, key1, key2, key3 and table2 has 4 keys, key1, key2, key3 and key4. If key1 and key2 are the same for both tables and they both mix in PartitionKey, then you will be able to share the query based around the full partition key.

In phantom, this looks something like this(let's assume all columns are text columns):

import com.websudos.phantom.dsl._

abstract class Table extends CassandraTable[Table1, TableRow] with RootConnector {
  object key1 extends StringColumn(this) with PartitionKey[String]
  object key2 extends StringColumn(this) with PartitionKey[String]

  def fromRow(row: Row): TableRow = ..

  // now the only shareable query of any real use:
  def getByKey1AndKey2(key1: String, key2: String): Future[List[TableRow]] = {
    select.where(_.key1 eqs key1).and(_.key2 eqs key2).fetch()

You can of course have multiple variations on top of the partition key, but that's about it. Because all Cassandra tables are built to denormalise and have queries based on different indexing, it's virtually impossible to fully cover all scenarios.

Phantom enforces type checking on indexes meaning you cannot randomly mix and return what you want. You would have no benefit from that anyway. Furthermore, phantom relies on a complex and sensitive reflection mechanism to offer you things like automated schema generation and automated table migrations in the commercial version, which might just break for the very small gain of minimal DRY compliance.

We hope you've enjoyed this article, it is the first of many to come on phantom, Cassandra and Spark and we look forward to your feedback!

Want to learn more?

As official Datastax partners, Websudos offers a comprehensive range of professional training services for Apache Cassandra and Datastax Enterprise, taking your engineering team from Cassandra newbies to full blown productivity in record time. Our example driven courses are the weapon of choice for companies of any size and if you happen to be a Scala user, we will also throw in a professional training session on using phantom in your company. All of our face-to-face training courses come with free ongoing access to our online training material.

For enquiries and bookings, please contact us by email at

Related articles