ArangoDB client written in Scala
Scarango is published to Sonatype OSS and Maven Central currently supporting Scala and Scala.js (core only) on 2.13 and 3.
Configuring the driver in SBT requires:
libraryDependencies += "com.outr" %% "scarango-driver" % "3.20.0"
Or in Mill:
ivy"com.outr::scarango-driver:3.20.0"
Scarango wraps ArangoDB's Java library to provide lots of additional features and Scala-specific functionality. We utilize cats-effect and fs2 for a more modern asynchronous approach. Previous to 3.0, we utilized direct HTTP RESTful calls and Futures, but the performance benefits of Java's library made a migration worthwhile.
Although there are a few different ways we can utilize Scarango to interact with ArangoDB, the cleanest and most powerful approach is to utilize the Graph layer to set up our database structure and interact with it in a type-safe way. For example:
import com.outr.arango.{Document, DocumentModel, Field, Graph, Id, Index}
import com.outr.arango.collection.DocumentCollection
import com.outr.arango.query._
import fabric.rw._
import cats.effect.unsafe.implicits.global
// Case class to represent a person collection
case class Person(name: String, age: Int, _id: Id[Person] = Person.id()) extends Document[Person]
// We use the companion object to represent additional information about the collection
object Person extends DocumentModel[Person] {
override implicit val rw: RW[Person] = RW.gen
val name: Field[String] = field("name")
val age: Field[Int] = field("age")
override def indexes: List[Index] = List(
name.index.persistent()
)
override val collectionName: String = "people"
}
// We represent our entire database here referencing all collections
object Database extends Graph("example") {
val people: DocumentCollection[Person, Person.type] = vertex(Person)
}
This is the basic setup of a single-collection database. Notice the RW
in the companion object. That is defined
using Fabric for conversion to/from JSON for storage in the database. All fields aren't
required to be defined, but it will help us when we want to write simple queries in a type-safe way or when defining things
like indexes.
The next thing we need to do is initialize the database:
Database.init().unsafeRunSync()
NOTE: We're adding .unsafeRunSync()
at the end of each call for the documentation generation to get the result. Under
normal circumstances this is not the ideal way to execute a cats-effect IO
.
We can easily clear out everything out of the database:
Database.truncate().unsafeRunSync()
A simple insert of a record into the database:
Database.people.insert(Person("User 1", 30)).unsafeRunSync()
// res2: com.outr.arango.core.CreateResult[Person] = CreateResult(
// key = None,
// id = None,
// rev = None,
// document = Person(
// name = "User 1",
// age = 30,
// _id = Id(value = "Ncx2NMcIpYGtWe9sOd8byYLRuYOCPrnH", collection = "people")
// ),
// newDocument = None,
// oldDocument = None
// )
We can also do batch record insertion:
Database.people.batch.insert(List(
Person("Adam", 21),
Person("Bethany", 19)
)).unsafeRunSync()
// res3: com.outr.arango.core.CreateResults[Person] = CreateResults(
// results = List()
// )
You can also use the Database.people.stream
to cross-stream records into the database.
In order to get the data out that we just inserted we can do a simple AQL query:
Database
.people
.query(aql"FOR p IN ${Database.people} RETURN p")
.toList
.unsafeRunSync()
// res4: List[Person] = List(
// Person(
// name = "User 1",
// age = 30,
// _id = Id(value = "Ncx2NMcIpYGtWe9sOd8byYLRuYOCPrnH", collection = "people")
// ),
// Person(
// name = "Adam",
// age = 21,
// _id = Id(value = "b61wDJm3nMePIBhzHXpE8jOD52NRvL1V", collection = "people")
// ),
// Person(
// name = "Bethany",
// age = 19,
// _id = Id(value = "Ps4IYa9Wfge3y0lwMw3Yxs6ln3CsW3cg", collection = "people")
// )
// )
For an example of data conversion in the result, if we want to only get the person's name back:
Database
.people
.query(aql"FOR p IN ${Database.people} RETURN p.name")
.as[String]
.toList
.unsafeRunSync()
// res5: List[String] = List("Adam", "Bethany", "User 1")
For more examples see the specs: https://github.com/outr/scarango/blob/master/driver/src/test/scala/spec/GraphSpec.scala
- Improved ScalaDocs
- Add AQL compile-time validation support (revive from 2.x)