Skip to content

Commit 2287faf

Browse files
committed
refactor(cache): from extends to type-class
1 parent ff11eae commit 2287faf

5 files changed

Lines changed: 84 additions & 42 deletions

File tree

src/main/scala/net/yoshinorin/qualtet/Modules.scala

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import org.typelevel.log4cats.slf4j.Slf4jFactory as Log4CatsSlf4jFactory
1111
import org.typelevel.otel4s.trace.Tracer
1212
import com.github.benmanes.caffeine.cache.{Cache as CaffeineCache, Caffeine}
1313
import net.yoshinorin.qualtet.auth.{AuthService, Jwt, KeyPair}
14-
import net.yoshinorin.qualtet.cache.{CacheRepository, InMemoryCache}
14+
import net.yoshinorin.qualtet.cache.CacheRepository
1515
import net.yoshinorin.qualtet.config.ApplicationConfig
1616
import net.yoshinorin.qualtet.domains.{ArticlesPagination, FeedsPagination, Limit, Page, PaginationOps, PaginationRequestModel, TagsPagination}
1717
import net.yoshinorin.qualtet.domains.archives.{ArchiveRepository, ArchiveRepositoryAdapter, ArchiveService}
@@ -98,7 +98,10 @@ class Modules(tx: Transactor[IO], maybeTracer: Option[Tracer[IO]] = None) {
9898
val contentTypeRepositoryAdapter: ContentTypeRepositoryAdapter[ConnectionIO] = new ContentTypeRepositoryAdapter(contentTypeRepository)
9999
val contentTypeCaffeinCache: CaffeineCache[String, ContentType] =
100100
Caffeine.newBuilder().expireAfterAccess(config.cache.contentType, TimeUnit.SECONDS).build[String, ContentType]
101-
val contentTypeCache: CacheRepository[IO, String, ContentType] = new InMemoryCache[IO, String, ContentType](contentTypeCaffeinCache)
101+
val contentTypeCache: CacheRepository[IO, String, ContentType] = {
102+
given CaffeineCache[String, ContentType] = contentTypeCaffeinCache
103+
summon[CacheRepository[IO, String, ContentType]]
104+
}
102105
val contentTypeService = new ContentTypeService[IO, ConnectionIO](contentTypeRepositoryAdapter, contentTypeCache)
103106

104107
val robotsRepository: RobotsRepository[ConnectionIO] = summon[RobotsRepository[ConnectionIO]]
@@ -113,7 +116,10 @@ class Modules(tx: Transactor[IO], maybeTracer: Option[Tracer[IO]] = None) {
113116
val tagRepository: TagRepository[ConnectionIO] = summon[TagRepository[ConnectionIO]]
114117
val tagsCaffeinCache: CaffeineCache[String, Seq[TagResponseModel]] =
115118
Caffeine.newBuilder().expireAfterAccess(config.cache.tags, TimeUnit.SECONDS).build[String, Seq[TagResponseModel]]
116-
val tagsCache: CacheRepository[IO, String, Seq[TagResponseModel]] = new InMemoryCache[IO, String, Seq[TagResponseModel]](tagsCaffeinCache)
119+
val tagsCache: CacheRepository[IO, String, Seq[TagResponseModel]] = {
120+
given CaffeineCache[String, Seq[TagResponseModel]] = tagsCaffeinCache
121+
summon[CacheRepository[IO, String, Seq[TagResponseModel]]]
122+
}
117123
val tagRepositoryAdapter: TagRepositoryAdapter[ConnectionIO] = new TagRepositoryAdapter[ConnectionIO](tagRepository)
118124
val tagService = new TagService[IO, ConnectionIO](tagRepositoryAdapter, tagsCache, contentTaggingRepositoryAdapter)
119125

@@ -159,14 +165,20 @@ class Modules(tx: Transactor[IO], maybeTracer: Option[Tracer[IO]] = None) {
159165
val sitemapRepository: SitemapsRepository[ConnectionIO] = summon[SitemapsRepository[ConnectionIO]]
160166
val sitemapCaffeinCache: CaffeineCache[String, Seq[Url]] =
161167
Caffeine.newBuilder().expireAfterAccess(config.cache.sitemap, TimeUnit.SECONDS).build[String, Seq[Url]]
162-
val sitemapCache: CacheRepository[IO, String, Seq[Url]] = new InMemoryCache[IO, String, Seq[Url]](sitemapCaffeinCache)
168+
val sitemapCache: CacheRepository[IO, String, Seq[Url]] = {
169+
given CaffeineCache[String, Seq[Url]] = sitemapCaffeinCache
170+
summon[CacheRepository[IO, String, Seq[Url]]]
171+
}
163172
val sitemapRepositoryAdapter: SitemapRepositoryAdapter[ConnectionIO] = new SitemapRepositoryAdapter[ConnectionIO](sitemapRepository)
164173
val sitemapService = new SitemapService[IO, ConnectionIO](sitemapRepositoryAdapter, sitemapCache)
165174

166175
val feedsPaginationOps = summon[PaginationOps[FeedsPagination]]
167176
val feedCaffeinCache: CaffeineCache[String, ArticleWithCountResponseModel] =
168177
Caffeine.newBuilder().expireAfterAccess(config.cache.feed, TimeUnit.SECONDS).build[String, ArticleWithCountResponseModel]
169-
val feedCache: CacheRepository[IO, String, ArticleWithCountResponseModel] = new InMemoryCache[IO, String, ArticleWithCountResponseModel](feedCaffeinCache)
178+
val feedCache: CacheRepository[IO, String, ArticleWithCountResponseModel] = {
179+
given CaffeineCache[String, ArticleWithCountResponseModel] = feedCaffeinCache
180+
summon[CacheRepository[IO, String, ArticleWithCountResponseModel]]
181+
}
170182
val feedService = new FeedService[IO, ConnectionIO](
171183
feedsPaginationOps.make(PaginationRequestModel(Option(Page(1)), Option(Limit(config.feed.limit)), None)),
172184
feedCache,

src/main/scala/net/yoshinorin/qualtet/cache/CacheRepository.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,33 @@ trait CacheRepository[F[_]: Monad, T1, T2] {
1313
def invalidate(): F[Unit]
1414

1515
}
16+
17+
object CacheRepository {
18+
19+
import com.github.benmanes.caffeine.cache.Cache as CaffeineCache
20+
21+
given InMemoryCache[F[_]: Monad, T1, T2](using caffeineCache: CaffeineCache[T1, T2]): CacheRepository[F, T1, T2] = {
22+
23+
new CacheRepository[F, T1, T2] {
24+
override def get(k: T1): F[Option[T2]] = {
25+
Monad[F].pure(Option(caffeineCache.getIfPresent(k)))
26+
}
27+
28+
override def put(k: T1, v: T2): F[Unit] = {
29+
Monad[F].pure(caffeineCache.put(k, v))
30+
}
31+
32+
override def put(k: T1, v: Option[T2]): F[Unit] = {
33+
v match {
34+
case Some(x: T2) => Monad[F].pure(caffeineCache.put(k, x))
35+
case _ => Monad[F].pure(()) // Nothing to do
36+
}
37+
}
38+
39+
override def invalidate(): F[Unit] = {
40+
Monad[F].pure(caffeineCache.invalidateAll())
41+
}
42+
}
43+
}
44+
45+
}

src/main/scala/net/yoshinorin/qualtet/cache/InMemoryCache.scala

Lines changed: 0 additions & 27 deletions
This file was deleted.

src/test/scala/net/yoshinorin/qualtet/cache/InMemoryCacheSpec.scala

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ class InMemoryCacheSpec extends AnyWordSpec {
1515
val contentTypeCaffeinCache: CaffeineCache[String, ContentType] =
1616
Caffeine.newBuilder().expireAfterAccess(5, TimeUnit.SECONDS).build[String, ContentType]
1717

18-
val contentTypeCache = new InMemoryCache[IO, String, ContentType](contentTypeCaffeinCache)
18+
val contentTypeCache: CacheRepository[IO, String, ContentType] = {
19+
given CaffeineCache[String, ContentType] = contentTypeCaffeinCache
20+
summon[CacheRepository[IO, String, ContentType]]
21+
}
1922
contentTypeCache.put(articleContentType.name.value, articleContentType)
2023

2124
val caffeinCache: CaffeineCache[Int, String] =
@@ -39,7 +42,10 @@ class InMemoryCacheSpec extends AnyWordSpec {
3942
}
4043

4144
"hit optional" in {
42-
val cache = new InMemoryCache[IO, Int, String](caffeinCache)
45+
val cache: CacheRepository[IO, Int, String] = {
46+
given CaffeineCache[Int, String] = caffeinCache
47+
summon[CacheRepository[IO, Int, String]]
48+
}
4349
(for {
4450
_ <- cache.put(1, Option("foo"))
4551
cached <- cache.get(1)
@@ -49,7 +55,10 @@ class InMemoryCacheSpec extends AnyWordSpec {
4955
}
5056

5157
"miss optional" in {
52-
val cache = new InMemoryCache[IO, Int, String](caffeinCache)
58+
val cache: CacheRepository[IO, Int, String] = {
59+
given CaffeineCache[Int, String] = caffeinCache
60+
summon[CacheRepository[IO, Int, String]]
61+
}
5362
(for {
5463
_ <- cache.put(2, None)
5564
cached <- cache.get(2)
@@ -59,7 +68,10 @@ class InMemoryCacheSpec extends AnyWordSpec {
5968
}
6069

6170
"miss after expire" in {
62-
val cache = new InMemoryCache[IO, Int, String](caffeinCache)
71+
val cache: CacheRepository[IO, Int, String] = {
72+
given CaffeineCache[Int, String] = caffeinCache
73+
summon[CacheRepository[IO, Int, String]]
74+
}
6375
(for {
6476
_ <- cache.put(3, "foo")
6577
cached <- cache.get(1)
@@ -72,7 +84,10 @@ class InMemoryCacheSpec extends AnyWordSpec {
7284
}
7385

7486
"flush" in {
75-
val cache = new InMemoryCache[IO, Int, String](caffeinCache)
87+
val cache: CacheRepository[IO, Int, String] = {
88+
given CaffeineCache[Int, String] = caffeinCache
89+
summon[CacheRepository[IO, Int, String]]
90+
}
7691
(for {
7792
_ <- cache.put(4, "bar")
7893
_ <- cache.put(5, "baz")

src/test/scala/net/yoshinorin/qualtet/fixture/Fixture.scala

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import com.github.plokhotnyuk.jsoniter_scala.core.JsonValueCodec
1616
import net.yoshinorin.qualtet.config.ApplicationConfig
1717
import net.yoshinorin.qualtet.http.AuthProvider
1818
import net.yoshinorin.qualtet.http.CorsProvider
19-
import net.yoshinorin.qualtet.cache.{CacheRepository, InMemoryCache}
19+
import net.yoshinorin.qualtet.cache.CacheRepository
2020
import net.yoshinorin.qualtet.domains.contents.ContentPath
2121
import net.yoshinorin.qualtet.domains.articles.*
2222
import net.yoshinorin.qualtet.domains.authors.*
@@ -113,19 +113,28 @@ object Fixture {
113113
// TODO: from config for cache options
114114
val contentTypeCaffeinCache: CaffeineCache[String, ContentType] =
115115
Caffeine.newBuilder().expireAfterAccess(5, TimeUnit.SECONDS).build[String, ContentType]
116-
val contentTypeCache = new InMemoryCache[IO, String, ContentType](contentTypeCaffeinCache)
116+
val contentTypeCache: CacheRepository[IO, String, ContentType] = {
117+
given CaffeineCache[String, ContentType] = contentTypeCaffeinCache
118+
summon[CacheRepository[IO, String, ContentType]]
119+
}
117120
val contentTypeService = new ContentTypeService(modules.contentTypeRepositoryAdapter, contentTypeCache)
118121

119122
val sitemapRepository: SitemapsRepository[ConnectionIO] = summon[SitemapsRepository[ConnectionIO]]
120123
val sitemapCaffeinCache: CaffeineCache[String, Seq[Url]] =
121124
Caffeine.newBuilder().expireAfterAccess(5, TimeUnit.SECONDS).build[String, Seq[Url]]
122-
val sitemapCache = new InMemoryCache[IO, String, Seq[Url]](sitemapCaffeinCache)
125+
val sitemapCache: CacheRepository[IO, String, Seq[Url]] = {
126+
given CaffeineCache[String, Seq[Url]] = sitemapCaffeinCache
127+
summon[CacheRepository[IO, String, Seq[Url]]]
128+
}
123129
val sitemapRepositoryAdapter: SitemapRepositoryAdapter[ConnectionIO] = new SitemapRepositoryAdapter[ConnectionIO](sitemapRepository)
124130
val sitemapService = new SitemapService(sitemapRepositoryAdapter, sitemapCache)
125131

126132
val feedCaffeinCache: CaffeineCache[String, ArticleWithCountResponseModel] =
127133
Caffeine.newBuilder().expireAfterAccess(5, TimeUnit.SECONDS).build[String, ArticleWithCountResponseModel]
128-
val feedCache: CacheRepository[IO, String, ArticleWithCountResponseModel] = new InMemoryCache[IO, String, ArticleWithCountResponseModel](feedCaffeinCache)
134+
val feedCache: CacheRepository[IO, String, ArticleWithCountResponseModel] = {
135+
given CaffeineCache[String, ArticleWithCountResponseModel] = feedCaffeinCache
136+
summon[CacheRepository[IO, String, ArticleWithCountResponseModel]]
137+
}
129138
val feedService = new FeedService(
130139
summon[PaginationOps[FeedsPagination]].make(PaginationRequestModel(Option(Page(1)), Option(Limit(5)), None)),
131140
feedCache,
@@ -134,7 +143,10 @@ object Fixture {
134143

135144
val tagsCaffeinCache: CaffeineCache[String, Seq[TagResponseModel]] =
136145
Caffeine.newBuilder().expireAfterAccess(5, TimeUnit.SECONDS).build[String, Seq[TagResponseModel]]
137-
val tagsCache: CacheRepository[IO, String, Seq[TagResponseModel]] = new InMemoryCache[IO, String, Seq[TagResponseModel]](tagsCaffeinCache)
146+
val tagsCache: CacheRepository[IO, String, Seq[TagResponseModel]] = {
147+
given CaffeineCache[String, Seq[TagResponseModel]] = tagsCaffeinCache
148+
summon[CacheRepository[IO, String, Seq[TagResponseModel]]]
149+
}
138150
val tagService = new TagService(tagRepositoryAdapter, tagsCache, contentTaggingRepositoryAdapter)
139151

140152
val authProvider = new AuthProvider[IO, ConnectionIO](modules.authService)

0 commit comments

Comments
 (0)