Skip to content

Commit 52fe7c2

Browse files
committed
Generate cache key from an input function
1 parent ad64d3c commit 52fe7c2

File tree

5 files changed

+47
-26
lines changed

5 files changed

+47
-26
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ This project adheres to [Semantic Versioning](http://semver.org/).
44

55
More infos about this file : http://keepachangelog.com/
66

7-
## [???] - 2019.11.25
7+
## [???] - 2020.01.20
88
* Update dependencies
9+
* The cache key is now generated from a given function.
910

1011
## [v3.0.4] - 2019.11.12
1112
* Fix for `GoogleDistanceProvider`: stop adding the traffic duration to the default duration: consider it as a full

core/src/main/scala/com/colisweb/distances/Cache.scala

+5-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ abstract class Cache[F[_]: Async](ttl: Option[Duration]) {
3838

3939
object Cache {
4040
// V is the cached value
41-
// Any* corresponds to the key parts of V see [[InnerCache.cachingF()]]
41+
// K is the key value
42+
// Any* or Seq[Any] corresponds to the key parts of V see [[InnerCache.cachingF()]]
4243

4344
// This is a caching function for a value wrapped in `F`: it tries to execute and cache it if it succeeds.
4445
type CachingF[F[_], V] = (F[V], Decoder[V], Encoder[V], Any*) => F[V]
@@ -49,4 +50,7 @@ object Cache {
4950
// This returns the cached value corresponding to the specified key. It returns an Option
5051
// (None if the key doesn't exist) wrapped in a F instance.
5152
type GetCached[F[_], V] = (Decoder[V], Any*) => F[Option[V]]
53+
54+
// This generates the cache key from K
55+
type CacheKey[K] = K => Seq[Any]
5256
}

core/src/main/scala/com/colisweb/distances/DistanceApi.scala

+14-10
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.colisweb.distances
33
import cats.Parallel
44
import cats.effect.Async
55
import cats.kernel.Semigroup
6-
import com.colisweb.distances.Cache.{Caching, GetCached}
6+
import com.colisweb.distances.Cache.{CacheKey, Caching, GetCached}
77
import com.colisweb.distances.DistanceProvider.{BatchDistanceF, DistanceF}
88
import com.colisweb.distances.Types._
99
import io.circe.{Decoder, Encoder}
@@ -20,16 +20,17 @@ import scala.collection.breakOut
2020
* @param caching A function which, given a value, caches it and returns it wrapped in a F typeclass instance.
2121
* @param getCached A function which try to retrieve a value from the cache. The value is wrapped in a Option,
2222
* which is also wrapped in a F typeclass instance.
23+
* @param cacheKey A key construction function.
2324
* @tparam F A typeclass which is constructed from Async and Parallel.
2425
* @tparam E An error type, specific to the distance provider.
2526
*/
2627
class DistanceApi[F[_]: Async: Parallel, E](
2728
distanceF: DistanceF[F, E],
2829
batchDistanceF: BatchDistanceF[F, E],
2930
caching: Caching[F, Distance],
30-
getCached: GetCached[F, Distance]
31+
getCached: GetCached[F, Distance],
32+
cacheKey: CacheKey[DirectedPath]
3133
) {
32-
3334
import DistanceApi._
3435
import cats.implicits._
3536
import com.colisweb.distances.utils.Implicits._
@@ -70,8 +71,8 @@ class DistanceApi[F[_]: Async: Parallel, E](
7071
origins
7172
.flatMap(origin => destinations.map(Segment(origin, _)))
7273
.parTraverse { segment =>
73-
val maybeCachedValue =
74-
getCached(decoder, travelMode, segment.origin, segment.destination, maybeTrafficHandling)
74+
val path = DirectedPath(segment.origin, segment.destination, travelMode, maybeTrafficHandling)
75+
val maybeCachedValue = getCached(decoder, cacheKey(path): _*)
7576

7677
(segment.pure[F] -> maybeCachedValue).bisequence
7778
}
@@ -182,17 +183,18 @@ class DistanceApi[F[_]: Async: Parallel, E](
182183
origin: LatLong,
183184
destination: LatLong,
184185
maybeTrafficHandling: Option[TrafficHandling]
185-
): F[List[(DirectedPath, Either[E, Distance])]] =
186+
): F[List[(DirectedPath, Either[E, Distance])]] = {
186187
modes.parTraverse { mode =>
187188
for {
188-
cached <- getCached(decoder, mode, origin, destination, maybeTrafficHandling)
189+
cached <- getCached(decoder, cacheKey(DirectedPath(origin, destination, mode, maybeTrafficHandling)): _*)
189190
errorOrDistance <- cached match {
190191
case Some(value) => Either.right[E, Distance](value).pure[F]
191192
case None => distanceF(mode, origin, destination, maybeTrafficHandling)
192193
}
193194
result <- maybeUpdateCache(errorOrDistance, mode, origin, destination, maybeTrafficHandling)
194195
} yield DirectedPath(origin, destination, mode, maybeTrafficHandling) -> result
195196
}
197+
}
196198

197199
private def maybeUpdateCache(
198200
errorOrDistance: Either[E, Distance],
@@ -203,7 +205,8 @@ class DistanceApi[F[_]: Async: Parallel, E](
203205
): F[Either[E, Distance]] = {
204206
errorOrDistance match {
205207
case Right(distance) =>
206-
caching(distance, decoder, encoder, mode, origin, destination, maybeTrafficHandling).map(Right[E, Distance])
208+
caching(distance, decoder, encoder, cacheKey(DirectedPath(origin, destination, mode, maybeTrafficHandling)): _*)
209+
.map(Right[E, Distance])
207210

208211
case Left(error) => error.pure[F].map(Left[E, Distance])
209212
}
@@ -218,9 +221,10 @@ object DistanceApi {
218221
distanceF: DistanceF[F, E],
219222
batchDistanceF: BatchDistanceF[F, E],
220223
caching: Caching[F, Distance],
221-
getCached: GetCached[F, Distance]
224+
getCached: GetCached[F, Distance],
225+
cacheKey: CacheKey[DirectedPath]
222226
): DistanceApi[F, E] =
223-
new DistanceApi(distanceF, batchDistanceF, caching, getCached)
227+
new DistanceApi(distanceF, batchDistanceF, caching, getCached, cacheKey)
224228

225229
private[DistanceApi] final val directedPathSemiGroup: Semigroup[DirectedPathMultipleModes] =
226230
new Semigroup[DirectedPathMultipleModes] {

tests/src/test/scala/com/colisweb/distances/DistanceApiSpec.scala

+22-13
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import scala.concurrent.duration._
2121
import scala.language.postfixOps
2222

2323
class DistanceApiSpec extends AnyWordSpec with Matchers with ScalaFutures with BeforeAndAfterEach {
24-
2524
import com.colisweb.distances.utils.Stubs._
2625

2726
val globalExecutionContext: ExecutionContext = ExecutionContext.global
@@ -37,9 +36,10 @@ class DistanceApiSpec extends AnyWordSpec with Matchers with ScalaFutures with B
3736
"#distance" should {
3837
"if origin == destination" should {
3938
"not call the provider and return immmediatly Distance.zero" in {
40-
val cache = CaffeineCache[IO](Some(1 days))
41-
val stub = distanceProviderStub[IO, Unit]
42-
val distanceApi = DistanceApi[IO, Unit](stub.distance, stub.batchDistances, cache.caching, cache.get)
39+
val cache = CaffeineCache[IO](Some(1 days))
40+
val stub = distanceProviderStub[IO, Unit]
41+
val distanceApi =
42+
DistanceApi[IO, Unit](stub.distance, stub.batchDistances, cache.caching, cache.get, defaultCacheKey)
4343
val latLong = LatLong(0.0, 0.0)
4444
val expectedResult = Map((Driving, Right(Distance.zero)), (Bicycling, Right(Distance.zero)))
4545

@@ -51,9 +51,10 @@ class DistanceApiSpec extends AnyWordSpec with Matchers with ScalaFutures with B
5151
"#distanceFromPostalCodes" should {
5252
"if origin == destination" should {
5353
"not call the provider and return immmediatly Distance.zero" in {
54-
val cache = CaffeineCache[IO](Some(1 days))
55-
val stub = distanceProviderStub[IO, Unit]
56-
val distanceApi = DistanceApi[IO, Unit](stub.distance, stub.batchDistances, cache.caching, cache.get)
54+
val cache = CaffeineCache[IO](Some(1 days))
55+
val stub = distanceProviderStub[IO, Unit]
56+
val distanceApi =
57+
DistanceApi[IO, Unit](stub.distance, stub.batchDistances, cache.caching, cache.get, defaultCacheKey)
5758
val postalCode = PostalCode("59000")
5859
val expectedResult = Map((Driving, Right(Distance.zero)), (Bicycling, Right(Distance.zero)))
5960

@@ -89,10 +90,16 @@ class DistanceApiSpec extends AnyWordSpec with Matchers with ScalaFutures with B
8990
}
9091

9192
val distanceApi: DistanceApi[F, Unit] =
92-
DistanceApi[F, Unit](mockedDistanceF[F], mockedBatchDistanceF[F], cache.caching, cache.get)
93+
DistanceApi[F, Unit](mockedDistanceF[F], mockedBatchDistanceF[F], cache.caching, cache.get, defaultCacheKey)
9394

9495
val errorDistanceApi: DistanceApi[F, Unit] =
95-
DistanceApi[F, Unit](mockedDistanceErrorF[F], mockedBatchDistanceErrorF[F], cache.caching, cache.get)
96+
DistanceApi[F, Unit](
97+
mockedDistanceErrorF[F],
98+
mockedBatchDistanceErrorF[F],
99+
cache.caching,
100+
cache.get,
101+
defaultCacheKey
102+
)
96103

97104
val paris01 = LatLong(48.8640493, 2.3310526)
98105
val paris02 = LatLong(48.8675641, 2.34399)
@@ -266,8 +273,9 @@ class DistanceApiSpec extends AnyWordSpec with Matchers with ScalaFutures with B
266273
.asInstanceOf[Map[Segment, Either[Unit, Distance]]]
267274

268275
val caches = segments.map { segment =>
269-
runSync(cache.get(Distance.decoder, Driving, segment.origin, segment.destination, None))
270-
.asInstanceOf[Option[Distance]]
276+
val path = DirectedPath(segment.origin, segment.destination, Driving, None)
277+
278+
runSync(cache.get(Distance.decoder, defaultCacheKey(path): _*)).asInstanceOf[Option[Distance]]
271279
}
272280

273281
results.keys should contain theSameElementsAs segments
@@ -285,8 +293,9 @@ class DistanceApiSpec extends AnyWordSpec with Matchers with ScalaFutures with B
285293
.asInstanceOf[Map[Segment, Either[Unit, Distance]]]
286294

287295
val allCaches = allSegments.map { segment =>
288-
runSync(cache.get(Distance.decoder, Driving, segment.origin, segment.destination, None))
289-
.asInstanceOf[Option[Distance]]
296+
val path = DirectedPath(segment.origin, segment.destination, Driving, None)
297+
298+
runSync(cache.get(Distance.decoder, defaultCacheKey(path): _*)).asInstanceOf[Option[Distance]]
290299
}
291300

292301
allResults.keys should contain theSameElementsAs allSegments

tests/src/test/scala/com/colisweb/distances/utils/Stubs.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.colisweb.distances.utils
22

33
import cats.Monad
44
import cats.effect.Async
5-
import com.colisweb.distances.Types.{Distance, LatLong, Segment, TrafficHandling}
5+
import com.colisweb.distances.Types.{DirectedPath, Distance, LatLong, Segment, TrafficHandling}
66
import com.colisweb.distances.caches.NoCache
77
import com.colisweb.distances.{DistanceProvider, _}
88
import squants.motion.KilometersPerHour
@@ -50,6 +50,9 @@ object Stubs {
5050
earthRadiusMeters * greatCircleDistance
5151
}
5252

53+
def defaultCacheKey(path: DirectedPath): Seq[Any] =
54+
Seq(path.origin, path.destination, path.travelMode, path.maybeTrafficHandling)
55+
5356
def mockedBatchDistanceF[F[_]: Monad](
5457
mode: TravelMode,
5558
origins: List[LatLong],

0 commit comments

Comments
 (0)