Permalink
Browse files

Behavior overhaul (#11)

  • Loading branch information...
notxcain committed Jan 31, 2017
1 parent 683372b commit 8f2343269e2ef8dbf53225ec31f5ca5aa634e861
Showing with 1,568 additions and 1,453 deletions.
  1. +9 −0 .scalafmt.conf
  2. +1 −1 README.md
  3. +32 −50 build.sbt
  4. +11 −13 core/src/main/resources/reference.conf
  5. +204 −0 core/src/main/scala/aecor/aggregate/AggregateActor.scala
  6. +66 −0 core/src/main/scala/aecor/aggregate/AkkaRuntime.scala
  7. +34 −0 core/src/main/scala/aecor/aggregate/AkkaRuntimeSettings.scala
  8. +35 −0 core/src/main/scala/aecor/aggregate/Folder.scala
  9. +44 −0 core/src/main/scala/aecor/aggregate/Tagging.scala
  10. +20 −0 core/src/main/scala/aecor/aggregate/package.scala
  11. +14 −0 core/src/main/scala/aecor/aggregate/serialization/Codec.scala
  12. +35 −0 core/src/main/scala/aecor/aggregate/serialization/PersistentDecoder.scala
  13. +19 −0 core/src/main/scala/aecor/aggregate/serialization/PersistentEncoder.scala
  14. +22 −0 core/src/main/scala/aecor/aggregate/serialization/PersistentRepr.scala
  15. +0 −163 core/src/main/scala/aecor/core/aggregate/AggregateActor.scala
  16. +0 −36 core/src/main/scala/aecor/core/aggregate/AggregateBehavior.scala
  17. +0 −12 core/src/main/scala/aecor/core/aggregate/AggregateName.scala
  18. +0 −19 core/src/main/scala/aecor/core/aggregate/AggregateRegionRef.scala
  19. +0 −64 core/src/main/scala/aecor/core/aggregate/AggregateSharding.scala
  20. +0 −39 core/src/main/scala/aecor/core/aggregate/AggregateShardingSettings.scala
  21. +0 −15 core/src/main/scala/aecor/core/aggregate/EventContract.scala
  22. +0 −15 core/src/main/scala/aecor/core/message/Correlation.scala
  23. +0 −53 core/src/main/scala/aecor/core/serialization/akka/Codec.scala
  24. +0 −10 core/src/main/scala/aecor/core/serialization/kafka/PureDeserializer.scala
  25. +0 −10 core/src/main/scala/aecor/core/serialization/kafka/PureSerializer.scala
  26. +0 −24 core/src/main/scala/aecor/core/streaming/AggregateJournal.scala
  27. +0 −34 core/src/main/scala/aecor/core/streaming/CassandraAggregateJournal.scala
  28. +0 −75 core/src/main/scala/aecor/core/streaming/CassandraOffsetStore.scala
  29. +0 −68 core/src/main/scala/aecor/core/streaming/CassandraReadJournalExtension.scala
  30. +0 −6 core/src/main/scala/aecor/core/streaming/CommittableJournalEntry.scala
  31. +0 −8 core/src/main/scala/aecor/core/streaming/CommittableMessage.scala
  32. +3 −0 core/src/main/scala/aecor/data/EventTag.scala
  33. +34 −0 core/src/main/scala/aecor/data/Folded.scala
  34. +9 −0 core/src/main/scala/aecor/data/Handler.scala
  35. +43 −0 core/src/main/scala/aecor/streaming/AggregateJournal.scala
  36. +64 −0 core/src/main/scala/aecor/streaming/CassandraAggregateJournal.scala
  37. +56 −0 core/src/main/scala/aecor/streaming/CassandraOffsetStore.scala
  38. +41 −0 core/src/main/scala/aecor/streaming/Committable.scala
  39. +10 −0 core/src/main/scala/aecor/streaming/OffsetStore.scala
  40. +0 −18 core/src/main/scala/aecor/util/ConfigHelpers.scala
  41. +8 −13 core/src/main/scala/aecor/util/FunctionBuilder.scala
  42. +0 −15 core/src/main/scala/aecor/util/Partitioner.scala
  43. +0 −16 core/src/main/scala/aecor/util/cassandra/package.scala
  44. +0 −6 core/src/main/scala/aecor/util/function.scala
  45. +0 −9 core/src/main/scala/aecor/util/package.scala
  46. +32 −15 core/src/main/scala/akka/persistence/cassandra/CassandraSessionInitSerialization.scala
  47. +8 −5 example/src/main/resources/application.conf
  48. +8 −10 example/src/main/scala/aecor/example/AccountAPI.scala
  49. +10 −7 example/src/main/scala/aecor/example/App.scala
  50. +60 −49 example/src/main/scala/aecor/example/AppActor.scala
  51. +41 −40 example/src/main/scala/aecor/example/AuthorizePaymentAPI.scala
  52. +132 −93 example/src/main/scala/aecor/example/domain/AccountAggregate.scala
  53. +23 −6 example/src/main/scala/aecor/example/domain/AccountAggregateEvent.scala
  54. +0 −33 example/src/main/scala/aecor/example/domain/AccountAggregateOp.scala
  55. +48 −73 example/src/main/scala/aecor/example/domain/AuthorizationProcess.scala
  56. +0 −198 example/src/main/scala/aecor/example/domain/CardAuthorization.scala
  57. +138 −0 example/src/main/scala/aecor/example/domain/CardAuthorizationAggregate.scala
  58. +27 −0 example/src/main/scala/aecor/example/domain/CardAuthorizationAggregateEvent.scala
  59. +36 −0 example/src/main/scala/aecor/example/domain/CardAuthorizationAggregateOp.scala
  60. +29 −0 example/src/main/scala/aecor/example/persistentEncoderUtil.scala
  61. +1 −7 schedule/src/main/resources/reference.conf
  62. +38 −51 schedule/src/main/scala/aecor/schedule/Schedule.scala
  63. +101 −62 schedule/src/main/scala/aecor/schedule/ScheduleActor.scala
  64. +12 −5 schedule/src/main/scala/aecor/schedule/protobuf/ScheduleEventCodec.scala
  65. +0 −7 schedule/src/main/scala/aecor/schedule/protobuf/ScheduleEventSerializer.scala
  66. +10 −0 tests/src/test/scala/aecor/tests/CompositeCorrelationIdSpec.scala
View
@@ -0,0 +1,9 @@
danglingParentheses = true
maxColumn = 100
rewrite.rules = [PreferCurlyFors, RedundantBraces]
style = IntelliJ
align.arrowEnumeratorGenerator = true
align.openParenCallSite = false
spaces {
inImportCurlyBraces = true
}
View
@@ -1,7 +1,7 @@
[![Build Status](https://img.shields.io/travis/notxcain/aecor/master.svg)](https://travis-ci.org/notxcain/aecor)
[![Maven Central](https://img.shields.io/maven-central/v/io.aecor/aecor-core_2.11.svg)](https://github.com/notxcain/aecor)
[![Join the chat at https://gitter.im/notxcain/akka-ddd](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/notxcain/aecor)
[![Join the chat at https://gitter.im/notxcain/aecor](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/notxcain/aecor)
# Aecor
View
@@ -7,15 +7,9 @@ lazy val buildSettings = Seq(
crossScalaVersions := Seq("2.11.8", "2.12.0")
)
lazy val circeVersion = "0.6.1"
lazy val akkaVersion = "2.4.14"
lazy val akkaHttpVersion = "10.0.0"
lazy val reactiveKafkaVersion = "0.13"
lazy val akkaPersistenceCassandra = "0.21"
lazy val catsVersion = "0.8.1"
lazy val akkaHttpJsonVersion = "1.11.0"
lazy val freekVersion = "0.6.5"
lazy val kryoSerializationVersion = "0.5.1"
lazy val akkaVersion = "2.4.16"
lazy val akkaPersistenceCassandra = "0.22"
lazy val catsVersion = "0.9.0"
lazy val logbackVersion = "1.1.7"
lazy val scalaCheckVersion = "1.13.4"
@@ -27,14 +21,9 @@ lazy val paradiseVersion = "2.1.0"
lazy val commonSettings = Seq(
scalacOptions ++= commonScalacOptions,
resolvers ++= Seq(
Resolver.bintrayRepo("projectseptemberinc", "maven")
),
libraryDependencies ++= Seq(
compilerPlugin(
"org.spire-math" %% "kind-projector" % kindProjectorVersion),
compilerPlugin(
"org.scalamacros" % "paradise" % paradiseVersion cross CrossVersion.full)
compilerPlugin("org.spire-math" %% "kind-projector" % kindProjectorVersion),
compilerPlugin("org.scalamacros" % "paradise" % paradiseVersion cross CrossVersion.full)
),
parallelExecution in Test := false,
scalacOptions in (Compile, doc) := (scalacOptions in (Compile, doc)).value
@@ -49,14 +38,10 @@ lazy val aecor = project
.settings(aecorSettings)
.settings(noPublishSettings)
.aggregate(core, example, schedule, tests)
.dependsOn(core,
example % "compile-internal",
tests % "test-internal -> test")
.dependsOn(core, example % "compile-internal", tests % "test-internal -> test")
lazy val core = project
.settings(moduleName := "aecor-core")
.settings(aecorSettings)
.settings(coreSettings)
lazy val core =
project.settings(moduleName := "aecor-core").settings(aecorSettings).settings(coreSettings)
lazy val schedule = project
.dependsOn(core)
@@ -85,26 +70,33 @@ lazy val coreSettings = Seq(
"com.typesafe.akka" %% "akka-persistence-query-experimental" % akkaVersion,
"com.typesafe.akka" %% "akka-slf4j" % akkaVersion,
"com.typesafe.akka" %% "akka-persistence-cassandra" % akkaPersistenceCassandra,
"com.typesafe.akka" %% "akka-stream-kafka" % reactiveKafkaVersion,
"ch.qos.logback" % "logback-classic" % logbackVersion,
"com.chuusai" %% "shapeless" % shapelessVersion,
"org.typelevel" %% "cats" % catsVersion
)
)
lazy val scheduleSettings = commonProtobufSettings
lazy val exampleSettings = Seq(
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
"de.heikoseeberger" %% "akka-http-circe" % akkaHttpJsonVersion,
("com.projectseptember" %% "freek" % freekVersion)
.exclude("org.typelevel", "cats-free_2.12.0-RC2"),
"io.circe" %% "circe-core" % circeVersion,
"io.circe" %% "circe-generic" % circeVersion,
"io.circe" %% "circe-parser" % circeVersion
lazy val exampleSettings = {
val circeVersion = "0.6.1"
val akkaHttpVersion = "10.0.3"
val akkaHttpJsonVersion = "1.11.0"
val freekVersion = "0.6.5"
Seq(
resolvers ++= Seq(Resolver.bintrayRepo("projectseptemberinc", "maven")),
libraryDependencies ++=
Seq(
"com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
"de.heikoseeberger" %% "akka-http-circe" % akkaHttpJsonVersion,
("com.projectseptember" %% "freek" % freekVersion)
.exclude("org.typelevel", "cats-free_2.12.0-RC2"),
"io.circe" %% "circe-core" % circeVersion,
"io.circe" %% "circe-generic" % circeVersion,
"io.circe" %% "circe-parser" % circeVersion,
"ch.qos.logback" % "logback-classic" % logbackVersion
)
)
)
}
lazy val testingSettings = Seq(
libraryDependencies ++= Seq(
@@ -142,18 +134,11 @@ lazy val commonScalacOptions = Seq(
"-Ypartial-unification"
)
lazy val warnUnusedImport = Seq(
scalacOptions in (Compile, console) ~= {
_.filterNot(Set("-Ywarn-unused-import", "-Ywarn-value-discard"))
},
scalacOptions in (Test, console) := (scalacOptions in (Compile, console)).value
)
lazy val warnUnusedImport = Seq(scalacOptions in (Compile, console) ~= {
_.filterNot(Set("-Ywarn-unused-import", "-Ywarn-value-discard"))
}, scalacOptions in (Test, console) := (scalacOptions in (Compile, console)).value)
lazy val noPublishSettings = Seq(
publish := (),
publishLocal := (),
publishArtifact := false
)
lazy val noPublishSettings = Seq(publish := (), publishLocal := (), publishArtifact := false)
lazy val publishSettings = Seq(
releaseCommitMessage := s"Set version to ${if (releaseUseGlobalVersion.value) (version in ThisBuild).value
@@ -176,10 +161,7 @@ lazy val publishSettings = Seq(
},
autoAPIMappings := true,
scmInfo := Some(
ScmInfo(
url("https://github.com/notxcain/aecor"),
"scm:git:git@github.com:notxcain/aecor.git"
)
ScmInfo(url("https://github.com/notxcain/aecor"), "scm:git:git@github.com:notxcain/aecor.git")
),
pomExtra :=
<developers>
@@ -8,21 +8,10 @@ cassandra-query-journal {
}
aecor {
aggregate {
akka-runtime {
number-of-shards = 30
ask-timeout = 60s
default-idle-timeout = 120s
idle-timeout {
# per entity name idle timeout here
}
default-snapshot-after = off
snapshot-after {
}
idle-timeout = 60s
}
}
@@ -38,6 +27,15 @@ akka {
"java.lang.String" = java
"akka.dispatch.sysmsg.DeathWatchNotification" = java
}
serialization-identifiers {
"aecor.aggregate.serialization.PersistentReprSerializer" = 100
}
serializers {
persistent-repr = "aecor.aggregate.serialization.PersistentReprSerializer"
}
serialization-bindings {
"aecor.aggregate.serialization.PersistentRepr" = persistent-repr
}
}
persistence {
journal.plugin = "cassandra-journal"
@@ -0,0 +1,204 @@
package aecor.aggregate
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.time.{ Duration, Instant }
import aecor.aggregate.SnapshotPolicy.{ EachNumberOfEvents, Never }
import aecor.aggregate.serialization.PersistentDecoder.Result
import aecor.aggregate.serialization.{ PersistentDecoder, PersistentEncoder, PersistentRepr }
import aecor.data.{ Folded, Handler }
import akka.NotUsed
import akka.actor.{ ActorLogging, Props, ReceiveTimeout, Stash }
import akka.cluster.sharding.ShardRegion
import akka.persistence.journal.Tagged
import akka.persistence.{ PersistentActor, RecoveryCompleted, SnapshotOffer }
import cats.~>
import scala.concurrent.duration.FiniteDuration
import scala.util.{ Left, Right }
sealed trait SnapshotPolicy[+E]
object SnapshotPolicy {
def never[E]: SnapshotPolicy[E] = Never.asInstanceOf[SnapshotPolicy[E]]
def eachNumberOfEvents[E: PersistentEncoder: PersistentDecoder](
numberOfEvents: Int
): SnapshotPolicy[E] = EachNumberOfEvents(numberOfEvents)
private[aggregate] case object Never extends SnapshotPolicy[Nothing]
private[aggregate] case class EachNumberOfEvents[State: PersistentEncoder: PersistentDecoder](
numberOfEvents: Int
) extends SnapshotPolicy[State] {
def encode(state: State): PersistentRepr = PersistentEncoder[State].encode(state)
def decode(repr: PersistentRepr): Result[State] = PersistentDecoder[State].decode(repr)
}
}
sealed trait Identity
object Identity {
case class Provided(value: String) extends Identity
case object FromPathName extends Identity
}
object AggregateActor {
def props[Command[_], State, Event: PersistentEncoder: PersistentDecoder](
entityName: String,
behavior: Command ~> Handler[State, Event, ?],
identity: Identity,
snapshotPolicy: SnapshotPolicy[State],
tagging: Tagging[Event],
idleTimeout: FiniteDuration
)(implicit folder: Folder[Folded, Event, State]): Props =
Props(new AggregateActor(entityName, behavior, identity, snapshotPolicy, tagging, idleTimeout))
case object Stop
}
/**
*
* Actor encapsulating state of event sourced entity behavior [Behavior]
*
* @param entityName entity name used as persistence prefix and as a tag for all events
* @param behavior entity behavior
* @param identity describes how to extract entity identifier
* @param snapshotPolicy snapshot policy to use
* @param idleTimeout - time with no commands after which graceful actor shutdown is initiated
*/
class AggregateActor[Command[_], State, Event: PersistentEncoder: PersistentDecoder] private[aecor] (
entityName: String,
behavior: Command ~> Handler[State, Event, ?],
identity: Identity,
snapshotPolicy: SnapshotPolicy[State],
tagger: Tagging[Event],
idleTimeout: FiniteDuration
)(implicit folder: Folder[Folded, Event, State])
extends PersistentActor
with Stash
with ActorLogging {
final private val entityId: String = identity match {
case Identity.Provided(value) => value
case Identity.FromPathName =>
URLDecoder.decode(self.path.name, StandardCharsets.UTF_8.name())
}
final override val persistenceId: String = s"$entityName-$entityId"
private val recoveryStartTimestamp: Instant = Instant.now()
log.info("[{}] Starting...", persistenceId)
protected var state: State = folder.zero
private var eventCount = 0L
final override def receiveRecover: Receive = {
case repr: PersistentRepr =>
PersistentDecoder[Event].decode(repr) match {
case Left(cause) =>
onRecoveryFailure(cause, Some(repr))
case Right(event) =>
applyEvent(event)
}
case SnapshotOffer(_, snapshotRepr: PersistentRepr) =>
snapshotPolicy match {
case Never => ()
case e @ EachNumberOfEvents(_) =>
e.decode(snapshotRepr) match {
case Left(cause) =>
onRecoveryFailure(cause, Some(snapshotRepr))
case Right(snapshot) =>
state = snapshot
}
}
case RecoveryCompleted =>
log.info(
"[{}] Recovery to version [{}] completed in [{} ms]",
persistenceId,
lastSequenceNr,
Duration.between(recoveryStartTimestamp, Instant.now()).toMillis
)
setIdleTimeout()
}
final override def receiveCommand: Receive =
receivePassivationMessages.orElse(receiveCommandMessage)
private def receiveCommandMessage: Receive = {
case command =>
handleCommand(command.asInstanceOf[Command[_]])
}
private def handleCommand(command: Command[_]): Unit = {
val (events, reply) = behavior(command).run(state)
log.debug(
"[{}] Command [{}] produced reply [{}] and events [{}]",
persistenceId,
command,
reply,
events
)
val envelopes =
events.map(e => Tagged(PersistentEncoder[Event].encode(e), tagger(e)))
persistAll(envelopes)(_ => ())
deferAsync(NotUsed) { _ =>
events.foreach { event =>
applyEvent(event)
snapshotIfNeeded()
}
sender() ! reply
}
}
private def snapshotIfNeeded(): Unit =
snapshotPolicy match {
case e @ EachNumberOfEvents(numberOfEvents) if eventCount % numberOfEvents == 0 =>
saveSnapshot(e.encode(state))
case _ => ()
}
private def applyEvent(event: Event): Unit = {
state = folder
.fold(state, event)
.getOrElse {
val error = new IllegalStateException(s"Illegal state while applying [$event] to [$state]")
log.error(error, error.getMessage)
throw error
}
eventCount += 1
if (recoveryFinished)
log.debug("[{}] State [{}]", persistenceId, state)
}
private def receivePassivationMessages: Receive = {
case ReceiveTimeout =>
if (shouldPassivate) {
passivate()
} else {
setIdleTimeout()
}
case AggregateActor.Stop =>
context.stop(self)
}
protected def shouldPassivate: Boolean = true
private def passivate(): Unit = {
log.debug("[{}] Passivating...", persistenceId)
context.parent ! ShardRegion.Passivate(AggregateActor.Stop)
}
private def setIdleTimeout(): Unit = {
log.debug("[{}] Setting idle timeout to [{}]", persistenceId, idleTimeout)
context.setReceiveTimeout(idleTimeout)
}
}
Oops, something went wrong.

0 comments on commit 8f23432

Please sign in to comment.