GithubHelp home page GithubHelp logo

laserdisc-io / laserdisc Goto Github PK

View Code? Open in Web Editor NEW
94.0 7.0 14.0 1.05 MB

A Future-free Fs2 native pure FP Redis client

Home Page: http://laserdisc.io

License: MIT License

Scala 100.00%
redis redis-client scala fs2 mtl functional-programming fp

laserdisc's Introduction

LaserDisc

CI Release Known Vulnerabilities Join the chat at https://gitter.im/laserdisc-io/laserdisc License: MIT Scala Steward badge
Maven Central Maven Central Maven Central Maven Central Maven Central Maven Central
Scala.js

LaserDisc is a(nother) Scala driver for Redis, written in Scala from the ground up.

It differentiates itself from the others for having a core layer, which is made up of all the supported Redis commands and the Redis Serialization Protocol (RESP), that is strongly typed and which makes heavy use of shapeless and refined to achieve this. It also provides an implementation of RESP built using scodec.

On top of this, one or more clients can be implemented. The only one currently available out of the box is built using fs2/cats effect but more competing implementations can be added with limited effort. This implementation has found great inspiration from the excellent fs2-kafka library.

What's there:

  • Codecs for Redis' RESP wire format
  • Fully-fledged protocol encapsulating request/response pairs for (almost) all Redis commands
  • Initial version of single-node Redis client. Lots of improvements needed
  • Minimal CLI

What's missing:

  • Everything else :)

Note: the library is still evolving and more features will be added in the future. Even if the binary compatibility will not be guaranteed until version 1.0.0, the semantic versioning strategy will be observed in the process.

Why the name

Two reasons:

  1. "A LaserDisc" is an anagram for "Scala Redis"
  2. LaserDiscs were invented in 1978 (same year I was born) and were so cool (and foundational, more on Wikipedia)

Getting Started

LaserDisc is currently available for Scala 2.12 and 2.13 on the JVM.

Its core (protocol commands and RESP wire format) is also available for Scala.JS.

To add LaserDisc as a dependency to your project just add the following to your build.sbt:

libraryDependencies += "io.laserdisc" %% "laserdisc-fs2" % latestVersion

If you only need protocols (i.e. Redis commands and RESP wire format), you may simply add:

libraryDependencies += "io.laserdisc" %% "laserdisc-core" % latestVersion

Interoperability modules

Support for existing libraries is available via dedicated dependencies.

When an io.circe.Decoder[A] and a io.circe.Encoder[A] are implicilty available, instances of Show[A] and Read[Bulk, A] can be derived for free, just add the following in your build.sbt:

libraryDependecies += "io.laserdisc" %% "laserdisc-circe" % latestVersion 

then, to make use of them, at call site it should be sufficient to just:

import laserdisc.interop.circe._

Note: the derived Show[A] instance uses the most compact string representation of the JSON data structure, i.e. no spacing is used

Example usage

With a running Redis instance on localhost:6379 try running the following:

import cats.effect.{IO, IOApp}
import log.effect.LogWriter
import log.effect.fs2.SyncLogWriter
import cats.syntax.flatMap._

object Main extends IOApp.Simple {

  import laserdisc._
  import laserdisc.all._
  import laserdisc.auto._
  import laserdisc.fs2._

  def redisTest(implicit log: LogWriter[IO]): IO[Unit] =
    RedisClient[IO].to("localhost", 6379).use { client =>
      client.send(
        set("a", 23),
        set("b", 55),
        get[PosInt]("b"),
        get[PosInt]("a")
      ) >>= {
        case (Right(OK), Right(OK), Right(Some(getOfb)), Right(Some(getOfa))) if getOfb.value == 55 && getOfa.value == 23 =>
          log info "yay!"
        case other =>
          log.error(s"something went terribly wrong $other") >>
            IO.raiseError(new RuntimeException("boom"))
      }
    }

  override final def run: IO[Unit] =
    redisTest(SyncLogWriter.consoleLog[IO])
}

This should produce an output similar to the following one:

[debug] - [io-compute-3] Starting connection
[info] - [io-compute-4] Connected to server localhost:6379
[trace] - [io-compute-4] sending Arr(Bulk(SET),Bulk(a),Bulk(23))
[trace] - [io-compute-3] receiving Str(OK)
[trace] - [io-compute-1] sending Arr(Bulk(SET),Bulk(b),Bulk(55))
[trace] - [io-compute-4] receiving Str(OK)
[trace] - [io-compute-0] sending Arr(Bulk(GET),Bulk(b))
[trace] - [io-compute-5] receiving Bulk(55)
[trace] - [io-compute-0] sending Arr(Bulk(GET),Bulk(a))
[trace] - [io-compute-0] receiving Bulk(23)
[info] - [io-compute-5] yay!
[debug] - [io-compute-5] Shutting down connection
[debug] - [io-compute-5] Shutdown complete
[info] - [io-compute-0] Connection terminated: No issues

Dependencies

Shapeless
Maven Central 2.3.10

Fs2 Log Effect Laserdisc Core
Maven Central 3.4.0 0.16.3 Maven Central

Circe Laserdisc Core
Maven Central 0.14.3 Maven Central

Support

YourKit Image

This project is supported by YourKit with monitoring and profiling Tools. YourKit supports open source with innovative and intelligent tools for monitoring and profiling Java and .NET applications. YourKit is the creator of YourKit Java Profiler, YourKit .NET Profiler, and YourKit YouMonitor.

License

LaserDisc is licensed under the MIT License (the "License"); you may not use this software except in compliance with the License.

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

laserdisc's People

Contributors

agustafson avatar barambani avatar barryoneill avatar codacy-badger avatar gitter-badger avatar gurghet avatar nashid avatar scala-steward avatar sirocchj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

laserdisc's Issues

Move to Circle CI

Currently Travis doesn't provide openjdk 8 out of the box on bionic (see #170). It's probably time to consider Circle CI (or even Github Actions) for the builds and investigate if it can solve the problem.

CLI and connection handling issues

sbt --supershell=false or it will incur in sbt/sbt#5063 (thanks @barambani for finding this out)

However:

  • issuing quit does not terminate connection client-side (always been the case, not a new issue)
    localhost:6379> quit
    <<< OK - [5.06ms]
    localhost:6379>
    
  • Ctrl-C reports potential leak
    localhost:6379> ^C
    [warn] Canceling execution...
    [error] Total time: 124 s (02:04), completed 11-Nov-2019 08:45:47
    sbt:laserdisc> [warn] Thread[ioapp-compute-2,5,run-main-group-0] loading cats.syntax.EitherIdOps$ after test or run has completed. This is a likely resource leak
    2019-11-11 08:45:54,882 shutdown-hooks-run-all ERROR No Log4j 2 configuration file found. 
    Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging. 
    See https://logging.apache.org/log4j/2.x/manual/configuration.html for instructions on how to configure Log4j 2
    

Promote CLI to independent module

CLI.scala should be moved out of laserdisc-fs2 and put into a new laserdisc-cli module (dependent on laserdisc-fs2)
Tests could be added too (needs refactoring)

Add laws verification for Read

Read has primitives that can qualify it to be at least FlatMap and Functor (Covariant, Contravariant and Invariant) and may be others. Also with the addition of a lawful pure it could also be a Monad.
It would be good to add checks to validate the laws for those type classes.

Add pub/sub infrastructure

Plan is:

  • add all required simple req/res commands as per Redis protocol
  • add fs2 based impl of subscriptions

Unsafe API

In many circumstances, as much as we'd like it to be, creating a safe protocol that is verifiable at compile time is just not possible.

One example would be when the Key needed in a get has to be constructed at runtime (say by appending together other string only available at runtime by consuming some external source).

In all these circumstances we may feel that the burden of having to prove the compiler we have valid Keys is just too big and what we really wish for is a good ol' RuntimeException, something that would just make us feel at peace with the universe...

I'm thinking something like:

trait UnsafeStringP { self: StringP =>
  final def get[A](key: String)(
      implicit ev: NonNullBulkString ==> A
  ): Protocol.Aux[Option[A]] = {
    require(key.nonEmpty, "why are you doing this to me now")
    self.get(Key.unsafeFrom(key))
  }
}

I'd also love to see this properly packaged under some very comforting namespace like hell or blinded (which I personally like a lot 'cause it plays well with laserdisc, i.e. import laserdisc.blinded._)

Improve deserialization error messages

Right now, if there's a deserialization issue doing a GET on the value, laserdisc suppresses the underlying error. E.g. if the value for 'someKey' cannot be deserialized:

redisClient.send1(strings.get[MyBean](someKey))..

The error message is of the form:

Error(RESP type(s) matched but failed to deserialize correctly: BulkString("{\"my\":\"bad\",\"json\":\"here\""))

It would be very helpful to propagate the deserialization error (or even indicate the target bean type).

Consider removing sbt-release-early

sbt-release-early keeps failing in closing the repository or releasing it (a sample error below). We could consider replacing it with sbt-release that's pretty reliable until something better is available.

[info] Nexus repository URL: https://oss.sonatype.org/service/local
[info] sonatypeProfileName = io.laserdisc
[info] Reading staging repository profiles...
[error] Multiple repositories are found:
[error] [iolaserdisc-1086] status:open, profile:io.laserdisc(4c27934a30ac28) description: Implicitly created (auto staging).
[error] [iolaserdisc-1087] status:open, profile:io.laserdisc(4c27934a30ac28) description: Implicitly created (auto staging).
[error] Specify one of the repository ids in the command line
[error] java.lang.IllegalStateException: Found multiple staging repositories
[error] 	at xerial.sbt.Sonatype$NexusRESTService.$anonfun$findTargetRepository$8(Sonatype.scala:479)
[error] 	at scala.Option.getOrElse(Option.scala:121)
[error] 	at xerial.sbt.Sonatype$NexusRESTService.findTargetRepository(Sonatype.scala:476)
[error] 	at xerial.sbt.Sonatype$SonatypeCommand$.$anonfun$sonatypeRelease$1(Sonatype.scala:202)
[error] 	at sbt.Command$.$anonfun$applyEffect$4(Command.scala:134)
[error] 	at sbt.Command$.$anonfun$applyEffect$2(Command.scala:130)
[error] 	at ch.epfl.scala.sbt.release.Helper.runCommand$1(ReleaseEarlyPlugin.scala:524)
[error] 	at ch.epfl.scala.sbt.release.Helper.$anonfun$runCommandAndRemaining$1(ReleaseEarlyPlugin.scala:533)
[error] 	at ch.epfl.scala.sbt.release.ReleaseEarly$Defaults$.$anonfun$sonatypeRelease$1(ReleaseEarlyPlugin.scala:267)
[error] 	at ch.epfl.scala.sbt.release.ReleaseEarly$Defaults$.$anonfun$sonatypeRelease$1$adapted(ReleaseEarlyPlugin.scala:259)
[error] 	at scala.Function1.$anonfun$compose$1(Function1.scala:44)
[error] 	at sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:39)
[error] 	at sbt.std.Transform$$anon$4.work(System.scala:66)
[error] 	at sbt.Execute.$anonfun$submit$2(Execute.scala:263)
[error] 	at sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:16)
[error] 	at sbt.Execute.work(Execute.scala:272)
[error] 	at sbt.Execute.$anonfun$submit$1(Execute.scala:263)
[error] 	at sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:174)
[error] 	at sbt.CompletionService$$anon$2.call(CompletionService.scala:37)
[error] 	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error] 	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
[error] 	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error] 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
[error] 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
[error] 	at java.lang.Thread.run(Thread.java:748)
[error] java.lang.IllegalStateException: Found multiple staging repositories
[error] 	at xerial.sbt.Sonatype$NexusRESTService.$anonfun$findTargetRepository$8(Sonatype.scala:479)
[error] 	at scala.Option.getOrElse(Option.scala:121)
[error] 	at xerial.sbt.Sonatype$NexusRESTService.findTargetRepository(Sonatype.scala:476)
[error] 	at xerial.sbt.Sonatype$SonatypeCommand$.$anonfun$sonatypeRelease$1(Sonatype.scala:202)
[error] 	at sbt.Command$.$anonfun$applyEffect$4(Command.scala:134)
[error] 	at sbt.Command$.$anonfun$applyEffect$2(Command.scala:130)
[error] 	at ch.epfl.scala.sbt.release.Helper.runCommand$1(ReleaseEarlyPlugin.scala:524)
[error] 	at ch.epfl.scala.sbt.release.Helper.$anonfun$runCommandAndRemaining$1(ReleaseEarlyPlugin.scala:533)
[error] 	at ch.epfl.scala.sbt.release.ReleaseEarly$Defaults$.$anonfun$sonatypeRelease$1(ReleaseEarlyPlugin.scala:267)
[error] 	at ch.epfl.scala.sbt.release.ReleaseEarly$Defaults$.$anonfun$sonatypeRelease$1$adapted(ReleaseEarlyPlugin.scala:259)
[error] 	at scala.Function1.$anonfun$compose$1(Function1.scala:44)
[error] 	at sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:39)
[error] 	at sbt.std.Transform$$anon$4.work(System.scala:66)
[error] 	at sbt.Execute.$anonfun$submit$2(Execute.scala:263)
[error] 	at sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:16)
[error] 	at sbt.Execute.work(Execute.scala:272)
[error] 	at sbt.Execute.$anonfun$submit$1(Execute.scala:263)
[error] 	at sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:174)
[error] 	at sbt.CompletionService$$anon$2.call(CompletionService.scala:37)
[error] 	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error] 	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
[error] 	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error] 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
[error] 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
[error] 	at java.lang.Thread.run(Thread.java:748)
[error] java.lang.IllegalStateException: Found multiple staging repositories
[error] 	at xerial.sbt.Sonatype$NexusRESTService.$anonfun$findTargetRepository$8(Sonatype.scala:479)
[error] 	at scala.Option.getOrElse(Option.scala:121)
[error] 	at xerial.sbt.Sonatype$NexusRESTService.findTargetRepository(Sonatype.scala:476)
[error] 	at xerial.sbt.Sonatype$SonatypeCommand$.$anonfun$sonatypeRelease$1(Sonatype.scala:202)
[error] 	at sbt.Command$.$anonfun$applyEffect$4(Command.scala:134)
[error] 	at sbt.Command$.$anonfun$applyEffect$2(Command.scala:130)
[error] 	at ch.epfl.scala.sbt.release.Helper.runCommand$1(ReleaseEarlyPlugin.scala:524)
[error] 	at ch.epfl.scala.sbt.release.Helper.$anonfun$runCommandAndRemaining$1(ReleaseEarlyPlugin.scala:533)
[error] 	at ch.epfl.scala.sbt.release.ReleaseEarly$Defaults$.$anonfun$sonatypeRelease$1(ReleaseEarlyPlugin.scala:267)
[error] 	at ch.epfl.scala.sbt.release.ReleaseEarly$Defaults$.$anonfun$sonatypeRelease$1$adapted(ReleaseEarlyPlugin.scala:259)
[error] 	at scala.Function1.$anonfun$compose$1(Function1.scala:44)
[error] 	at sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:39)
[error] 	at sbt.std.Transform$$anon$4.work(System.scala:66)
[error] 	at sbt.Execute.$anonfun$submit$2(Execute.scala:263)
[error] 	at sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:16)
[error] 	at sbt.Execute.work(Execute.scala:272)
[error] 	at sbt.Execute.$anonfun$submit$1(Execute.scala:263)
[error] 	at sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:174)
[error] 	at sbt.CompletionService$$anon$2.call(CompletionService.scala:37)
[error] 	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error] 	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
[error] 	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error] 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
[error] 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
[error] 	at java.lang.Thread.run(Thread.java:748)
[error] (core / releaseEarlyPublish) java.lang.IllegalStateException: Found multiple staging repositories
[error] (coreJS / releaseEarlyPublish) java.lang.IllegalStateException: Found multiple staging repositories
[error] (fs2 / releaseEarlyPublish) java.lang.IllegalStateException: Found multiple staging repositories
[error] Total time: 779 s, completed Jul 19, 2018 4:17:40 PM

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.