All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avsystem.commons.redis.RedisApi.scala Maven / Gradle / Ivy

package com.avsystem.commons
package redis

import com.avsystem.commons.misc.ValueOf
import com.avsystem.commons.redis.config.ExecutionConfig

/**
  * Object which contains implementations of various variants of Redis API that this driver provides.
  * Each variant implements a set of methods corresponding directly to Redis commands, e.g.
  * [[commands.StringsApi.get get]] method represents Redis `GET` command.
  *
  * API variants may differ from each other in several independent aspects:
  *
  * The most important one is the result type returned by every method corresponding to a Redis command:
  *
  *  - [[RedisApi.Raw]] - type returned for each command-method is [[RawCommand]]. Probably the only reason
  * to use this API variant is to pass a [[RawCommand]] to
  * [[commands.NodeServerApi.commandGetkeys(command:com\.avsystem\.commons\.redis\.RawCommand)* commandGetkeys]]
  *  - [[RedisApi.Batches]] - type returned for each command-method is [[RedisBatch]].
  * Batch can then be combined with other batches to form larger batches, become a part of [[RedisOp]] or simply
  * get executed by [[RedisExecutor]] (e.g. one of Redis client implementations).
  *  - [[RedisApi.Keyed.Async]], [[RedisApi.Node.Async]] and [[RedisApi.Connection.Async]] variants are the ones that
  * actually ''execute'' commands by sending them to Redis. Because of that, they take an appropriate [[RedisExecutor]]
  * as constructor argument. Execution of commands is asynchronous and results are returned as
  * `Future`s.
  *  - [[RedisApi.Keyed.Blocking]], [[RedisApi.Node.Blocking]] and [[RedisApi.Connection.Blocking]] are similar
  * to their `Async` counterparts but execution is blocking and results are returned as unwrapped values.
  *
  * `Async` and `Blocking` API variants additionally come in three different "levels", each one exposing different
  * subset of Redis commands. This reflects the fact that not every [[RedisExecutor]] (client implementation) supports
  * every command (e.g. you can't execute unkeyed commands using [[RedisClusterClient]]).
  *
  *  - Variants from [[RedisApi.Keyed]] include only commands with keys (so that they can be executed on Redis Cluster)
  *  - Variants from [[RedisApi.Node]] include only commands which don't access connection state
  *  - Variants from [[RedisApi.Connection]] include all commands supported by the driver
  *
  * Every API variant may also use different types to represent Redis keys, hash keys and values.
  * You can define your own API variants for arbitrary combination of key, hash key and value types
  * as long as there is an instance of [[RedisDataCodec]] for every of these types.
  * Also, it is possible to customize `Record` type which is used primarily for entries in Redis Stream API.
  * `Record` type requires [[RedisRecordCodec]] instance.
  *
  * Key, field, value and record types and their serialization typeclass instances are enapsulated by
  * [[RedisSerialization]] instances. API variants are then parameterized with them.
  *
  * API variants which use only `String`s (textual) or only `ByteString`s (binary) are already implemented by the driver, e.g.
  * [[RedisApi.Keyed.Async.StringTyped]], [[RedisApi.Batches.BinaryTyped]].
  *
  * Note that [[RedisDataCodec]] is automatically provided for many simple types and also all types which have a
  * `GenCodec`. This effectively gives you a complete serialization
  * framework for keys, hash keys and values stored in Redis.
  *
  * Note that chosen key, hash key and value types can be adjusted "on the fly" with a convenient syntax.
  * For example, if you need to use some case class as a value type in a single, specific place, you can do it
  * without defining a completely separate API variant. For example:
  *
  * {{{
  *   case class Person(name: String, birthYear: Int)
  *   object Person {
  *     implicit val codec: GenCodec[Person] = GenCodec.materialize[Person]
  *   }
  *
  *   import scala.concurrent.duration._
  *   implicit val system: ActorSystem = ActorSystem()
  *
  *   val api = RedisApi.Keyed.Blocking.StringTyped(new RedisClusterClient)
  *
  *   // normally, we're just using String-typed API
  *   api.set("key", "value")
  *
  *   // but in one specific place, we might want to use Person as the value
  *   // Person has an instance of GenCodec, so it will be automatically serialized to binary format
  *   api.valueType[Person].set("binaryDataKey", Person("Fred", 1990))
  * }}}
  */
object RedisApi {
  class Raw[S <: RedisSerialization](val serialization: S)
    extends AbstractRedisApi[S] with RedisConnectionApi with RedisRawApi {

    type Self[S0 <: RedisSerialization] = Raw[S0]
    def withSerialization[S0 <: RedisSerialization](ser: S0): Raw[S0] = new Raw(ser)
  }

  /**
    * Entry point for API variants which return [[RawCommand]]s.
    */
  object Raw {
    def apply[S <: RedisSerialization : ValueOf]: Raw[S] = new Raw(ValueOf[S])

    final val StringTyped = apply[RedisSerialization.Strings.type]
    final val BinaryTyped = apply[RedisSerialization.ByteStrings.type]
  }

  class Batches[S <: RedisSerialization](val serialization: S)
    extends AbstractRedisApi[S] with RedisConnectionApi with RedisBatchApi {

    type Self[S0 <: RedisSerialization] = Batches[S0]
    def withSerialization[S0 <: RedisSerialization](ser: S0): Batches[S0] = new Batches(ser)
  }

  /**
    * Entry point for API variants which return [[RedisBatch]]es.
    */
  object Batches {
    def apply[S <: RedisSerialization : ValueOf]: Batches[S] = new Batches(ValueOf[S])

    final val StringTyped = apply[RedisSerialization.Strings.type]
    final val BinaryTyped = apply[RedisSerialization.ByteStrings.type]
  }

  /**
    * Entry point for API variants which expose only keyed commands.
    */
  object Keyed extends ExecutedApis {
    type RequiredExecutor = RedisKeyedExecutor

    case class Async[S <: RedisSerialization](
      serialization: S,
      executor: RequiredExecutor,
      execConfig: ExecutionConfig
    ) extends BaseAsync[S] with RedisRecoverableKeyedApi
    object Async extends VariantCompanion[Async]

    case class Monix[S <: RedisSerialization](
      serialization: S,
      executor: RequiredExecutor,
      execConfig: ExecutionConfig
    ) extends BaseMonix[S] with RedisRecoverableKeyedApi
    object Monix extends VariantCompanion[Monix]

    case class Blocking[S <: RedisSerialization](
      serialization: S,
      executor: RequiredExecutor,
      execConfig: ExecutionConfig
    ) extends BaseBlocking[S] with RedisRecoverableKeyedApi
    object Blocking extends VariantCompanion[Blocking]
  }

  /**
    * Entry point for API variants which expose node-level commands, i.e. the ones that don't access or modify
    * Redis connection state.
    */
  object Node extends ExecutedApis {
    type RequiredExecutor = RedisNodeExecutor

    case class Async[S <: RedisSerialization](
      serialization: S,
      executor: RequiredExecutor,
      execConfig: ExecutionConfig
    ) extends BaseAsync[S] with RedisRecoverableNodeApi
    object Async extends VariantCompanion[Async]

    case class Monix[S <: RedisSerialization](
      serialization: S,
      executor: RequiredExecutor,
      execConfig: ExecutionConfig
    ) extends BaseMonix[S] with RedisRecoverableNodeApi
    object Monix extends VariantCompanion[Monix]

    case class Blocking[S <: RedisSerialization](
      serialization: S,
      executor: RequiredExecutor,
      execConfig: ExecutionConfig
    ) extends BaseBlocking[S] with RedisRecoverableNodeApi
    object Blocking extends VariantCompanion[Blocking]
  }

  /**
    * Entry point for API variants which expose all commands, including connection-level ones, i.e. the ones that
    * access or modify Redis connection state.
    */
  object Connection extends ExecutedApis {
    type RequiredExecutor = RedisConnectionExecutor

    case class Async[S <: RedisSerialization](
      serialization: S,
      executor: RequiredExecutor,
      execConfig: ExecutionConfig
    ) extends BaseAsync[S] with RedisRecoverableConnectionApi
    object Async extends VariantCompanion[Async]

    case class Monix[S <: RedisSerialization](
      serialization: S,
      executor: RequiredExecutor,
      execConfig: ExecutionConfig
    ) extends BaseMonix[S] with RedisRecoverableConnectionApi
    object Monix extends VariantCompanion[Monix]

    case class Blocking[S <: RedisSerialization](
      serialization: S,
      executor: RequiredExecutor,
      execConfig: ExecutionConfig
    ) extends BaseBlocking[S] with RedisRecoverableConnectionApi
    object Blocking extends VariantCompanion[Blocking]
  }
}

abstract class AbstractRedisApi[S <: RedisSerialization] extends ApiSubset {
  type Self[S0 <: RedisSerialization] <: AbstractRedisApi[S0]
  def withSerialization[S0 <: RedisSerialization](ser: S0): Self[S0]

  type WithKey[K] = Self[serialization.WithKey[K]]
  type WithField[F] = Self[serialization.WithField[F]]
  type WithValue[V] = Self[serialization.WithValue[V]]
  type WithRecord[R] = Self[serialization.WithRecord[R]]

  final def keyType[K: RedisDataCodec]: WithKey[K] =
    withSerialization(serialization.keyType[K])

  final def fieldType[F: RedisDataCodec]: WithField[F] =
    withSerialization(serialization.fieldType[F])

  final def valueType[V: RedisDataCodec]: WithValue[V] =
    withSerialization(serialization.valueType[V])

  final def recordType[R: RedisRecordCodec]: WithRecord[R] =
    withSerialization(serialization.recordType[R])
}

abstract class ExecutedApis {
  type RequiredExecutor <: RedisExecutor

  type Async[S <: RedisSerialization] <: BaseAsync[S]
  val Async: VariantCompanion[Async]

  type Monix[S <: RedisSerialization] <: BaseMonix[S]
  val Monix: VariantCompanion[Monix]

  type Blocking[S <: RedisSerialization] <: BaseBlocking[S]
  val Blocking: VariantCompanion[Blocking]

  abstract class VariantCompanion[Variant[S <: RedisSerialization] <: AbstractRedisApi[S]] {
    def apply[S <: RedisSerialization](
      serialization: S,
      executor: RequiredExecutor,
      execConfig: ExecutionConfig
    ): Variant[S]

    def apply[S <: RedisSerialization : ValueOf](
      executor: RequiredExecutor,
      execConfig: ExecutionConfig = ExecutionConfig.Default
    ): Variant[S] = apply(ValueOf[S], executor, execConfig)

    type StringTyped = Variant[RedisSerialization.Strings.type]

    def StringTyped(
      exec: RequiredExecutor, config: ExecutionConfig = ExecutionConfig.Default
    ): StringTyped = apply(RedisSerialization.Strings, exec, config)

    type BinaryTyped = Variant[RedisSerialization.ByteStrings.type]

    def BinaryTyped(
      exec: RequiredExecutor, config: ExecutionConfig = ExecutionConfig.Default
    ): BinaryTyped = apply(RedisSerialization.ByteStrings, exec, config)
  }

  protected abstract class BaseAsync[S <: RedisSerialization] extends AbstractRedisApi[S] with RedisAsyncApi {
    def executor: RequiredExecutor
    def execConfig: ExecutionConfig

    type Self[S0 <: RedisSerialization] = Async[S0]
    def withSerialization[S0 <: RedisSerialization](ser: S0): Self[S0] = Async(ser, executor, execConfig)
  }

  protected abstract class BaseMonix[S <: RedisSerialization] extends AbstractRedisApi[S] with RedisMonixApi {
    def executor: RequiredExecutor
    def execConfig: ExecutionConfig

    type Self[S0 <: RedisSerialization] = Monix[S0]
    def withSerialization[S0 <: RedisSerialization](ser: S0): Self[S0] = Monix(ser, executor, execConfig)
  }

  protected abstract class BaseBlocking[S <: RedisSerialization] extends AbstractRedisApi[S] with RedisBlockingApi {
    def executor: RequiredExecutor
    def execConfig: ExecutionConfig

    type Self[S0 <: RedisSerialization] = Blocking[S0]
    def withSerialization[S0 <: RedisSerialization](ser: S0): Self[S0] = Blocking(ser, executor, execConfig)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy