Cat Theory: science of composition. Most of maths can be based on it, and programming as well.
Compose and abstract. We need to abstract because we can't hold more than 2-3 things and details at time in our head (short memory). We can't check every time the impl details.
f: A => B
. Can either base on Set theory, or forget about details and focus on substance, and I have object/arrows and properties.
Void
or empty set is an initial object. It has an arrow that goes to any other object (in the category of all possible types). For every type A
, there is one and only one function going from Void
to A
.
There are no functions that can target this Void
, because this would imply the possibility of constructing an element from this type.
There is a function absurd[A]: Void => A
but it cannot be called! Sourcing a type is called Elimination. Targeting from a type is called Introduction.
Unit
: we reverse all the arrows. Unit
is an object that has arrows from any other object. It is a set of just one element.
Introduction: unit[A]: A => Unit
; Elimination: Unit => A
There are many functions that implement the Elimination, as many as the #A
Given A
and B
I want to define a product C
such that there is f: C => A
and g: C => B
and we have h(c: C) = (f(c), g(c))
. There are many of these candidates C
, but the best ones are those that allow me to get the full set A from the application of h
. If there is any other candidate C'
there is by definition a function that factorizes the initial ones. This defines Product in terms of compositions.
Reversing arrows we get to sum types.
With Void -> 0
, Unit -> 1
, Product -> x
, Sum -> +
=> We have 2 monoids.
This is enough to build basically all types:
Bool = True | False
Nat = Unit | (Unit | (Unit | (Unit |
Same for Option[A]
, or List[A]
(which is a recursive type)
We have 2 categories, each with its own arrows and objects. Can I define a category between the objects of Cat1 and Cat2. We want that this functor is able to map the arrows of Cat1 to those of Cat2. If exists f: A => B
, there is a F[A => B]: F[A] => F[B]
...and then I get lost with adjunctions :-)
Natural transformations. Given F[A] ~> G[A]
, it must exist for all A
! E.g. map List[_] ~> Option[_]
by headOption
, it works for all A
!
Using cats, I can define any type. We can define function types out of it, then abstract on functions between categories called functors. If src and target cat is the same we have endofunctors (and monads) In the cat of functors I can define arrows as nat transformations.
If hold state inside actor it's ok. Actor has total control over state, fine.
How to break this: I have a state that refers an external entity that is referenced by other actors. E.g. close over mutable state in async calls, or closing over actor context, or sender. Or passing mutable state to constructor of Actor.
Why is this bad? Actors don't control the thread they are working on, therefore no ThreadLocal
!!! What to do:
- use immutable messages and data structures.
- Ask actors to provide state information
- for async calls, use always the
pipeTo
pattern! And always add a recover for timeout for the future, otherwise it will send a message that is without specific context
Don't put too many actors under /user
level. The parent actor must take care of exceptions sent by children actors. The /user
default mechanism just restarts actors and they loose state.
We should focus on happy path, let it fail! Don't recover, the part out of band, e.g. the supervisor, will take care of handling the error.
We want stuff to be bulk headed from one another. But keep in mind: each AS is backed by a ThreadPool, that contend each for cores.
What to do: Trust akka, bulkhead with custom dispatchers. Config: define contexts.mydispatcher.thread-pool-executor.thread-size
and when instantiating actor use `Props[MyActor].withDispatcher("context.mydispatcher")``
- Avoid string concat, rather use curly braces
- Avoid non-async logging
- Avoid debugging level in prod
- Use Logstash
e.g. Fork-join executor by default: With all the virtualization around it, we neeed to know how many actual cores the application is granted => much context switching.
Call async lib => async lib => blocking call!!! How do I find this? That's difficult.
Don't await for futures. Context switching costs few order of magnitude. If you have to block, do it on dedicated dispatcher. Use the akka scheduler if ou are awaiting for something (sending a reminder and checking periodically rather than blocking, and mark it as a control message).
If one of the 2 systems downs, the system 1 must be restarted to be able to handle this.
We rather need a failure detector, we need a gossip protocol under it. Akka-Cluster helps, it gives you all this.
Akka-Cluster is not a silver bullet though, we can have a split brain => use split brain resolver
Don't leave the default on. Never.
It is bad for protocol evolution. Use always something that is bacwards compatible. Avro has schema registry, backwards compatibility guaranteed.
Behavior of actor: what actor can do:
- create new actors
- change behavior for next messages
- change its state and interact with external systems
Distr systems are about protocols, not about implementation.
In legacy actors I can model behavior by receive and changing receive
In Typed Akka:
- I have no
ActorRef
butActorRef[T]
, whereT
is the type actor is able to handle - I don't extend
Actor
but I extendBehavior[T]
- I don't have anymore implicit
sender()
norActorSelection
, because the comppiler doesn't know what sender is sednign the message therefore at compile time we can't know the type the sender is able to handle Behavior[T]
is aMessage => Behavior[N]
. Messages are either of typeT
or of typeSignal
- I use
Actor.immutable
if the actor doesn't have state. NB. as there is no sender, I need to pass the sender necessraily in the message itself!!! - ==> cmd
GetBalance
becomesGetBalance(replyTo: ActorRef[Balance])
- ==> cmd
Withdraw
becomesWithrdraw(amount: Long, replyTo: ActorRef[WithdrawReply]
whereWithdrawReply = Withdrawn | InsuffBalance(value: Long
(pardon my Haskell) - To create the actor I create some method that returns a
Behavior[Command]
def apply(b: Long): Behavior[Command] = Actor.immutable {
case (_, GetBalance(replyTo: ActorRef[Balance]_)
}
- Testing is slightly different
- If I want to ignore some messages, I use
Actor.immutablePartial
to ignore some messages - Actor.deferred to defer to children actors
- Actor.onSignal for actors that handle signals
- To create children use
ActorContext.spawn
- To create an actor able to receive messages that I'm not able to handle,
Context.spawnAdapter{case ExternalMs => InternalMsg etc...}
- Adapters give us type safety with relatively low boilerplate
- There is
akka.typed.scaladsl.adapter._
that when imported allows to mix typed and untyped actors together - Supervision: Faulty actors are not restarted, but STOPPED! This is a great behavior we always tend to apply to our (untyped) actors.
contextspawn(Actor.supervise(MyTypedActor).onFailure(exception...)
- What is available so far: there are Typed API for Akka persistence, Cluster, Sharding, CRDT. Implementation is still based on untyped. Be there at workshop on Saturday!
- Aim: end Q1-2018 experimental and Prod ready.
Defs:
- Patterns: Mean Algebraic pattern, that can be used on all ctx. Different implementations lead to different implementations, but algebra still holds
- Modularity: Build small features to compose and build bigger ones. We want pure APIs, functional
- Domain model: nothing to do with solution architecture, it is the business domain
We abstract Side effects with pure values that we call effects.
Make side effects tractable through effects. Is Domain model a function? No. But models inside the full DM is a Bounded Ctx that is composed of:
- Domain objects named after consistent vocabulary
- Rules that are modeled through functions and properties these functions honor. Functions are closed under composition
- All this builds an algebra
- A strongly typed language allows to express some invariants through type constraints
- Some other rules can be tested through Property Based testing
Typically algebras are abstract on the type. E.g. a Monoid
or Foldable
are generic on type. Then I can derive a mapReduce
algebra given the ones of Foldable
and Monoid
Algebras are strictly reusable. Domain model is union of Domain Algebras (one per domain model)
- Be explicit about your algebra, use types names from DDD
- Group functions in modules
- Group modules
Side effects will be there (DB, logs, partiality, exceptions). Side effects are anti-patterns for modularity, they break RT. Solution: Abstract them into Data types, and compose them. These Data type constructors we call Effect. Reader, State, IO, Writer
all have shape of F[A]
where F
deals with context, and A
with the value inside the context.
We keep the algebra open for interpretation as long as possible. E.g. in test I can be happy with Id
rather than Future
monad
I want to be explicit about which property I need to be able to e.g. compose or parallelize my computations.
Let's commit as late as possible about the concrete effect we want to commit to. This allows us to focus on the storytelling, i.e. on the what and forget about the how. Optimize, compose later on.
Result: I can compose stuff. I want to plugin in new algebras together and e.g. abstract the logging the logs and then wirigin all together. I can test that I am logging properly!!!
Advantage of Free
over tagless final: it's stack safe, and it can allow to build an AST for the program and do optimizations (e.g. based on known business rules)
Other options is Eff
.
- Use the least possible abstraction
- Parameterize on types as much as possible
- Commit on types as late as you can
Issues on Shapeless: Compile time long. Lazy works with recursive types but it's very dirty under the hood. Macros are not portable.
To fix these issues probably we need to work on the compiler itself
Things we care about:
- Compilation safety
- Naming
- Formatting
Use Either
, with typed error and typed Right part. How do we compose stuff that have different Left
parts, being that each function deals with different apis? How do we put this in a for
comprehension? The left types must be aligned and they aren't.
One solution is to have all our error types to extend the same error, e.g. DBError extends MyError
, and WSError extends MyError
. I would have to make all errors in 1 file, with sealed trait MyError
, and this can make this file huge to cover all possible errors.
When calls are async we have Future[Either[Error, User]]
and I have extra noise, while I care only about User
. Future
can have success or failure, and to map to our Either
we can always use the .recover
and go back to our Future[Either[_, _]]
. But what if I forget? How can I have some guarantees?
Other problem: I have to map the value on the right part. I need a map with a map inside, it's a mess and very error prone.
This way I can pretend the future is not there. But the naming is not immediate, and e.g. a junior can have trouble with it. Formatting is great and back to shape. Compilation fails though as EitherT
is invariant on error. and I have to use EitherT[Fut, MyError, A]
and I lost my type safety
I'd like to be able to use Union Types, avoiding that sealed trait... and rather have a type that says A | B | C
. The for comprehension could automatically map to this union type and I don't loose any compilation check.