pishen / store4s

A Scala library for Google Cloud Datastore

Version Matrix

store4s

Maven Central javadoc

A Scala library for Google Cloud Datastore, providing compile-time mappings between case classes and Datastore entities, and a type-safe query DSL.

Installation

For regular use:

libraryDependencies += "net.pishen" %% "store4s" % "<version>"

For datastore-v1 (compatible with Apache Beam):

libraryDependencies += "net.pishen" %% "store4s-v1" % "<version>"

Encoding

Convert a case class to entity using asEntity:

import store4s._

case class Zombie(number: Int, name: String, girl: Boolean)

implicit val datastore = Datastore.defaultInstance

val z6 = Zombie(6, "Lily Hoshikawa", false).asEntity

// provide a name
val z1 = Zombie(1, "Sakura Minamoto", true).asEntity("heroine")
// provide an id
val z2 = Zombie(2, "Saki Nikaido", true).asEntity(2)

The basic data types, Seq, Option, and nested case classes are supported.

To support custom types, one can create a ValueEncoder from an existing ValueEncoder using contramap:

val enc: ValueEncoder[LocalDate] =
  ValueEncoder.stringEncoder.contramap[LocalDate](_.toString)

To insert, upsert, or update the entity into datastore:

datastore.add(z6)
datastore.put(z1)
datastore.update(z2)

Decoding

Get an entity from datastore:

import store4s._
case class Zombie(number: Int, name: String, girl: Boolean)

val ds = Datastore.defaultInstance

val key1 = ds.keyFactory[Zombie].newKey("heroine")
val e1: Option[Entity] = ds.get(key1)

Decode an entity into case class using decodeEntity:

val zE: Either[Throwable, Zombie] = decodeEntity[Zombie](e1.get)

If you want to decode the entity directly and throw the Exception when it fail:

val zOpt: Option[Zombie] = ds.getRight[Zombie]("heroine")

To support custom types, one can create a ValueDecoder from an existing ValueDecoder using map or emap:

val dec: ValueDecoder[LocalDate] =
  ValueDecoder.stringDecoder.map(LocalDate.parse)

Querying

A query can be built using the Query object:

import store4s._
case class Zombie(number: Int, name: String, girl: Boolean)

implicit val datastore = Datastore.defaultInstance

val q = Query[Zombie]
  .filter(_.girl)
  .filter(_.number > 1)
  .sortBy(_.number.desc)
  .take(3)

val r1: EntityQuery = q.builder().build()
val r2: Seq[Entity] = q.run.getEntities
val r3: Seq[Either[Throwable, Zombie]] = q.run.getEithers
val r4: Seq[Zombie] = q.run.getRights

Use getRights to decode the Entities and throw Exceptions if any decoding failed.

For querying on array type values, which corresponds to Seq, an exists function is available:

import store4s._
case class Task(tags: Seq[String])

implicit val datastore = Datastore.defaultInstance

Query[Task]
  .filter(_.tags.exists(_ == "Scala"))
  .filter(_.tags.exists(_ == "rocks"))
  .run

For querying on the properties of embedded entity (which can be referred using .):

import store4s._
case class Hometown(country: String, city: String)
case class Zombie(name: String, hometown: Hometown)

implicit val datastore = Datastore.defaultInstance

Query[Zombie]
  .filter(_.hometown.city == "Saga")
  .run

Check the testing code for more supported features.