-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
451 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package aecor.aggregate | ||
|
||
import aecor.data.Handler | ||
import cats.data._ | ||
import cats.implicits._ | ||
import cats.{ Monad, ~> } | ||
|
||
object StateRuntime { | ||
|
||
/** | ||
* Creates an aggregate runtime that uses StateT as a target context | ||
* | ||
* This runtime doesn't account for correlation, | ||
* i.e. all operations are executed against common sequence of events | ||
* | ||
*/ | ||
def shared[Op[_], S, E, F[_]: Monad]( | ||
behavior: Op ~> Handler[S, E, ?] | ||
)(implicit folder: Folder[F, E, S]): Op ~> StateT[F, Vector[E], ?] = | ||
new (Op ~> StateT[F, Vector[E], ?]) { | ||
override def apply[A](fa: Op[A]): StateT[F, Vector[E], A] = | ||
for { | ||
events <- StateT.get[F, Vector[E]] | ||
state <- StateT.lift(folder.consume(events)) | ||
result <- { | ||
val (es, r) = behavior(fa).run(state) | ||
StateT.modify[F, Vector[E]](_ ++ es).map(_ => r) | ||
} | ||
} yield result | ||
} | ||
|
||
/** | ||
* Creates an aggregate runtime that uses StateT as a target context | ||
* | ||
* This runtime uses correlation function to get entity identifier | ||
* that is used to execute commands against corresponding | ||
* sequence of events | ||
* | ||
*/ | ||
def correlated[O[_], S, E, F[_]: Monad]( | ||
behavior: O ~> Handler[S, E, ?], | ||
correlation: Correlation[O] | ||
)(implicit folder: Folder[F, E, S]): O ~> StateT[F, Map[String, Vector[E]], ?] = | ||
new (O ~> StateT[F, Map[String, Vector[E]], ?]) { | ||
override def apply[A](fa: O[A]): StateT[F, Map[String, Vector[E]], A] = { | ||
val inner: O ~> StateT[F, Vector[E], ?] = shared(behavior) | ||
val entityId = correlation(fa) | ||
inner(fa).transformS(_.getOrElse(entityId, Vector.empty[E]), _.updated(entityId, _)) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package aecor.tests | ||
|
||
import aecor.data.Folded | ||
import cats.{ Cartesian, CoflatMap, Eval, Later, Monad, MonadCombine, MonadError, TraverseFilter } | ||
import cats.laws.{ ApplicativeLaws, CoflatMapLaws, FlatMapLaws, MonadLaws } | ||
import cats.laws.discipline._ | ||
import Folded.syntax._ | ||
import org.scalacheck.{ Arbitrary, Cogen } | ||
|
||
class FoldedTests extends LawSuite { | ||
|
||
implicit def arbitraryFolded[A](implicit A: Arbitrary[Option[A]]): Arbitrary[Folded[A]] = | ||
Arbitrary(A.arbitrary.map(_.map(_.next).getOrElse(impossible))) | ||
|
||
implicit def cogenFolded[A](implicit A: Cogen[Option[A]]): Cogen[Folded[A]] = | ||
A.contramap(_.toOption) | ||
|
||
checkAll("Folded[Int]", CartesianTests[Folded].cartesian[Int, Int, Int]) | ||
checkAll("Cartesian[Folded]", SerializableTests.serializable(Cartesian[Folded])) | ||
|
||
checkAll("Folded[Int]", CoflatMapTests[Folded].coflatMap[Int, Int, Int]) | ||
checkAll("CoflatMap[Folded]", SerializableTests.serializable(CoflatMap[Folded])) | ||
|
||
checkAll("Folded[Int]", MonadCombineTests[Folded].monadCombine[Int, Int, Int]) | ||
checkAll("MonadCombine[Folded]", SerializableTests.serializable(MonadCombine[Folded])) | ||
|
||
checkAll("Folded[Int]", MonadTests[Folded].monad[Int, Int, Int]) | ||
checkAll("Monad[Folded]", SerializableTests.serializable(Monad[Folded])) | ||
|
||
checkAll( | ||
"Folded[Int] with Folded", | ||
TraverseFilterTests[Folded].traverseFilter[Int, Int, Int, Int, Folded, Folded] | ||
) | ||
checkAll("TraverseFilter[Folded]", SerializableTests.serializable(TraverseFilter[Folded])) | ||
|
||
checkAll("Folded with Unit", MonadErrorTests[Folded, Unit].monadError[Int, Int, Int]) | ||
checkAll("MonadError[Folded, Unit]", SerializableTests.serializable(MonadError[Folded, Unit])) | ||
|
||
test("show") { | ||
impossible[Int].show should ===("Impossible") | ||
1.next.show should ===("Next(1)") | ||
|
||
forAll { fs: Folded[String] => | ||
fs.show should ===(fs.toString) | ||
} | ||
} | ||
|
||
// The following tests check laws which are a different formulation of | ||
// laws that are checked. Since these laws are more or less duplicates of | ||
// existing laws, we don't check them for all types that have the relevant | ||
// instances. | ||
|
||
test("Kleisli associativity") { | ||
forAll { (l: Long, f: Long => Folded[Int], g: Int => Folded[Char], h: Char => Folded[String]) => | ||
val isEq = FlatMapLaws[Folded].kleisliAssociativity(f, g, h, l) | ||
isEq.lhs should ===(isEq.rhs) | ||
} | ||
} | ||
|
||
test("Cokleisli associativity") { | ||
forAll { (l: Folded[Long], f: Folded[Long] => Int, g: Folded[Int] => Char, h: Folded[Char] => String) => | ||
val isEq = CoflatMapLaws[Folded].cokleisliAssociativity(f, g, h, l) | ||
isEq.lhs should ===(isEq.rhs) | ||
} | ||
} | ||
|
||
test("applicative composition") { | ||
forAll { (fa: Folded[Int], fab: Folded[Int => Long], fbc: Folded[Long => Char]) => | ||
val isEq = ApplicativeLaws[Folded].applicativeComposition(fa, fab, fbc) | ||
isEq.lhs should ===(isEq.rhs) | ||
} | ||
} | ||
|
||
val monadLaws = MonadLaws[Folded] | ||
|
||
test("Kleisli left identity") { | ||
forAll { (a: Int, f: Int => Folded[Long]) => | ||
val isEq = monadLaws.kleisliLeftIdentity(a, f) | ||
isEq.lhs should ===(isEq.rhs) | ||
} | ||
} | ||
|
||
test("Kleisli right identity") { | ||
forAll { (a: Int, f: Int => Folded[Long]) => | ||
val isEq = monadLaws.kleisliRightIdentity(a, f) | ||
isEq.lhs should ===(isEq.rhs) | ||
} | ||
} | ||
|
||
test("map2Eval is lazy") { | ||
val bomb: Eval[Folded[Int]] = Later(sys.error("boom")) | ||
impossible[Int].map2Eval(bomb)(_ + _).value should ===(impossible[Int]) | ||
} | ||
} |
Oops, something went wrong.