Shade alternatives and similar packages
Based on the "Database" category.
Alternatively, view Shade alternatives based on common mentions on social networks and blogs.
-
Slick
Slick (Scala Language Integrated Connection Kit) is a modern database query and access library for Scala -
PostgreSQL and MySQL async
DISCONTINUED. Async database drivers to talk to PostgreSQL and MySQL in Scala. -
ScalikeJDBC
A tidy SQL-based DB access library for Scala developers. This library naturally wraps JDBC APIs and provides you easy-to-use APIs. -
scala-redis
A scala library for connecting to a redis server, or a cluster of redis nodes using consistent hashing on the client side. -
scredis
Non-blocking, ultra-fast Scala Redis client built on top of Akka IO, used in production at Livestream -
Scruid
Scala + Druid: Scruid. A library that allows you to compose queries in Scala, and parse the result back into typesafe classes. -
lucene4s
Light-weight convenience wrapper around Lucene to simplify complex tasks and add Scala sugar. -
GCP Datastore Akka Persistence Plugin
akka-persistence-gcp-datastore is a journal and snapshot store plugin for akka-persistence using google cloud firestore in datastore mode.
CodeRabbit: AI Code Reviews for Developers
Do you think we are missing an alternative of Shade or a related project?
Popular Comparisons
README
Shade - Memcached Client for Scala
Overview
Shade is a Memcached client based on the de-facto Java library SpyMemcached.
The interface exposed is very Scala-ish, as you have a choice between making asynchronous calls, with results wrapped as Scala Futures, or blocking calls. The performance is stellar as it benefits from the optimizations that went into SpyMemcached over the years. Shade also fixes some problems with SpyMemcached's architecture, choices that made sense in the context of Java, but don't make so much sense in the context of Scala (TODO: add details).
The client is production quality. Supported for Scala versions: 2.10, 2.11 and 2.12.
Release Notes
- [Version 1.10.x](release-notes/1.10.md)
- [Version 1.9.x](release-notes/1.9.md)
- [Version 1.8.x](release-notes/1.8.md)
- [Version 1.7.x](release-notes/1.7.md)
- [Version 1.6.0](release-notes/1.6.0.md)
Maintainers
These are the people maintaining this project that you can annoy:
- Alex: @alexandru
- Lloyd: @lloydmeta
Usage From SBT
dependencies += "io.monix" %% "shade" % "1.10.0"
Initializing the Memcached Client
To initialize a Memcached client, you need a configuration object. Checkout the [Configuration](src/main/scala/shade/memcached/Configuration.scala) case class.
import shade.memcached._
import scala.concurrent.ExecutionContext.Implicits.global
val memcached =
Memcached(Configuration("127.0.0.1:11211"))
As you can see, you also need an ExecutionContext passed explicitly. As an implementation detail, the execution context represents the thread-pool in which requests get processed.
Simple non-blocking requests
Useful imports:
import concurrent.duration._ // for specifying timeouts
import concurrent.Future
Setting a key:
val op: Future[Unit] = memcached.set("username", "Alex", 1.minute)
Adding a key that will only set it if the key is missing (returns true if the key was added, or false if the key was already there):
val op: Future[Boolean] = memcached.add("username", "Alex", 1.minute)
Deleting a key (returns true if a key was deleted, or false if the key was missing):
val op: Future[Boolean] = memcached.delete("username")
Fetching a key:
val result: Future[Option[String]] = memcached.get[String]("username")
As you can see, for fetching a key the get()
method needs an
explicit type parameter, otherwise it doesn't know how to deserialize
it. More on this below.
Blocking requests
Sometimes working with Futures is painful for quick hacks, therefore
add()
, set()
, delete()
and get()
have blocking versions in the
form of awaitXXX()
:
memcached.awaitGet("username") match {
case Some(username) => println(s"Hello, $username")
case None =>
memcached.awaitSet("username", "Alex", 1.minute)
}
Compare-and-set
Sometimes you want to have some sort of synchronization for modifying values safely, like incrementing a counter. Memcached supports Compare-And-Swap atomic operations and so does this client.
val op: Future[Boolean] =
memcached.compareAndSet("username", Some("Alex"), "Amalia", 1.minute)
This will return either true or false if the operation was a success
or not. But working with compareAndSet
is too low level, so the
client also provides these helpers:
def incrementCounter: Future[Int] =
memcached.transformAndGet[Int]("counter", 1.minute) {
case Some(existing) => existing + 1
case None => 1
}
The above returns the new, incremented value. In case you want the old value to be returned, do this:
def incrementCounter: Future[Option[Int]] =
memcached.getAndTransform[Int]("counter", 1.minute) {
case Some(existing) => existing + 1
case None => 1
}
Serializing/Deserializing
Storing values in Memcached and retrieving values involves serializing
and deserializing those values into bytes. Methods such as get()
,
set()
, add()
take an implicit parameter of type Codec[T]
which
is a type-class that specifies how to serialize and deserialize values
of type T
.
By default, Shade provides default implementations of Codec[T]
for
primitives, such as Strings and numbers. Checkout
[Codec.scala](src/main/scala/shade/memcached/Codec.scala) to see those
defaults.
For more complex types, a default implementation based on Java's ObjectOutputStream and ObjectInputStream exist (also in Codec.scala).
However, because serializing/deserializing values like this is
problematic (you can end up with lots of errors related to the
ClassLoader used), this codec is available as part of the
MemcachedCodecs
trait (also in
[Codec.scala](src/main/scala/shade/memcached/Codec.scala)) and it
either needs to be imported or mixed-in.
The import works like so:
import shade.memcached.MemcachedCodecs._
But this can land you in trouble because of the ClassLoader. For
example in a Play 2.x application, in development mode the code is
recompiled when changes happen and the whole environment gets
restarted. If you do a plain import, you'll get ClassCastException
or other weird errors. You can solve this by mixing-in
MemcachedCodecs
in whatever trait, class or object you want to do
requests, as in:
case class User(id: Int, name: String, age: Int)
trait HelloController extends Controller with MemcachedCodecs {
def memcached: Memcached // to be injected
// a Play 2.2 standard controller action
def userInfo(id: Int) = Action.async {
for (user <- memcached.get[User]("user-" + id)) yield
Ok(views.showUserDetails(user))
}
// ...
}
Or, in case you want to optimize serialization/deserialization, you
can always implement your own Codec[T]
, like:
// hackish example
implicit object UserCodec extends Codec[User] {
def serialize(user: User): Array[Byte] =
s"${user.id}|${user.name}|${user.age}".getBytes("utf-8")
def deserialize(data: Array[Byte]): User = {
val str = new String(data, "utf-8")
val Array(id, name, age) = str.split("|")
User(id.toInt, name, age.toInt)
}
}