Skip to content

Instantly share code, notes, and snippets.

@wsargent
Last active May 20, 2018 17:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wsargent/a2d3b4a35c27404296d45c4e911bf524 to your computer and use it in GitHub Desktop.
Save wsargent/a2d3b4a35c27404296d45c4e911bf524 to your computer and use it in GitHub Desktop.
package repository
import java.util.UUID
import cats._
import cats.implicits._
import scala.util._
/**
* Demonstrates exposing capabilities as facets of a repository, so individual elements are exposed.
*
* The capabilities use tagless final to show how you can use different effects with capabilities.
*
* For example, the Id effect is an identity, so a failure will cause an exception.
*
* The Try effect is a disjoint union with Exception, so a failure will return Failure(Exception) as a result.
*/
object Main {
val ID = UUID.fromString("c31d34e2-5892-4a2d-9fd5-3ce2e0efedf7")
import ItemRepository._
def main(args: Array[String]): Unit = {
val itemRepository = new ItemRepository()
changeWithId(itemRepository)
changeWithTry(itemRepository)
}
def changeWithId(itemRepository: ItemRepository): Unit = {
val idAccess = new ItemRepository.IdAccess()
val idNameChanger = new NameChanger[Id]( idAccess.finder(itemRepository), idAccess.updater(itemRepository), _.map(identity))
val idResult = idNameChanger.changeName(ID, "new name")
println(s"id result = $idResult")
}
def changeWithTry(itemRepository: ItemRepository): Unit = {
val tryAccess = new ItemRepository.TryAccess()
val tryNameChanger = new NameChanger[Try](tryAccess.finder(itemRepository), tryAccess.updater(itemRepository), {
case Success(Some(result)) => result.map(Some(_))
case Success(None) => Success(None)
case Failure(ex) => Failure(ex)
})
val tryResult = tryNameChanger.changeName(ID, "new name")
println(s"try result = $tryResult")
}
class NameChanger[G[_]: Functor](finder: Finder[G], updater: Updater[G], transform: G[Option[G[UpdateResult]]] => G[Option[UpdateResult]]) {
def changeName(id: UUID, newName: String): G[Option[UpdateResult]] = {
val saved: G[Option[G[UpdateResult]]] = finder.find(id).map { maybeItem: Option[Item] =>
maybeItem.map { item =>
updater.update(item.copy(name = newName))
}
}
transform(saved)
}
}
// #repository
case class Item(id: UUID, name: String)
class ItemRepository {
import ItemRepository._
private var items = Seq(Item(ID, "item name"))
private def find(id: UUID): Option[Item] = items.find(_.id == id)
private def update(u: Item): UpdateResult = UpdateResult(s"item $u updated")
private object capabilities {
val finder: Finder[Id] = new Finder[Id]() {
override def find(id: UUID): Id[Option[Item]] = ItemRepository.this.find(id)
}
val updater: Updater[Id] = new Updater[Id]() {
override def update(item: Item): Id[UpdateResult] = ItemRepository.this.update(item)
}
}
}
object ItemRepository {
sealed trait Finder[F[_]] {
def find(id: UUID): F[Option[Item]]
}
sealed trait Updater[F[_]] {
def update(item: Item): F[UpdateResult]
}
case class UpdateResult(message: String)
class IdAccess {
def finder(repo: ItemRepository): Finder[Id] = repo.capabilities.finder
def updater(repo: ItemRepository): Updater[Id] = repo.capabilities.updater
}
class TryAccess {
def finder(repo: ItemRepository): Finder[Try] = new Finder[Try] {
override def find(id: UUID): Try[Option[Item]] = Try(repo.capabilities.finder.find(id))
}
def updater(repo: ItemRepository): Updater[Try] = new Updater[Try] {
override def update(item: Item): Try[UpdateResult] = Try(repo.capabilities.updater.update(item))
}
}
}
// #repository
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment