Skip to content

Commit

Permalink
Merge pull request #198 from 47deg/release
Browse files Browse the repository at this point in the history
1.1.0 release
  • Loading branch information
purrgrammer authored Jun 4, 2019
2 parents 3f6bdc3 + db90c14 commit 1050ee4
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 141 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 06/05/2019 - Version 1.1.0

- Add `Fetch#runAll` for running a Fetch getting back both log and cache
- Add `Fetch#liftIO` for lifting `cats.effect.IO` values into a `Fetch`
- Add `Fetch#liftF` for lifting arbitrary `cats.effect.Concurrent` values into a `Fetch`
- Loosen `ConcurrentEffect` constraint to `Concurrent` (@kubukoz)

## 02/26/2019 - Version 1.0.0

The 1.0.0 release of Fetch is a redesign of the library in terms of `cats-effect` abstractions. It's a backwards-incompatible release that introduces numerous breaking changes as well as a couple of new features. It now should be easier to use and require less work from the user of the library, specially when you are already using `cats-effect`.
Expand Down
79 changes: 40 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[comment]: # (Start Badges)

[![Join the chat at https://gitter.im/47deg/fetch](https://badges.gitter.im/47deg/fetch.svg)](https://gitter.im/47deg/fetch?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/47deg/fetch.svg?branch=master)](https://travis-ci.org/47deg/fetch) [![codecov.io](http://codecov.io/github/47deg/fetch/coverage.svg?branch=master)](http://codecov.io/github/47deg/fetch?branch=master) [![Maven Central](https://img.shields.io/badge/maven%20central-1.0.0-green.svg)](https://oss.sonatype.org/#nexus-search;gav~com.47deg~fetch*) [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/47deg/fetch/master/LICENSE) [![Latest version](https://img.shields.io/badge/fetch-1.0.0-green.svg)](https://index.scala-lang.org/47deg/fetch) [![Scala.js](http://scala-js.org/assets/badges/scalajs-0.6.17.svg)](http://scala-js.org) [![GitHub Issues](https://img.shields.io/github/issues/47deg/fetch.svg)](https://github.com/47deg/fetch/issues)
[![Join the chat at https://gitter.im/47deg/fetch](https://badges.gitter.im/47deg/fetch.svg)](https://gitter.im/47deg/fetch?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/47deg/fetch.svg?branch=master)](https://travis-ci.org/47deg/fetch) [![codecov.io](http://codecov.io/github/47deg/fetch/coverage.svg?branch=master)](http://codecov.io/github/47deg/fetch?branch=master) [![Maven Central](https://img.shields.io/badge/maven%20central-0.6.1-green.svg)](https://oss.sonatype.org/#nexus-search;gav~com.47deg~fetch*) [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/47deg/fetch/master/LICENSE) [![Latest version](https://img.shields.io/badge/fetch-0.6.1-green.svg)](https://index.scala-lang.org/47deg/fetch) [![Scala.js](http://scala-js.org/assets/badges/scalajs-0.6.15.svg)](http://scala-js.org) [![GitHub Issues](https://img.shields.io/github/issues/47deg/fetch.svg)](https://github.com/47deg/fetch/issues)

[comment]: # (End Badges)

Expand All @@ -19,13 +19,13 @@ For Scala 2.11.x and 2.12.x:
[comment]: # (Start Replace)

```scala
"com.47deg" %% "fetch" % "1.0.0"
"com.47deg" %% "fetch" % "1.1.0"
```

Or, if using Scala.js (0.6.x):

```scala
"com.47deg" %%% "fetch" % "1.0.0"
"com.47deg" %%% "fetch" % "1.1.0"
```

[comment]: # (End Replace)
Expand All @@ -51,17 +51,17 @@ Data Sources take two type parameters:

```scala
import cats.data.NonEmptyList
import cats.effect.ConcurrentEffect
import cats.effect.Concurrent

trait DataSource[F[_], Identity, Result]{
def data: Data[Identity, Result]
def CF: ConcurrentEffect[F]
def CF: Concurrent[F]
def fetch(id: Identity): F[Option[Result]]
def batch(ids: NonEmptyList[Identity]): F[Map[Identity, Result]]
}
```

Returning `ConcurrentEffect` instances from the fetch methods allows us to specify if the fetch must run synchronously or asynchronously and use all the goodies available in `cats` and `cats-effect`.
Returning `Concurrent` instances from the fetch methods allows us to specify if the fetch must run synchronously or asynchronously and use all the goodies available in `cats` and `cats-effect`.

We'll implement a dummy data source that can convert integers to strings. For convenience, we define a `fetchString` function that lifts identities (`Int` in our dummy data source) to a `Fetch`.

Expand All @@ -75,16 +75,16 @@ import cats.syntax.all._

import fetch._

def latency[F[_] : Effect](milis: Long): F[Unit] =
Effect[F].delay(Thread.sleep(milis))
def latency[F[_] : Concurrent](milis: Long): F[Unit] =
Concurrent[F].delay(Thread.sleep(milis))

object ToString extends Data[Int, String] {
def name = "To String"

def source[F[_] : ConcurrentEffect]: DataSource[F, Int, String] = new DataSource[F, Int, String]{
def source[F[_] : Concurrent]: DataSource[F, Int, String] = new DataSource[F, Int, String]{
override def data = ToString

override def CF = ConcurrentEffect[F]
override def CF = Concurrent[F]

override def fetch(id: Int): F[Option[String]] = for {
_ <- CF.delay(println(s"--> [${Thread.currentThread.getId}] One ToString $id"))
Expand All @@ -100,13 +100,13 @@ object ToString extends Data[Int, String] {
}
}

def fetchString[F[_] : ConcurrentEffect](n: Int): Fetch[F, String] =
def fetchString[F[_] : Concurrent](n: Int): Fetch[F, String] =
Fetch(n, ToString.source)
```

## Creating a runtime

Since `Fetch` relies on `ConcurrentEffect` from the `cats-effect` library, we'll need a runtime for executing our effects. We'll be using `IO` from `cats-effect` to run fetches, but you can use any type that has a `ConcurrentEffect` instance.
Since `Fetch` relies on `Concurrent` from the `cats-effect` library, we'll need a runtime for executing our effects. We'll be using `IO` from `cats-effect` to run fetches, but you can use any type that has a `Concurrent` instance.

For executing `IO` we need a `ContextShift[IO]` used for running `IO` instances and a `Timer[IO]` that is used for scheduling, let's go ahead and create them, we'll use a `java.util.concurrent.ScheduledThreadPoolExecutor` with a couple of threads to run our fetches.

Expand All @@ -126,7 +126,7 @@ implicit val cs: ContextShift[IO] = IO.contextShift(executionContext)
Now that we can convert `Int` values to `Fetch[F, String]`, let's try creating a fetch.

```scala
def fetchOne[F[_] : ConcurrentEffect]: Fetch[F, String] =
def fetchOne[F[_] : Concurrent]: Fetch[F, String] =
fetchString(1)
```

Expand All @@ -137,8 +137,8 @@ import scala.concurrent.duration._
// import scala.concurrent.duration._

Fetch.run[IO](fetchOne).unsafeRunTimed(5.seconds)
// --> [107] One ToString 1
// <-- [107] One ToString 1
// --> [65] One ToString 1
// <-- [65] One ToString 1
// res0: Option[String] = Some(1)
```

Expand All @@ -149,16 +149,16 @@ As you can see in the previous example, the `ToStringSource` is queried once to
Multiple fetches to the same data source are automatically batched. For illustrating it, we are going to compose three independent fetch results as a tuple.

```scala
def fetchThree[F[_] : ConcurrentEffect]: Fetch[F, (String, String, String)] =
def fetchThree[F[_] : Concurrent]: Fetch[F, (String, String, String)] =
(fetchString(1), fetchString(2), fetchString(3)).tupled
```

When executing the above fetch, note how the three identities get batched and the data source is only queried once.

```scala
Fetch.run[IO](fetchThree).unsafeRunTimed(5.seconds)
// --> [107] Batch ToString NonEmptyList(1, 2, 3)
// <-- [107] Batch ToString NonEmptyList(1, 2, 3)
// --> [65] Batch ToString NonEmptyList(1, 2, 3)
// <-- [65] Batch ToString NonEmptyList(1, 2, 3)
// res1: Option[(String, String, String)] = Some((1,2,3))
```

Expand All @@ -168,10 +168,10 @@ Note that the `DataSource#batch` method is not mandatory, it will be implemented
object UnbatchedToString extends Data[Int, String] {
def name = "Unbatched to string"

def source[F[_] : ConcurrentEffect] = new DataSource[F, Int, String] {
def source[F[_] : Concurrent] = new DataSource[F, Int, String] {
override def data = UnbatchedToString

override def CF = ConcurrentEffect[F]
override def CF = Concurrent[F]

override def fetch(id: Int): F[Option[String]] =
CF.delay(println(s"--> [${Thread.currentThread.getId}] One UnbatchedToString $id")) >>
Expand All @@ -181,27 +181,27 @@ object UnbatchedToString extends Data[Int, String] {
}
}

def unbatchedString[F[_] : ConcurrentEffect](n: Int): Fetch[F, String] =
def unbatchedString[F[_] : Concurrent](n: Int): Fetch[F, String] =
Fetch(n, UnbatchedToString.source)
```

Let's create a tuple of unbatched string requests.

```scala
def fetchUnbatchedThree[F[_] : ConcurrentEffect]: Fetch[F, (String, String, String)] =
def fetchUnbatchedThree[F[_] : Concurrent]: Fetch[F, (String, String, String)] =
(unbatchedString(1), unbatchedString(2), unbatchedString(3)).tupled
```

When executing the above fetch, note how the three identities get requested in parallel. You can override `batch` to execute queries sequentially if you need to.

```scala
Fetch.run[IO](fetchUnbatchedThree).unsafeRunTimed(5.seconds)
// --> [107] One UnbatchedToString 1
// --> [110] One UnbatchedToString 3
// --> [109] One UnbatchedToString 2
// <-- [107] One UnbatchedToString 1
// <-- [110] One UnbatchedToString 3
// <-- [109] One UnbatchedToString 2
// --> [65] One UnbatchedToString 1
// --> [68] One UnbatchedToString 3
// --> [67] One UnbatchedToString 2
// <-- [65] One UnbatchedToString 1
// <-- [67] One UnbatchedToString 2
// <-- [68] One UnbatchedToString 3
// res2: Option[(String, String, String)] = Some((1,2,3))
```

Expand All @@ -213,10 +213,10 @@ If we combine two independent fetches from different data sources, the fetches c
object Length extends Data[String, Int] {
def name = "Length"

def source[F[_] : ConcurrentEffect] = new DataSource[F, String, Int] {
def source[F[_] : Concurrent] = new DataSource[F, String, Int] {
override def data = Length

override def CF = ConcurrentEffect[F]
override def CF = Concurrent[F]

override def fetch(id: String): F[Option[Int]] = for {
_ <- CF.delay(println(s"--> [${Thread.currentThread.getId}] One Length $id"))
Expand All @@ -232,25 +232,25 @@ object Length extends Data[String, Int] {
}
}

def fetchLength[F[_] : ConcurrentEffect](s: String): Fetch[F, Int] =
def fetchLength[F[_] : Concurrent](s: String): Fetch[F, Int] =
Fetch(s, Length.source)
```

And now we can easily receive data from the two sources in a single fetch.

```scala
def fetchMulti[F[_] : ConcurrentEffect]: Fetch[F, (String, Int)] =
def fetchMulti[F[_] : Concurrent]: Fetch[F, (String, Int)] =
(fetchString(1), fetchLength("one")).tupled
```

Note how the two independent data fetches run in parallel, minimizing the latency cost of querying the two data sources.

```scala
Fetch.run[IO](fetchMulti).unsafeRunTimed(5.seconds)
// --> [107] One ToString 1
// --> [108] One Length one
// <-- [107] One ToString 1
// <-- [108] One Length one
// --> [65] One ToString 1
// --> [66] One Length one
// <-- [65] One ToString 1
// <-- [66] One Length one
// res3: Option[(String, Int)] = Some((1,3))
```

Expand All @@ -261,7 +261,7 @@ When fetching an identity, subsequent fetches for the same identity are cached.
```scala
import cats.syntax.all._

def fetchTwice[F[_] : ConcurrentEffect]: Fetch[F, (String, String)] = for {
def fetchTwice[F[_] : Concurrent]: Fetch[F, (String, String)] = for {
one <- fetchString(1)
two <- fetchString(1)
} yield (one, two)
Expand All @@ -271,8 +271,8 @@ While running it, notice that the data source is only queried once. The next tim

```scala
Fetch.run[IO](fetchTwice).unsafeRunTimed(5.seconds)
// --> [110] One ToString 1
// <-- [110] One ToString 1
// --> [67] One ToString 1
// <-- [67] One ToString 1
// res4: Option[(String, String)] = Some((1,1))
```

Expand All @@ -288,6 +288,7 @@ For more in-depth information take a look at our [documentation](http://47deg.gi
If you wish to add your library here please consider a PR to include it in the list below.

[comment]: # (Start Copyright)

# Copyright

Fetch is designed and developed by 47 Degrees
Expand Down
Loading

0 comments on commit 1050ee4

Please sign in to comment.