Skip to content

Commit ddf2016

Browse files
committed
Add invalidation for the schema-list cache (close #215)
1 parent f667a7b commit ddf2016

File tree

2 files changed

+96
-4
lines changed

2 files changed

+96
-4
lines changed

modules/core/src/main/scala/com.snowplowanalytics.iglu/client/resolver/Resolver.scala

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,36 @@ final case class Resolver[F[_]](repos: List[Registry], cache: Option[ResolverCac
108108
* Get list of available schemas for particular vendor and name part
109109
* Server supposed to return them in proper order
110110
*/
111+
def listSchemasResult(vendor: Vendor, name: Name, model: Model)(implicit
112+
F: Monad[F],
113+
L: RegistryLookup[F],
114+
C: Clock[F]
115+
): F[Either[ResolutionError, SchemaListLookupResult]] =
116+
listSchemasResult(vendor, name, model, None)
117+
118+
/**
119+
* Vendor, name, model are extracted from supplied schema key to call on the `listSchemas`. The important difference
120+
* from `listSchemas` is that it would invalidate cache, if returned list did not contain SchemaKey supplied in
121+
* argument. Making it a safer option is latest schema bound is known.
122+
*/
123+
def listSchemasLikeResult(schemaKey: SchemaKey)(implicit
124+
F: Monad[F],
125+
L: RegistryLookup[F],
126+
C: Clock[F]
127+
): F[Either[ResolutionError, SchemaListLookupResult]] =
128+
listSchemasResult(schemaKey.vendor, schemaKey.name, schemaKey.version.model, Some(schemaKey))
129+
130+
/**
131+
* Get list of available schemas for particular vendor and name part
132+
* Has an extra argument `mustIncludeKey` which is used to invalidate cache if SchemaKey supplied in it is not in the
133+
* list.
134+
* Server supposed to return them in proper order
135+
*/
111136
def listSchemasResult(
112137
vendor: Vendor,
113138
name: Name,
114-
model: Model
139+
model: Model,
140+
mustIncludeKey: Option[SchemaKey] = None
115141
)(implicit
116142
F: Monad[F],
117143
L: RegistryLookup[F],
@@ -140,7 +166,11 @@ final case class Resolver[F[_]](repos: List[Registry], cache: Option[ResolverCac
140166

141167
getSchemaListFromCache(vendor, name, model).flatMap {
142168
case Some(TimestampedItem(Right(schemaList), timestamp)) =>
143-
Monad[F].pure(Right(ResolverResult.Cached((vendor, name, model), schemaList, timestamp)))
169+
if (mustIncludeKey.forall(schemaList.schemas.contains))
170+
Monad[F].pure(Right(ResolverResult.Cached((vendor, name, model), schemaList, timestamp)))
171+
else
172+
traverseRepos[F, SchemaList](get, prioritize(vendor, allRepos.toList), Map.empty)
173+
.flatMap(handleAfterFetch)
144174
case Some(TimestampedItem(Left(failures), _)) =>
145175
retryCached[F, SchemaList](get, vendor)(failures)
146176
.flatMap(handleAfterFetch)
@@ -165,6 +195,19 @@ final case class Resolver[F[_]](repos: List[Registry], cache: Option[ResolverCac
165195
): F[Either[ResolutionError, SchemaList]] =
166196
listSchemasResult(vendor, name, model).map(_.map(_.value))
167197

198+
/**
199+
* Vendor, name, model are extracted from supplied schema key to call on the `listSchemas`. The important difference
200+
* from `listSchemas` is that it would invalidate cache, if returned list did not contain SchemaKey supplied in
201+
* argument. Making it a safer option is latest schema bound is known.
202+
*/
203+
def listSchemasLike(schemaKey: SchemaKey)(implicit
204+
F: Monad[F],
205+
L: RegistryLookup[F],
206+
C: Clock[F]
207+
): F[Either[ResolutionError, SchemaList]] =
208+
listSchemasResult(schemaKey.vendor, schemaKey.name, schemaKey.version.model, Some(schemaKey))
209+
.map(_.map(_.value))
210+
168211
/** Get list of full self-describing schemas available on Iglu Server for particular vendor/name pair */
169212
def fetchSchemas(
170213
vendor: Vendor,
@@ -365,6 +408,7 @@ object Resolver {
365408

366409
result.value
367410
}
411+
368412
def parseConfig(
369413
config: Json
370414
): Either[DecodingFailure, ResolverConfig] = {

modules/core/src/test/scala/com.snowplowanalytics.iglu.client/resolver/ResolverSpec.scala

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,17 @@
1212
*/
1313
package com.snowplowanalytics.iglu.client.resolver
1414

15-
import java.net.URI
15+
import com.snowplowanalytics.iglu.core.SchemaList
16+
1617
import java.time.Instant
18+
import java.net.URI
1719
import scala.collection.immutable.SortedMap
1820
import scala.concurrent.duration.DurationInt
1921

2022
// Cats
2123
import cats.Id
2224
import cats.effect.IO
23-
import cats.implicits._
25+
import cats.syntax.all._
2426

2527
// circe
2628
import io.circe.Json
@@ -69,6 +71,7 @@ class ResolverSpec extends Specification with CatsEffect {
6971
a Resolver should accumulate errors from all repositories $e8
7072
we can construct a Resolver from a valid resolver 1-0-2 configuration JSON $e10
7173
a Resolver should cache SchemaLists with different models separately $e11
74+
a Resolver should use schemaKey provided in SchemaListLike for result validation $e12
7275
"""
7376

7477
import ResolverSpec._
@@ -404,4 +407,49 @@ class ResolverSpec extends Specification with CatsEffect {
404407
case _ => ko("Unexpected result for two consequent listSchemas")
405408
}
406409
}
410+
411+
def e12 = {
412+
val IgluCentralServer = Registry.Http(
413+
Registry.Config("Iglu Central EU1", 0, List("com.snowplowanalytics")),
414+
Registry
415+
.HttpConnection(URI.create("https://com-iglucentral-eu1-prod.iglu.snplow.net/api"), None)
416+
)
417+
418+
val schema100 = SchemaKey(
419+
"com.snowplowanalytics.snowplow",
420+
"link_click",
421+
"jsonschema",
422+
SchemaVer.Full(1, 0, 0)
423+
)
424+
val schema101 = SchemaKey(
425+
"com.snowplowanalytics.snowplow",
426+
"link_click",
427+
"jsonschema",
428+
SchemaVer.Full(1, 0, 1)
429+
)
430+
431+
val resolverRef = Resolver.init[Id](10, None, IgluCentralServer)
432+
val resolver = resolverRef.map(res =>
433+
new Resolver(
434+
res.repos,
435+
res.cache.flatMap { c =>
436+
c.putSchemaList(
437+
"com.snowplowanalytics.snowplow",
438+
"link_click",
439+
1,
440+
SchemaList(List(schema100)).asRight
441+
)
442+
c.some
443+
}
444+
)
445+
)
446+
447+
val resultOne = resolver.listSchemasLike(schema100)
448+
val resultTwo = resolver.listSchemasLike(schema101)
449+
450+
resultOne must beRight(SchemaList(List(schema100)))
451+
resultTwo.map(s => s.copy(schemas = s.schemas.take(2))) must beRight(
452+
SchemaList(List(schema100, schema101))
453+
)
454+
}
407455
}

0 commit comments

Comments
 (0)