vlingo/lattice

A compute and processing grid for actors with vlingo/lattice.

Temporary Notes

Actor-based Entities Contrasted with Plain-Object Entities

In a typical object-based application or service that uses POJOs, for example, the lifecycle of Entities is different from that of actor-based Entities. Here we can assume that Entity and Aggregate as defined by DDD is interchangeable, as in what is described here deals with both concepts.

POJO Lifecycles

The following describes the typical lifecycle of a POJO Entity:

  • Non-existing Entity states are newly constructed and then persisted to storage

  • Preexisting Entity states are reconstituted from storage, modified, and then persisted back to the same storage

  • Across a single VM or a multi-VM cluster, there may be any number of the same Entity instances by unique identity, and thus the database must provide optimistic concurrency and detect concurrency violations via state versions

  • The object reference is released after Entity persistence and the instance garbage collected

Actor-Entity Lifecycles

The following describes the typical lifecycle of an actor-based Entity. Note that this applies across various types of Entities supported by vlingo/lattice, including Sourced<T>, StatefulEntity<T>, and ObjectEntity<T>:

  • Non-existing states are newly persisted, and persisted state is then reflected into the actor state

  • Preexisting states are possibly already in memory; if not in memory, states are reconstituted from storage; proposed changes are then persisted back to the same storage; following persistence, the state is then reflected into the actor state

  • Across a single VM or a multi-VM cluster, there is one entity instance by identity, and all requests for creation/modification will be focused on that single instance

  • The actor is retained in memory until memory constraints call for an least-recently used determination to indicate that this specific actor instance must be evicted to make room for other "hot" actors

Regarding Sourced,EventSourced, and CommandSourced designs.

I did attempt to support the constructor based approach. It leads to several very complex problems. I'll use your example to explain. (1) The MySourcedEntity constructor doesn't execute until after the Sourced constructor, so restoring state in the Sourcedconstructor is impossible unless forcing the MySourcedEntity and all other concrete entities to pass their id to the Sourced constructor. (2) Forcing 1 to work makes it extremely difficult to reconstitute the MySourcedEntity and all other concrete entities dynamically when the lattice detects a sent message but the actor is not currently in memory. (3) Even if forcing entities to pass their id to the Sourced constructor, think of what would be required to enable the following apply(...) to work. The Sourced constructor would have to make a blocking invocation to the Journal to recover state before returning to the MySourcedEntityconstructor to allow that apply(...) to work. Otherwise, returning to the MySourcedEntityconstructor immediately would enable the apply(...)to carry out prior to state recovery, or worse, simultaneously on two separate threads, doubt causing entirely crazy race conditions that would indeed be our problem to fix. (4) To avoid issues 1-3 we could pass in the existing lifecycle event stream to a constructor, but this assumes that we could safely perform an async fetch of the stream and then reconstitute the entity before another thread raced to accomplish the same, and thus at least duplicating instances in the cluster (or worse). (5) Attempting 4 would require more asynchronous components to load the stream and reconstitute the entity. This would likely lead to requiring both Application Service components and Repository components, such are found in the over-engineered N-tier architectures that vlingo is fighting against.

We could list another 2-3 undesirable outcomes of that design, but I hopefully you are already convinced that you really don't want it after all :)

One way to rethink this is that the constructor is really not a good expression of behavior and the Ubiquitous Language. Even when designing with POJOs it's generally desirable to hide the constructor behind an expressive Factory Method, such as follows:

Proposal proposal = Proposal.submitFor(client, expectations);

If you look at the vlingo-iddd-collaboration example you can see how this is done in all cases where a new EventSourced entity is being created:

https://github.com/vlingo/vlingo-examples/blob/8fbc432545555641efab01d790183c6580a985c9/vlingo-iddd-collaboration/src/main/java/com/saasovation/collaboration/model/forum/Forum.java#L24

Again inside ForumEntity: https://github.com/vlingo/vlingo-examples/blob/8fbc432545555641efab01d790183c6580a985c9/vlingo-iddd-collaboration/src/main/java/com/saasovation/collaboration/model/forum/ForumEntity.java#L57

Again inside DiscussionEntity: https://github.com/vlingo/vlingo-examples/blob/8fbc432545555641efab01d790183c6580a985c9/vlingo-iddd-collaboration/src/main/java/com/saasovation/collaboration/model/forum/DiscussionEntity.java#L39

I did since think of one possible way to make the constructor apply() work. I could cache the event(s) of the constructor's apply()to actually be performed following the completion of restore() under the control of start(). I haven't looked at it yet, but it probably would work. This would support any uses of apply()that occur prior to start(). A major caveat here is that MySourcedEntity may assume that it has no state currently (because we are in the same constructor used for initial state and pre-recovery, and the cached apply() may be wrongly performed following start() because the event(s) could be wrong given an eventually recovered state. Thus, even though I could find a way to support it, I still think it is ultimately the wrong feature to support.

The vlingo-lattice uses a LRU to evict actors from the lattice (grid) that have not been recently used. This would not happen to an actor that had just been created or reconstituted into the lattice.