DistributedProcessing
should be used to distribute you processes across the cluster.
Schedule
in general, i.e if it's not a part of your domain, an anti-pattern, it is here for the sake of backwards compatibility, because we use it in our projects at work. What I would suggest for a greenfield project is to use a view side of an aggregate to perform planned actions, e.g. periodically query all subscription where nextBillingDate < now, and invoice them, instead of adding an entry to schedule and react when it fires.
@notxcain Thanks for the explanation :) If I understood correctly,
1) the former can be implemented with a command handler with side effect like this https://pavkin.ru/aecor-part-2/#gist93471703 or a Saga that orchestrates the consequent commands, and
2) the latter can be implemented with another event handler that subscribes the original journal, filters interesting events and calls external service unless the correlation id is marked as 'done.'
Are both ways possible with aecor, or am I missing something?
UserEvent
with different subtypes Created
, ...Created
event to kafka. It first gets encoded using Avro into a GenericRecord and the schema gets registered in the registry.Map[TypeTag, Decoder]
flying around in every consumer.
val event = consumer.poll[UserEvent](topicName) // Gives back GenericRecord
def fold(e: UserEvent, aggregate: State) = { e match { case Created => .... } }
fold(event, currentState) // Need for the correct decoding of a GenericRecord back to the correct scala type
The only place where Aecor handles it is in boopickel-wire-protocol
module. And it uses numeric type tags for. Let's say you have
sealed trait FooEvent
case class FooCreated(name: String) extends FooEvent
case class FooDeleted(reason: String) extends FooEvent
Let's say you have some encoder: FooEvent => Avro
and decoder: Avro => Option[FooEvent]
. In pseudo code it would be
val encoder = {
case FooCreated(name) => encodeInt(0) then encodeString(name)
case FooDeleted(reason) => encodeInt(1) then encodeString(reason)
}
val decoder = decodeInt.flatMap {
case 0 => decodeString.map(name => FooCreated(name))
case 1 => decoderString.map(reason => FooDeleted(reason))
case _ => Option.empty
}