Skip to content

Commit

Permalink
Migration to Pekko and Play 3.0 (#278)
Browse files Browse the repository at this point in the history
* Migrate from Akka to Pekko

Version 3 of the Scala Play Framework no longer ships with Akka. See release note: https://www.playframework.com/documentation/3.0.x/Highlights30

When combining version 3 of the Scala Play Framework with the Redis Cache module it requires Akka leading to compatibility issues

* Missed refactored naming from 'Akka' to 'Pekko'

* Migration to Pekko

---------

Co-authored-by: TomJKing <[email protected]>
  • Loading branch information
KarelCemus and TomJKing authored Feb 4, 2024
1 parent 4c09acb commit aab6bff
Show file tree
Hide file tree
Showing 39 changed files with 107 additions and 110 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@

## Changelog

### [:link: 4.0.0](https://github.com/KarelCemus/play-redis/tree/4.0.0-M1)

Migration to Pekko and Play 3.0, thanks to @TomJKing for help in [#272](https://github.com/KarelCemus/play-redis/pull/272),
finished in [#278](https://github.com/KarelCemus/play-redis/pull/278)

### [:link: 3.0.0](https://github.com/KarelCemus/play-redis/tree/3.0.0-M1)

Updated to Play `2.9.0` and dropped `Scala 2.12` since it was discontinued from the Play framework.
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ operations. Besides the basic methods such as `get`, `set`
and `remove`, it provides more convenient methods such as
`expire`, `exists`, `invalidate` and much more.

The implementation builds on the top of Akka actor system,
The implementation builds on the top of Pekko actor system,
it is **completely non-blocking and asynchronous** under
the hood, though it also provides blocking APIs to ease
the use. Furthermore, the library supports several configuration
Expand All @@ -51,9 +51,9 @@ as well as on your premise.
- support of [standalone, cluster,](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#standalone-vs-cluster)
[aws-cluster,](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#aws-cluster)
and [sentinel modes](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#sentinel)
- build on the top of Akka actors and serializers, [agnostic to the serialization mechanism](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#limitation-of-data-serialization)
- build on the top of Pekko actors and serializers, [agnostic to the serialization mechanism](https://github.com/KarelCemus/play-redis/blob/3.0.0-M3/doc/20-configuration.md#limitation-of-data-serialization)
- for simplicity, it uses deprecated Java serialization by default
- it is recommended to use [Kryo library](https://github.com/romix/akka-kryo-serialization) or any other mechanism
- it is recommended to use [Kryo library](https://github.com/romix/akka-kryo-serialization) or any other mechanism


## Provided APIs
Expand Down
8 changes: 4 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ crossScalaVersions := Seq("2.13.12") //, "3.3.0"

scalaVersion := crossScalaVersions.value.head

playVersion := "2.9.0"
playVersion := "3.0.0"

libraryDependencies ++= Seq(
// play framework cache API
"com.typesafe.play" %% "play-cache" % playVersion.value % Provided,
"org.playframework" %% "play-cache" % playVersion.value % Provided,
// redis connector
"io.github.rediscala" %% "rediscala" % "1.14.0-akka",
"io.github.rediscala" %% "rediscala" % "1.14.0-pekko",
// test framework with mockito extension
"org.scalatest" %% "scalatest" % "3.2.17" % Test,
"org.scalamock" %% "scalamock" % "5.2.0" % Test,
// test module for play framework
"com.typesafe.play" %% "play-test" % playVersion.value % Test,
"org.playframework" %% "play-test" % playVersion.value % Test,
// to run integration tests
"com.dimafeng" %% "testcontainers-scala-core" % "0.41.2" % Test,
)
Expand Down
20 changes: 10 additions & 10 deletions doc/20-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,8 @@ it uses `JavaSerializer` by default.
Since Akka 2.4.1, default `JavaSerializer` is [officially considered inefficient for production use](https://github.com/akka/akka/pull/18552).
Nevertheless, to keep things simple, play-redis **still uses this inefficient serializer NOT to enforce** any serialization
library to end users. Although, it recommends [kryo serializer](https://github.com/romix/akka-kryo-serialization) claiming
great performance and small output stream. Any serialization library can be smoothly connected through Akka
configuration, see the [official Akka documentation](https://doc.akka.io/docs/akka/current/scala/serialization.html).
great performance and small output stream. Any serialization library can be smoothly connected through Pekko
configuration, see the [official Pekko documentation](https://pekko.apache.org/docs/pekko/current/serialization.html).


## Overview
Expand All @@ -366,11 +366,11 @@ configuration, see the [official Akka documentation](https://doc.akka.io/docs/ak

### Instance-specific (can be locally overridden)

| Key | Type | Default | Description |
|----------------------------------------------------------|---------:|--------------------------------:|-------------------------------------|
| [play.cache.redis.source](#standalone-vs-cluster) | String | `standalone` | Defines the source of the configuration. Accepted values are `standalone`, `cluster`, `connection-string`, and `custom` |
| [play.cache.redis.sync-timeout](#timeout) | Duration | `1s` | conversion timeout applied by `SyncAPI` to convert `Future[T]` to `T`|
| [play.cache.redis.redis-timeout](#timeout) | Duration | `null` | waiting for the response from redis server |
| [play.cache.redis.prefix](#namespace-prefix) | String | `null` | optional namespace, i.e., key prefix |
| play.cache.redis.dispatcher | String | `akka.actor.default-dispatcher` | Akka actor |
| [play.cache.redis.recovery](#recovery-policy) | String | `log-and-default` | Defines behavior when command execution fails. For accepted values and more see |
| Key | Type | Default | Description |
|----------------------------------------------------------|---------:|-------------------------------------:|-------------------------------------------------------------------------------------------------------------------------|
| [play.cache.redis.source](#standalone-vs-cluster) | String | `standalone` | Defines the source of the configuration. Accepted values are `standalone`, `cluster`, `connection-string`, and `custom` |
| [play.cache.redis.sync-timeout](#timeout) | Duration | `1s` | conversion timeout applied by `SyncAPI` to convert `Future[T]` to `T` |
| [play.cache.redis.redis-timeout](#timeout) | Duration | `null` | waiting for the response from redis server |
| [play.cache.redis.prefix](#namespace-prefix) | String | `null` | optional namespace, i.e., key prefix |
| play.cache.redis.dispatcher | String | `pekko.actor.default-dispatcher` | Pekko actor |
| [play.cache.redis.recovery](#recovery-policy) | String | `log-and-default` | Defines behavior when command execution fails. For accepted values and more see |
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.9.7
sbt.version=1.9.8
2 changes: 1 addition & 1 deletion src/main/java/play/cache/redis/AsyncCacheApi.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package play.cache.redis;

import akka.Done;
import org.apache.pekko.Done;

import java.util.Arrays;
import java.util.List;
Expand Down
8 changes: 4 additions & 4 deletions src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,13 @@ play.cache.redis {
#
prefix: null

# akka dispatcher
# pekko dispatcher
#
# note: this is global definition, can be locally overriden for each
# cache instance. To do so, redefine this property
# under 'play.cache.redis.instances.instance-name.this-property'.
#
dispatcher: akka.actor.default-dispatcher
dispatcher: pekko.actor.default-dispatcher

# invocation policy applies in methods `getOrElse`. It determines
# whether to wait until the `set` completes or return eagerly the
Expand Down Expand Up @@ -288,9 +288,9 @@ play.cache.redis {
}

# ==================
# Akka configuration
# Pekko configuration
# ==================
akka {
pekko {

actor {
serialization-bindings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import play.api.{Configuration, Environment}
* from configuration package</p>
*/
trait RedisCacheComponents {
implicit def actorSystem: akka.actor.ActorSystem
implicit def actorSystem: org.apache.pekko.actor.ActorSystem
implicit def applicationLifecycle: ApplicationLifecycle
def configuration: Configuration
def environment: Environment
Expand All @@ -27,14 +27,14 @@ trait RedisCacheComponents {
/** override this for providing a custom redis instance resolver */
implicit def redisInstanceResolver: RedisInstanceResolver = emptyInstanceResolver

private lazy val akkaSerializer: connector.AkkaSerializer = new connector.AkkaSerializerProvider().get
private lazy val pekkoSerializer: connector.PekkoSerializer = new connector.PekkoSerializerProvider().get

private lazy val manager = configuration.get("play.cache.redis")(play.api.cache.redis.configuration.RedisInstanceManager)

/** translates the cache name into the configuration */
private def redisInstance(name: String)(implicit resolver: RedisInstanceResolver): RedisInstance = manager.instanceOf(name).resolved(resolver)

private def cacheApi(instance: RedisInstance): impl.RedisCaches = new impl.RedisCachesProvider(instance, akkaSerializer, environment).get
private def cacheApi(instance: RedisInstance): impl.RedisCaches = new impl.RedisCachesProvider(instance, pekkoSerializer, environment).get

def cacheApi(name: String)(implicit resolver: RedisInstanceResolver): RedisCaches = cacheApi(redisInstance(name)(resolver))
}
6 changes: 3 additions & 3 deletions src/main/scala/play/api/cache/redis/RedisCacheModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class RedisCacheModule extends Module {
// common settings
val commons = Seq(
// bind serializer
bind[connector.AkkaSerializer].toProvider[connector.AkkaSerializerProvider],
bind[connector.PekkoSerializer].toProvider[connector.PekkoSerializerProvider],
bind[configuration.RedisInstanceResolver].to[GuiceRedisInstanceResolver],
)
// bind recovery resolver
Expand Down Expand Up @@ -108,10 +108,10 @@ class GuiceRedisCacheProvider(instance: RedisInstanceProvider) extends Provider[

override lazy val get: RedisCaches = new impl.RedisCachesProvider(
instance = instance.resolved(bind[configuration.RedisInstanceResolver]),
serializer = bind[connector.AkkaSerializer],
serializer = bind[connector.PekkoSerializer],
environment = bind[play.api.Environment],
)(
system = bind[akka.actor.ActorSystem],
system = bind[org.apache.pekko.actor.ActorSystem],
lifecycle = bind[ApplicationLifecycle],
recovery = bind[RecoveryPolicyResolver],
).get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import play.api.ConfigLoader

/**
* Configures non-connection related settings of redis instance, e.g.,
* synchronization timeout, Akka dispatcher, and recovery policy.
* synchronization timeout, Pekko dispatcher, and recovery policy.
*/
trait RedisSettings {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package play.api.cache.redis.connector

import akka.actor.ActorSystem
import akka.serialization._
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.serialization.{Serialization, SerializationExtension}
import play.api.cache.redis._

import java.util.Base64
Expand All @@ -13,7 +13,7 @@ import scala.util._
* Provides a encode and decode methods to serialize objects into strings and
* vise versa.
*/
trait AkkaSerializer {
trait PekkoSerializer {

/**
* Method accepts a value to be serialized into the string. Based on the
Expand Down Expand Up @@ -44,20 +44,20 @@ trait AkkaSerializer {
}

/**
* Akka encoder provides implementation of serialization using Akka serializer.
* The implementation considers all primitives, nulls, and refs. This enables
* us to use Akka settings to modify serializer mapping and use different
* serializers for different objects.
* Pekko encoder provides implementation of serialization using Pekko
* serializer. The implementation considers all primitives, nulls, and refs.
* This enables us to use Pekko settings to modify serializer mapping and use
* different serializers for different objects.
*/
private[connector] class AkkaEncoder(serializer: Serialization) {
private[connector] class PekkoEncoder(serializer: Serialization) {

/** Unsafe method encoding the given value into a string */
def encode(value: Any): String = value match {
// null is special case
case null => unsupported("Null is not supported by the redis cache connector.")
// AnyVal is not supported by default, have to be implemented manually; also basic types are processed as primitives
case primitive if isPrimitive(primitive) => primitive.toString
// AnyRef is supported by Akka serializers, but it does not consider classTag, thus it is done manually
// AnyRef is supported by Pekko serializers, but it does not consider classTag, thus it is done manually
case anyRef: AnyRef => anyRefToString(anyRef)
// $COVERAGE-OFF$
// if no of the cases above matches, throw an exception
Expand All @@ -84,12 +84,12 @@ private[connector] class AkkaEncoder(serializer: Serialization) {
}

/**
* Akka decoder provides implementation of deserialization using Akka
* Pekko decoder provides implementation of deserialization using Pekko
* serializer. The implementation considers all primitives, nulls, and refs.
* This enables us to use Akka settings to modify serializer mapping and use
* This enables us to use Pekko settings to modify serializer mapping and use
* different serializers for different objects.
*/
private[connector] class AkkaDecoder(serializer: Serialization) {
private[connector] class PekkoDecoder(serializer: Serialization) {

import scala.reflect.{ClassTag => Scala}

Expand Down Expand Up @@ -139,19 +139,19 @@ private[connector] class AkkaDecoder(serializer: Serialization) {
}

@Singleton
private[connector] class AkkaSerializerImpl @Inject() (system: ActorSystem) extends AkkaSerializer {
private[connector] class PekkoSerializerImpl @Inject() (system: ActorSystem) extends PekkoSerializer {

/**
* serializer dispatcher used to serialize the objects into bytes; the
* instance is retrieved from Akka based on its configuration
* instance is retrieved from Pekko based on its configuration
*/
protected val serializer: Serialization = SerializationExtension(system)

/** value serializer based on Akka serialization */
private val encoder = new AkkaEncoder(serializer)
/** value serializer based on Pekko serialization */
private val encoder = new PekkoEncoder(serializer)

/** value decoder based on Akka serialization */
private val decoder = new AkkaDecoder(serializer)
/** value decoder based on Pekko serialization */
private val decoder = new PekkoDecoder(serializer)

/**
* Method accepts a value to be serialized into the string. Based on the
Expand Down Expand Up @@ -224,6 +224,6 @@ private[connector] object JavaClassTag {
val String: ClassTag[String] = ClassTag(classOf[String])
}

class AkkaSerializerProvider @Inject() (implicit system: ActorSystem) extends Provider[AkkaSerializer] {
lazy val get = new AkkaSerializerImpl(system)
class PekkoSerializerProvider @Inject() (implicit system: ActorSystem) extends Provider[PekkoSerializer] {
lazy val get = new PekkoSerializerImpl(system)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package play.api.cache.redis.connector

import akka.actor.{ActorSystem, Scheduler}
import org.apache.pekko.actor.{ActorSystem, Scheduler}
import play.api.Logger
import play.api.cache.redis.configuration._
import play.api.inject.ApplicationLifecycle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import scala.reflect.ClassTag
* @param redis
* implementation of the commands
*/
private[connector] class RedisConnectorImpl(serializer: AkkaSerializer, redis: RedisCommands)(implicit runtime: RedisRuntime) extends RedisConnector {
private[connector] class RedisConnectorImpl(serializer: PekkoSerializer, redis: RedisCommands)(implicit runtime: RedisRuntime) extends RedisConnector {

import ExpectedFuture._

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package play.api.cache.redis.connector

import akka.actor.ActorSystem
import org.apache.pekko.actor.ActorSystem
import play.api.cache.redis._
import play.api.inject.ApplicationLifecycle

import javax.inject.Provider

/** Provides an instance of named redis connector */
private[redis] class RedisConnectorProvider(instance: RedisInstance, serializer: AkkaSerializer)(implicit system: ActorSystem, lifecycle: ApplicationLifecycle, runtime: RedisRuntime) extends Provider[RedisConnector] {
private[redis] class RedisConnectorProvider(instance: RedisInstance, serializer: PekkoSerializer)(implicit system: ActorSystem, lifecycle: ApplicationLifecycle, runtime: RedisRuntime) extends Provider[RedisConnector] {

private[connector] lazy val commands = new RedisCommandsProvider(instance).get

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package play.api.cache.redis.connector

import akka.actor.Scheduler
import akka.pattern.after
import org.apache.pekko.actor.Scheduler
import org.apache.pekko.pattern.after
import redis._

import scala.concurrent.duration._
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/play/api/cache/redis/impl/Builders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ private object Builders {

import dsl._
import play.api.cache.redis._
import akka.pattern.AskTimeoutException
import org.apache.pekko.pattern.AskTimeoutException

trait ResultBuilder[Result[_]] {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package play.api.cache.redis.impl

import akka.Done
import org.apache.pekko.Done
import play.api.Environment
import play.api.cache.redis._

import scala.concurrent.{ExecutionContext, Future}
import scala.reflect.ClassTag

private[impl] object JavaCompatibility extends JavaCompatibilityBase {
import scala.compat.java8.{FutureConverters, OptionConverters}
import scala.jdk.javaapi.{FutureConverters, OptionConverters}

type CompletionStage[T] = java.util.concurrent.CompletionStage[T]
type Callable[T] = java.util.concurrent.Callable[T]
Expand All @@ -28,7 +28,7 @@ private[impl] object JavaCompatibility extends JavaCompatibilityBase {
}

implicit class Java8Stage[T](private val future: Future[T]) extends AnyVal {
@inline def asJava: CompletionStage[T] = FutureConverters.toJava(future)
@inline def asJava: CompletionStage[T] = FutureConverters.asJava(future)
@inline def asDone(implicit ec: ExecutionContext): Future[Done] = future.map(_ => Done)
}

Expand All @@ -41,7 +41,7 @@ private[impl] object JavaCompatibility extends JavaCompatibilityBase {
}

implicit class ScalaCompatibility[T](private val future: CompletionStage[T]) extends AnyVal {
@inline def asScala: Future[T] = FutureConverters.toScala(future)
@inline def asScala: Future[T] = FutureConverters.asScala(future)
}

implicit class RichFuture(private val future: Future.type) extends AnyVal {
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/play/api/cache/redis/impl/RedisCaches.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package play.api.cache.redis.impl

import akka.actor.ActorSystem
import org.apache.pekko.actor.ActorSystem
import play.api.Environment
import play.api.cache.redis._
import play.api.inject.ApplicationLifecycle
Expand All @@ -21,7 +21,7 @@ trait RedisCaches {
def javaAsync: play.cache.redis.AsyncCacheApi
}

private[redis] class RedisCachesProvider(instance: RedisInstance, serializer: connector.AkkaSerializer, environment: Environment)(implicit system: ActorSystem, lifecycle: ApplicationLifecycle, recovery: RecoveryPolicyResolver) extends Provider[RedisCaches] {
private[redis] class RedisCachesProvider(instance: RedisInstance, serializer: connector.PekkoSerializer, environment: Environment)(implicit system: ActorSystem, lifecycle: ApplicationLifecycle, recovery: RecoveryPolicyResolver) extends Provider[RedisCaches] {
import RedisRuntime._

implicit private lazy val runtime: RedisRuntime = RedisRuntime(instance, instance.recovery, instance.invocationPolicy, instance.prefix)(system)
Expand Down
Loading

0 comments on commit aab6bff

Please sign in to comment.