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?
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
With respect to typesafety and how well retrofitted the DSL is to Cassandra features there is really no contest. The DSL has very "intimate" knowledge of your data structures and provides complete support for Cassandra features. It knows at compile time what is possible with respect to Cassandra and what isn't.
Quill devs argue phantom has a lot more dependencies, but that's not entirely accurate, as most of those are optional, including things like Play iteratees and streams support. What you don't want you don't get, simple as that.
The Quill comparison simply states: "You could extend Phantom by extending the DSL to add new features, although it might not be a straightforward process.", which is a bit inaccurate. Being a very new player in the game, Quill is a toy when it comes to Cassandra feature support and you will often find yourself needing to add features. Phantom has its gaps without a doubt, but it's a far far more mature alternative, and the amount of times when extension is required are significantly rarer.
We have addressed most bugs within a number of days or weeks for more complex features, but generally everything you may require is already there, with a number of features currently not found in Quill that would take me hours even to write down.
I don't come from a strong JPA background, but the mapping between Cassandra and phantom is an extremely powerful layer, since it allows you to auto-generate the entire schema of the tables directly from the mapping DSL. It also allows the DSL to fully mimic Cassandra's behaviour at compile time, it will know which queries are possible with respect to your choice of a primary key and so on, quill has no such support at all.
Phantom has very powerful application level abstraction layers, such as connectors, databases, auto-generation of databases, things that help you run applications into productions.
The code behind Quill is far more complex, and while I would be the first to give strong credit to the engineering ability behind it, when I think user friendliness the story doesn't hold up quite as well.
Quill tries to do a lot more in one go. It's a mini engine for generation, something scalaquery tried to do some years back before they decided to focus entirely on SQL dbs and dropped supports for anything else. It's the precursor to the modern day Slick, and uses a similar QDSL quoted approach.
Quill is a leaking abstraction. As they aim to support a broader range of databases, they have vastly inferior support for the particularities of db specifics. An example is below:
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
Phantom did indeed make it slightly less verbose to extend things like TypeCodec
, but as of phantom 2.9.0 we have introduced an extremely powerful macro mechanism to encode types in Cassandra that doesn't rely on TypeCodec
at all!
Phantom requires minimal boilerplate around defining the table DSL and by nature doesn't really work well with sharing table columns. It can be done, but it's not the most beautiful code, nor is it the worst.
Overall