-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add storage client using Redisson (#108)
- Loading branch information
1 parent
ab599b1
commit 1b76d54
Showing
5 changed files
with
167 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
storage-redisson/src/main/scala/com/devsisters/shardcake/RedisConfig.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.devsisters.shardcake | ||
|
||
/** | ||
* The configuration for the Redis storage implementation. | ||
* @param assignmentsKey the key to store shard assignments | ||
* @param podsKey the key to store registered pods | ||
*/ | ||
case class RedisConfig(assignmentsKey: String, podsKey: String) | ||
|
||
object RedisConfig { | ||
val default: RedisConfig = RedisConfig(assignmentsKey = "shard_assignments", podsKey = "pods") | ||
} |
72 changes: 72 additions & 0 deletions
72
storage-redisson/src/main/scala/com/devsisters/shardcake/StorageRedis.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package com.devsisters.shardcake | ||
|
||
import scala.jdk.CollectionConverters._ | ||
|
||
import com.devsisters.shardcake.interfaces.Storage | ||
import org.redisson.api.RedissonClient | ||
import org.redisson.api.listener.MessageListener | ||
import org.redisson.client.codec.StringCodec | ||
import zio.stream.ZStream | ||
import zio.{ Queue, Task, Unsafe, ZIO, ZLayer } | ||
|
||
object StorageRedis { | ||
|
||
/** | ||
* A layer that returns a Storage implementation using Redis | ||
*/ | ||
val live: ZLayer[RedissonClient with RedisConfig, Nothing, Storage] = | ||
ZLayer { | ||
for { | ||
config <- ZIO.service[RedisConfig] | ||
redisClient <- ZIO.service[RedissonClient] | ||
assignmentsMap = redisClient.getMap[String, String](config.assignmentsKey) | ||
podsMap = redisClient.getMap[String, String](config.podsKey) | ||
assignmentsTopic = redisClient.getTopic(config.assignmentsKey, StringCodec.INSTANCE) | ||
} yield new Storage { | ||
def getAssignments: Task[Map[ShardId, Option[PodAddress]]] = | ||
ZIO | ||
.fromCompletionStage(assignmentsMap.readAllEntrySetAsync()) | ||
.map( | ||
_.asScala | ||
.flatMap(entry => | ||
entry.getKey.toIntOption.map( | ||
_ -> (if (entry.getValue.isEmpty) None | ||
else PodAddress(entry.getValue)) | ||
) | ||
) | ||
.toMap | ||
) | ||
def saveAssignments(assignments: Map[ShardId, Option[PodAddress]]): Task[Unit] = | ||
ZIO.fromCompletionStage(assignmentsMap.putAllAsync(assignments.map { case (k, v) => | ||
k.toString -> v.fold("")(_.toString) | ||
}.asJava)) *> | ||
ZIO.fromCompletionStage(assignmentsTopic.publishAsync("ping")).unit | ||
def assignmentsStream: ZStream[Any, Throwable, Map[ShardId, Option[PodAddress]]] = | ||
ZStream.unwrap { | ||
for { | ||
queue <- Queue.unbounded[String] | ||
runtime <- ZIO.runtime[Any] | ||
_ <- ZIO.fromCompletionStage( | ||
assignmentsTopic.addListenerAsync( | ||
classOf[String], | ||
new MessageListener[String] { | ||
def onMessage(channel: CharSequence, msg: String): Unit = | ||
Unsafe.unsafe(implicit unsafe => runtime.unsafe.run(queue.offer(msg))) | ||
} | ||
) | ||
) | ||
} yield ZStream.fromQueueWithShutdown(queue).mapZIO(_ => getAssignments) | ||
} | ||
def getPods: Task[Map[PodAddress, Pod]] = | ||
ZIO | ||
.fromCompletionStage(podsMap.readAllEntrySetAsync()) | ||
.map( | ||
_.asScala | ||
.flatMap(entry => PodAddress(entry.getKey).map(address => address -> Pod(address, entry.getValue))) | ||
.toMap | ||
) | ||
def savePods(pods: Map[PodAddress, Pod]): Task[Unit] = | ||
ZIO.fromCompletionStage(podsMap.putAllAsync(pods.map { case (k, v) => k.toString -> v.version }.asJava)).unit | ||
} | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
storage-redisson/src/test/scala/com/devsisters/shardcake/StorageRedisSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package com.devsisters.shardcake | ||
|
||
import com.devsisters.shardcake.interfaces.Storage | ||
import com.dimafeng.testcontainers.GenericContainer | ||
import org.redisson.Redisson | ||
import org.redisson.config.{ Config => RedissonConfig } | ||
import org.redisson.api.RedissonClient | ||
import zio.Clock.ClockLive | ||
import zio._ | ||
import zio.stream.ZStream | ||
import zio.test.TestAspect.sequential | ||
import zio.test._ | ||
|
||
object StorageRedisSpec extends ZIOSpecDefault { | ||
val container: ZLayer[Any, Nothing, GenericContainer] = | ||
ZLayer.scoped { | ||
ZIO.acquireRelease { | ||
ZIO.attemptBlocking { | ||
val container = new GenericContainer(dockerImage = "redis:6.2.5", exposedPorts = Seq(6379)) | ||
container.start() | ||
container | ||
}.orDie | ||
}(container => ZIO.attemptBlocking(container.stop()).orDie) | ||
} | ||
|
||
val redis: ZLayer[GenericContainer, Throwable, RedissonClient] = | ||
ZLayer { | ||
for { | ||
container <- ZIO.service[GenericContainer] | ||
uri = s"redis://foobared@${container.host}:${container.mappedPort(container.exposedPorts.head)}" | ||
redissonConfig = new RedissonConfig() | ||
_ = redissonConfig.useSingleServer().setAddress(uri) | ||
client = Redisson.create(redissonConfig) | ||
} yield client | ||
} | ||
|
||
def spec: Spec[TestEnvironment with Scope, Any] = | ||
suite("StorageRedisSpec")( | ||
test("save and get pods") { | ||
val expected = List(Pod(PodAddress("host1", 1), "1.0.0"), Pod(PodAddress("host2", 2), "2.0.0")) | ||
.map(p => p.address -> p) | ||
.toMap | ||
for { | ||
_ <- ZIO.serviceWithZIO[Storage](_.savePods(expected)) | ||
actual <- ZIO.serviceWithZIO[Storage](_.getPods) | ||
} yield assertTrue(expected == actual) | ||
}, | ||
test("save and get assignments") { | ||
val expected = Map(1 -> Some(PodAddress("host1", 1)), 2 -> None) | ||
for { | ||
_ <- ZIO.serviceWithZIO[Storage](_.saveAssignments(expected)) | ||
actual <- ZIO.serviceWithZIO[Storage](_.getAssignments) | ||
} yield assertTrue(expected == actual) | ||
}, | ||
test("assignments stream") { | ||
val expected = Map(1 -> Some(PodAddress("host1", 1)), 2 -> None) | ||
for { | ||
p <- Promise.make[Nothing, Map[Int, Option[PodAddress]]] | ||
_ <- ZStream.serviceWithStream[Storage](_.assignmentsStream).runForeach(p.succeed(_)).fork | ||
_ <- ClockLive.sleep(1 second) | ||
_ <- ZIO.serviceWithZIO[Storage](_.saveAssignments(expected)) | ||
actual <- p.await | ||
} yield assertTrue(expected == actual) | ||
} | ||
).provideLayerShared( | ||
container >>> redis ++ ZLayer.succeed(RedisConfig.default) >>> StorageRedis.live | ||
) @@ sequential | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters