Skip to content

Commit

Permalink
refactor: improve post example
Browse files Browse the repository at this point in the history
  • Loading branch information
tassiluca committed Jan 12, 2024
1 parent deb31fd commit 942e80a
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 32 deletions.
9 changes: 8 additions & 1 deletion src/main/scala/io/github/tassiLuca/boundaries/either.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.github.tassiLuca.boundaries

import scala.util.boundary
import scala.util.{Failure, Success, Try, boundary}
import scala.util.boundary.{Label, break}

object either:
Expand All @@ -12,3 +12,10 @@ object either:
inline def ?(using Label[Left[L, Nothing]]): R = e match
case Right(value) => value
case Left(value) => break(Left(value))

type ThrowableConverter[L] = Throwable => L

extension [R](t: Try[R])
inline def ?[L](using Label[Left[L, Nothing]])(using converter: ThrowableConverter[L]): R = t match
case Success(value) => value
case Failure(exception) => break(Left(converter(exception)))
10 changes: 9 additions & 1 deletion src/main/scala/io/github/tassiLuca/posts/Utils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import gears.async.default.given
import gears.async.Async
import gears.async.AsyncOperations

import scala.util.Random
import scala.util.{Failure, Random, Success}

extension (component: String)
def simulates(action: String)(using Async): Unit =
Expand All @@ -16,3 +16,11 @@ extension (component: String)
println(s"[$component - ${Thread.currentThread()}] $action")
Thread.sleep(Random.nextInt(10_000))
println(s"[$component - ${Thread.currentThread()}] ended $action")

extension [T](p: (T, T))
def both(f: T => Boolean): Boolean = f(p._1) && f(p._2)

@main def testBoth(): Unit =
println(
(Success(true), Failure(IllegalStateException())).both(r => r.isSuccess && r.get)
)
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ trait PostsRepositoryComponent:
/** The repository in charge of storing and retrieving blog posts. */
trait PostsRepository:
/** Save the given [[post]]. */
def save(post: Post)(using Async): Try[Unit]
def save(post: Post)(using Async): Try[Post]

/** Load the post with the given [[postTitle]]. */
def load(postTitle: Title)(using Async): Try[Post]
Expand All @@ -30,10 +30,11 @@ trait PostsRepositoryComponent:
private class PostsLocalRepository extends PostsRepository:
private var posts: Set[Post] = Set()

override def save(post: Post)(using Async): Try[Unit] = Try:
override def save(post: Post)(using Async): Try[Post] = Try:
require(posts.count(_.title == post.title) == 0, "A post with same title has already been saved")
"PostsRepository" simulates s"saving post ${post.title}"
synchronized { posts = posts + post }
post

override def load(postTitle: Title)(using Async): Try[Post] = Try:
"PostsRepository" simulates s"loading post $postTitle"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package io.github.tassiLuca.posts.direct

import gears.async.AsyncOperations.sleep
import gears.async.default.given
import gears.async.{Async, Future, Task}
import io.github.tassiLuca.boundaries.either
import io.github.tassiLuca.boundaries.either.?
import io.github.tassiLuca.posts.{PostsModel, simulates}
import io.github.tassiLuca.boundaries.either.{?, ThrowableConverter}
import io.github.tassiLuca.posts.{PostsModel, simulates, both}

import java.util.Date
import scala.util.{Failure, Success, Try}

/** The component blog posts service. */
trait PostsServiceComponent:
Expand All @@ -33,29 +35,29 @@ trait PostsServiceComponent:

private class PostsServiceImpl extends PostsService:

given ThrowableConverter[String] = (t: Throwable) => t.getMessage

override def create(authorId: AuthorId, title: Title, body: Body): Either[String, Post] =
either:
Async.blocking:
val post: Future[Post] = Future:
val p = Post(authorId, title, body, Date())
val f1 = p.verifyAuthor.run
val f2 = p.verifyContent.run
f1.await.?
f2.await.?
context.repository.save(post.await)
post.await
Async.blocking:
val post = Post(authorId, title, body, Date())
if post.verifyAuthor.run.zip(post.verifyContent.run).await.both(r => r.isSuccess && r.get) then
either { context.repository.save(post).? }
else Left("Error")

extension (p: Post)
private def verifyAuthor(using Async): Task[Either[String, Post]] = Task:
"PostsService" simulates s"verifying author ${p.author}"
if Math.random() > 0.3 then Right(p) else Left(s"${p.author} is not authorized to post content!")

private def verifyContent(using Async): Task[Either[String, Post]] = Task:
"PostsService" simulates s"verifying author ${p.author}"
if Math.random() > 0.3 then Right(p) else Left("The post contains non-appropriate sections.")

override def get(title: Title): Either[String, Post] = Async.blocking:
context.repository.load(title).toEither.left.map(_.getMessage)

override def all(): Either[String, LazyList[Post]] = Async.blocking:
context.repository.loadAll().toEither.left.map(_.getMessage)
private def verifyAuthor(using Async): Task[Try[Boolean]] = Task:
Try:
sleep(10_000)
"PostsService" simulates s"verifying author '${p.author}'"
true

private def verifyContent(using Async): Task[Try[Boolean]] = Task:
Try:
"PostsService" simulates s"verifying post '${p.title}' content"
false

override def get(title: Title): Either[String, Post] = either:
Async.blocking { context.repository.load(title).? }

override def all(): Either[String, LazyList[Post]] = either:
Async.blocking(context.repository.loadAll().?)
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ object BlogPostsApp$ extends PostsServiceComponent with PostsModel with PostsRep
p <- app.service.get("A hello world post")
yield p
Await.ready(post, Duration.Inf)
println(post)
println(post.value)

Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ trait PostsServiceComponent:

extension (p: Post)
private def verifyAuthor: Boolean =
"PostsService" simulatesBlocking s"verifying author ${p.author}"
"PostsService" simulatesBlocking s"verifying author '${p.author}''"
if Math.random() > 0.3 then true else false

private def verifyContent: Boolean =
"PostsService" simulatesBlocking s"verifying author ${p.author}"
"PostsService" simulatesBlocking s"verifying post '${p.title}' content"
if Math.random() > 0.3 then true else false

override def get(title: Title): Future[Post] = context.repository.load(title)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package io.github.tassiLuca.posts.direct

class BlogPostsServiceTest {

}

0 comments on commit 942e80a

Please sign in to comment.