All Projects → pwliwanow → Foundationdb4s

pwliwanow / Foundationdb4s

Licence: apache-2.0
Type-safe and idiomatic Scala client for FoundationDB

Programming Languages

scala
5932 projects

Projects that are alternatives of or similar to Foundationdb4s

telepooz
Functional Telegram Bot API wrapper for Scala on top of akka, circe, cats, and shapeless
Stars: ✭ 26 (+13.04%)
Mutual labels:  cats, akka-streams
Skunk
A data access library for Scala + Postgres.
Stars: ✭ 1,107 (+4713.04%)
Mutual labels:  cats, database
trembita
Model complex data transformation pipelines easily
Stars: ✭ 44 (+91.3%)
Mutual labels:  cats, akka-streams
Pulsar4s
Idiomatic, typesafe, and reactive Scala client for Apache Pulsar
Stars: ✭ 172 (+647.83%)
Mutual labels:  cats, akka-streams
Es Cqrs Shopping Cart
A resilient and scalable shopping cart system designed using Event Sourcing (ES) and Command Query Responsibility Segregation (CQRS)
Stars: ✭ 19 (-17.39%)
Mutual labels:  cats, akka-streams
Sql Streams
Painless low level jdbc abstraction using the java 8 stream api.
Stars: ✭ 17 (-26.09%)
Mutual labels:  database
Polygen
PolyGen is a code generator that produces database schema, ORM layer, REST API and a (coming soon — stay tuned!) single-page web UI for your business model.
Stars: ✭ 19 (-17.39%)
Mutual labels:  database
Syscrack
Virtual Online Crime Simulator (VOCS) written in PHP 7.0
Stars: ✭ 17 (-26.09%)
Mutual labels:  database
Orbit Db Http Api
A HTTP API Server for the OrbitDB distributed peer-to-peer database
Stars: ✭ 17 (-26.09%)
Mutual labels:  database
Catpapers
Cool vision, learning, and graphics papers on Cats!
Stars: ✭ 918 (+3891.3%)
Mutual labels:  cats
Mariadb Container
MariaDB container images based on Red Hat Software Collections and intended for OpenShift and general usage. Users can choose between Red Hat Enterprise Linux, Fedora, and CentOS based images.
Stars: ✭ 19 (-17.39%)
Mutual labels:  database
Rocker
Erlang/Elixir wrapper for RocksDB
Stars: ✭ 19 (-17.39%)
Mutual labels:  database
Phpmyadmin sql backup
A Python script to automate SQL dumps via phpMyAdmin's web interface
Stars: ✭ 17 (-26.09%)
Mutual labels:  database
Reiner
萊納 - A MySQL wrapper which might be better than the ORMs and written in Golang
Stars: ✭ 19 (-17.39%)
Mutual labels:  database
Mithril Data
A rich data model library for Mithril javascript framework
Stars: ✭ 17 (-26.09%)
Mutual labels:  database
Redix
a persistent real-time key-value store, with the same redis protocol with powerful features
Stars: ✭ 907 (+3843.48%)
Mutual labels:  database
Scalable Image Matching
This is a image matching system for scalable and efficient matching of images from a large database. The basic idea is to compute perceptural hash value for each image and compare the similarity based on the pHash computed. Searching are scalable with the elasticsearch as the backend database.
Stars: ✭ 17 (-26.09%)
Mutual labels:  database
Entity Framework Code First Succinctly
This is the companion repo for Entity Framework Code First Succinctly by Ricardo Peres. Published by Syncfusion.
Stars: ✭ 18 (-21.74%)
Mutual labels:  database
Http.cat
🐱 HTTP Cats API
Stars: ✭ 898 (+3804.35%)
Mutual labels:  cats
Storagedone Android
Kotlin library to make easy using local document-oriented database in Android apps.
Stars: ✭ 18 (-21.74%)
Mutual labels:  database

foundationdb4s

Maven Central Build Status codecov Scala Steward badge

foundationdb4s is a wrapper for FoundationDB Java client. It aims to be type-safe and idiomatic for Scala.

implicit val ec = scala.concurrent.ExecutionContext.global
val database: Database = FDB.selectAPIVersion(620).open(null, ec)

final case class Book(isbn: String, title: String, publishedOn: LocalDate)

val booksSubspace = new TypedSubspace[Book, String] {
  override val subspace: Subspace = new Subspace(Tuple.from("books"))
  override def toKey(entity: Book): String = entity.isbn
  override def toRawValue(entity: Book): Array[Byte] = {
    Tuple.from(entity.title, entity.publishedOn.toString).pack
  }
  override def toTupledKey(key: String): Tuple = Tuple.from(key)
  override def toKey(tupledKey: Tuple): String = tupledKey.getString(0)
  override def toEntity(key: String, value: Array[Byte]): Book = {
    val tupledValue = Tuple.fromBytes(value)
    val publishedOn = LocalDate.parse(tupledValue.getString(1))
    Book(isbn = key, title = tupledValue.getString(0), publishedOn = publishedOn)
  }
}

val dbio: DBIO[Option[Book]] = for {
  _ <- booksSubspace.set(Book("978-0451205766", "The Godfather", LocalDate.parse("2002-03-01")))
  maybeBook <- booksSubspace.get("978-0451205766").toDBIO
} yield maybeBook

val maybeBook: Future[Option[Book]] = dbio.transact(database)

If you:

  • think some functionality is missing
  • find a bug
  • feel that API does not "feel right"

please create an issue.

Quickstart with sbt

To get started you can add the following dependencies to your project:

val fdb4sVersion = "0.11.0"

libraryDependencies ++= Seq(
  "com.github.pwliwanow.foundationdb4s" %% "core" % fdb4sVersion,
  "com.github.pwliwanow.foundationdb4s" %% "schema" % fdb4sVersion,
  "com.github.pwliwanow.foundationdb4s" %% "akka-streams" % fdb4sVersion
)

Note that starting from version 0.10.0, modules were renamed from foundationdb4s-core to core and from foundationdb4s-akka-streams to akka-streams.

Integrations

  • Cats - Monad instances are provided in companion objects for DBIO and ReadDBIO
  • Akka Streams Source implementation (akka-streams module)

Basic abstractions

Modifying data within a TypedSubspace (clear and set operations) returns DBIO[_] monad.

Reading from a TypedSubspace (get and getRange operations) returns ReadDBIO[_] monad. ReadDBIO[_] provides method .toDBIO for converting ReadDBIO[_] into DBIO[_].

Application level schema

Module schema further improves type-safety by requiring schema (simply HList from Shapeless) for keys (KeySchema) and values (ValueSchema) that are to be stored within given Namespace.

Type-safe getRange and clear operations

Having schema enables safe getRange and clear operations - those methods take HList (representing prefix) and during compilation it's checked (by requiring implicit parameter) if given prefix starts with the same types as KeySchema for a given subspace. E.g. given KeySchema: String :: Int :: HNil, it's possible to call getRange(String :: HNil), but getRange(Int :: HNil) will fail to compile.

Schema evolution and encoders/decoders derivation

Module schema also provides support for automatic derivation of encoders and decoders; derived codecs support schema evolution.

E.g. if key was written using TupleEncoder[String :: HNil], it is possible to read the key with TupleDecoder[String :: Option[A] :: HNil] (where A is any type for which TupleDecoder[A] exist) or with TupleDecoder[String :: List[A] :: HNil].

Custom encoders/decoders

Implicit TupleEncoders and TupleDecoders are provided for basic types, such as: Int, Long, Boolean and String. It also supports encoders and decoders for Option[A] and List[A], given that implicit TupleEncoder[A]/TupleDecoder[A] exists.

Encoders and decoders can be automatically derived for any case class, given that there exist implicit encoders/decoders for all its members.

Example with schema namespace

import com.github.pwliwanow.foundationdb4s.schema._
import shapeless.{::, HNil}

implicit val ec = scala.concurrent.ExecutionContext.global
val database: Database = FDB.selectAPIVersion(620).open(null, ec)

object Language extends Enumeration {
  type Language = Value
  val En, De = Value
}
case class ISBN(value: String) extends AnyVal
import Language._

final case class Book(language: Language, isbn: ISBN, title: String, publishedOn: LocalDate)

object Codecs {
  implicit val localDateEnc = implicitly[TupleEncoder[Long]].contramap[LocalDate](_.toEpochDay)
  implicit val localDateDec = implicitly[TupleDecoder[Long]].map(LocalDate.ofEpochDay)
  implicit val languageEnc = implicitly[TupleEncoder[String]].contramap[Language](_.toString)
  implicit val languageDec = implicitly[TupleDecoder[String]].map(Language.withName)
}
import Codecs._

object BookSchema extends Schema {
  type Entity = Book
  type KeySchema = Language :: LocalDate :: ISBN :: HNil
  type ValueSchema = String :: HNil

  override def toKey(entity: Book): BookSchema.KeySchema =
    entity.language :: entity.publishedOn :: entity.isbn :: HNil
  override def toValue(entity: Book): BookSchema.ValueSchema =
    entity.title :: HNil
  override def toEntity(key: BookSchema.KeySchema, valueRepr: BookSchema.ValueSchema): Book = {
    val language :: publishedOn :: isbn :: HNil = key
    val title :: HNil = valueRepr
    Book(language, isbn, title, publishedOn)
  }
}
  
val booksNamespace = new BookSchema.Namespace(new Subspace(Tuple.from("books")))

val dbio: ReadDBIO[Seq[Book]] = booksNamespace.getRange((Language.En, LocalDate.of(2018, 7, 10)))
// or booksNamespace.getRange(Language.En :: LocalDate.of(2018, 7, 10) :: HNil)
val result: Future[Seq[Book]] = dbio.transact(database)

Parallel requests

Sometimes it may prove useful to perform operations in parallel (e.g. in case of independent gets). For that use case Parallel type class from Cats is provided: it allows users to use operations like parSequence, parTraverse and parMapN.

// `Parallel` type classes are defined in `DBIO` and `ReadDBIO` companion objects, 
// so there is no need to import them as they are in scope automatically 
import cats.implicits._

val dbios: List[ReadDBIO[Option[Book]]] = 
  List(booksSubspace.get("978-0451205766"), booksSubspace.get("978-1491962299"))
// instruct `dbios` to be run in parallel
val dbio: ReadDBIO[List[Option[Book]]] = dbios.parSequence
val result: Future[List[Option[Book]]] = dbio.transact(database)

Versionstamps

Versionstamp consists of "transaction" version and of a user version.

Transaction version is usually assigned by the database in such a way that all transactions receive a different version that is consistent with a serialization order of the transactions within the database. This also implies that the transaction version of newly committed transactions will be monotonically increasing over time. Note that transaction version will be assigned during commit, which implies that it is not possible to use/get "current" transaction version inside the transaction itself.

User version should be set by the client. It allows the user to use this class to impose a total order of items across multiple transactions in the database in a consistent and conflict-free way.

More information in FoundationDB Javadoc.

Working with Versionstamps

foundationdb4s supports working with keys or values that contain versionstamps by providing SubspaceWithVersionstampedKeys and SubspaceWithVersionstampedValues. Compared to TypedSubspace, those require additional method to be implemented: extractVersionstamp.

To obtain versionstamp which was used by any versionstamp operations in this DBIO, use transactVersionstamped instead of transact.

Note that if the given DBIO did not modify the database, returned Versionstamp will be empty.

implicit val ec = scala.concurrent.ExecutionContext.global
val database: Database = FDB.selectAPIVersion(620).open(null, ec)

case class EventKey(eventType: String, versionstamp: Versionstamp)
case class Event(key: EventKey, content: Array[Byte])

val eventsSubspace = new SubspaceWithVersionstampedKeys[Event, EventKey] {
  override val subspace: Subspace = new Subspace(Tuple.from("events"))
  override def toKey(entity: Event): EventKey = entity.key
  override def toRawValue(entity: Event): Array[Byte] = event.content
  override def toTupledKey(key: EventKey): Tuple = Tuple.from(key.eventType, key.versiostamp)
  override def toKey(tupledKey: Tuple): EventKey = {
    EventKey(tupledKey.getString(0), tupledKey.getVersiostamp(1))
  }
  override def toEntity(key: EventKey, value: Array[Byte]): Event = Event(key, value)
  override def extractVersionstamp(key: EventKey): Versionstamp = key.versionstamp
}

val event = Event(
  key = EventKey("UserAdded", Versionstamp.incomplete(0)), 
  content = Tuple.from("""{ "name": "John Smith" }""").pack)

// save new event
val setDbio: DBIO[Unit] = eventsSubspace.set(event)
val completedVersionstamp: Versionstamp = 
  Await.result(
    setDbio
      .transactVersionstamped(database, userVersion = 0)
      .map { case (_, Some(versionstamp)) => versionstamp },
    Duration.Inf)

// update previously persisted event
val updatedEvent = event.copy(key = event.key.copy(versionstamp = completedVersionstamp))
val updateDbio = eventsSubspace.set(updatedEvent)

updateDbio.transact(database)

Watches

When application needs to monitor changes done to a given key, it can periodically read the key or it can use watches. Watches are created for a given key, and return a Promise that will be completed, once the value for the key changes.

Note that there is limited number of watches that can be active for each database connection (by default 10,000), so watches that are no longer needed should be canceled. Once the number is exceeded creating new watches will fail.

// reusing first example
final case class Book(isbn: String, title: String, publishedOn: LocalDate)
val booksSubspace: TypedSubspace[Book, String] = ???

val key = "978-0451205766"
val dbio: DBIO[Promise[Unit]] = booksSubspace.watch(key)
val futureWatch: Future[Promise[Unit]] = dbio.transact(database)

More information about watches can be found in FoundationDB developer guide and FoundationDB Javadoc.

Reading big amount of data

If you want to stream data from a subspace, it can take longer than FoundationDB transaction time limit, your data is immutable and append only, or if approximation is good enough for your use case, you can use either use SubspaceSource (from akka-streams module) or you can use RefreshingSubspaceStream (from core module).

Advantage of using SubspaceSource is that it closes resources automatically and exposes easier to use API leveraging Akka Streams.

To create a source you need at least subspace: TypedSubspace[Entity, Key] and database: Database:

val source: Source[Entity, _] = SubspaceSource.from(subspace, database)

However, if you don't want to add Akka as a dependency or you need more control over streaming the data you can use RefreshingSubspaceStream.

Processing data continuously

If a subspace (or part of a subspace) is modeled as a log and one wants to process the data once it arrives, InfinitePollingSubspaceSource may become useful.

InfinitePollingSubspaceSource will stream the data from the subspace (or from part of a subspace). Once it reaches the last element it will try to resume from the last seen value.

val processEntityFlow: Flow[Entity, Entity, NotUsed] = ???
val commitOffsetSink: Sink[Entity, NotUsed] = ???
val lastSeenKey: Array[Byte] = fetchLastSeenKey()
val source = Source[Entity, _] = 
  InfinitePollingSubspaceSource.from(
    typedSubspace, 
    database, 
    pollingInterval = 100.millis, 
    begin = KeySelector.firstGreaterThan(lastSeenKey))
source.via(processEntityFlow).to(commitOffsetSink).run()

Example - class scheduling

Module example contains implementation of Class Scheduling from FoundationDB website.

Contributing

Contributors and help is always welcome!

Please make sure that issue exists for the functionality that you want to create (or bug that you want to fix), and in the commit message please include issue number and issue title (e.g. "#1 Incorrect ...").

Testing

To run tests you'll need a local FoundationDB instance running (here are installation instructions for Linux and macOS).

Then simply execute sbt test.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].