cassandraplayframework-2.5phantom-dsl

Phantom vs Quill for Playframework (Scala) and Cassandra


I am currently looking at using Cassandra as my database in a PlayFramework project. I was looking for a reactive driver and it seems my choices are limited to Phantom and Quill. My experience with nosql databases is limited to MongoDB and I have not worked with any of Quill or Phantom before.

Looking at the comparison here , it seems one may end up writing more code in Phantom. Moreover, using a DSL to describe models seems counter-intuitive (coming from a heavy hibernate/JPA background) - but that could just be me.

I was wondering if someone can provide practical advice/use cases where one would excel over the other and things to watch out for in each?


Solution

  • In a slightly biased view as the author of phantom, I have a very strong grasp of the design goals in phantom. There is an existing comparison between Quill and Phantom available on the Quill library website, which is naturally biased in the other direction.

    Phantom aims to be the perfect choice for an application level layer, where as Quill aims to be the fanciest string generator, which is not a very useful comparison when you build large apps on top of Cassandra.

    Pros of using phantom

    From the very basic examples in the comparison you read:

    val getAllByCountry = quote {
      (country: String) => query[WeatherStation]
         .filter(_.country == country)
      }
    }
    

    All great so far, presumably less verbose than the phantom equivalent, if we include the necessary mapping code.

    select.where(_.country eqs country).fetch()
    

    But lets explore that further. What if you are trying to fetch a single country like that? Or what if you are trying to fetch PagingState information? Or feed in existing PagingState to display things over a UI.

    This is where Quill at least in the comparison fails to give the user any real preview of what their experience will end up being. It's natural to assume that whenever you go to the page of a tool it will describe itself as the best tool in its category, as certainly we behind phantom do, but that's never the full story.

    To be more concise, a few more cool things:

    select.where(_.country eqs country).fetchRecord()
    select.where(_.country eqs country).one()
    

    What about a partial select?

    select(_.country, _.city).where(_.country eqs country)
    

    Phantom semantically distinguishes between all things that are possible at runtime using Cassandra and it tries above all to prevent runtime errors using compile time trickery and knowledge of the domain. How can you have the Quill equivalent?

    Furthermore, Quill is perfectly capable of generating queries directly from a case class.

      case class WeatherStation(
        country: String,
        city: String,
        stationId: String,
        entry: Int,
        value: Int
      )
    
      object WeatherStation {
    
        val getAllByCountry = quote {
          (country: String) =>
            query[WeatherStation].filter(_.country == country)
        }
    
        val getAllByCountryAndCity = quote {
          (country: String, city: String) =>
            getAllByCountry(country).filter(_.city == city)
        }
    
        val getAllByCountryCityAndId = quote {
          (country: String, city: String, stationId: String) =>
            getAllByCountryAndCity(country, city).filter(_.stationId == stationId)
        }
      }
    

    But it lacks any kind of knowledge about your schema whatsoever. What if country is not part of the primary? That query is invalid, phantom won't let you compile it, and this is just the most basic example.

    Phantom can auto-generate your CQL from the table directly, it can generate entire databases on the fly, the pro version can even auto-migrate tables and help you deal with schema inconsistencies, and give you a very advanced UI and monitoring interface where you can upgrade and downgrade schemas on the fly.

    Cons

    Overall