New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wip: experiment with EntityRef at ActorSystem level #30934
base: main
Are you sure you want to change the base?
Conversation
"EntityRef - tell (using system.entityRefFor) " in { | ||
val typeKeyWithEnvelopes = akka.actor.typed.EntityTypeKey[TestProtocol]("envelope-shard") | ||
val charlieRef = system.entityRefFor(typeKeyWithEnvelopes, "charlie") | ||
|
||
val p = TestProbe[String]() | ||
|
||
charlieRef ! WhoAreYou(p.ref) | ||
p.receiveMessage() should startWith("I'm charlie") | ||
|
||
charlieRef.tell(WhoAreYou(p.ref)) | ||
p.receiveMessage() should startWith("I'm charlie") | ||
|
||
charlieRef ! StopPlz() | ||
} | ||
|
||
"EntityRef - tell (using system.initEntity system.entityRefFor) " in { | ||
val typeKeyWithEnvelopes = akka.actor.typed.EntityTypeKey[TestProtocol]("envelope-shard-2") | ||
system.initEntity(akka.actor.typed.Entity(typeKeyWithEnvelopes)(ctx => behavior(ctx.shard))) | ||
val charlieRef = system.entityRefFor(typeKeyWithEnvelopes, "charlie") | ||
|
||
val p = TestProbe[String]() | ||
|
||
charlieRef ! WhoAreYou(p.ref) | ||
p.receiveMessage() should startWith("I'm charlie") | ||
|
||
charlieRef.tell(WhoAreYou(p.ref)) | ||
p.receiveMessage() should startWith("I'm charlie") | ||
|
||
charlieRef ! StopPlz() | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just added this here to move fast. If we go forward with this idea, it must have it's own tests. Probably repeating everything that's done in this test, but using system.initEntity
and system.entityRefFor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea, and maybe it's not so intrusive...
val messageExtractor: Option[EntityMessageExtractor[E, M]], | ||
val allocationStrategy: Option[EntityAllocationStrategy], | ||
val role: Option[String], | ||
val dataCenter: Option[DataCenter]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are several things that are only relevant in a clustered setup. role and dataCenter obviously, but also the whole idea of shard and allocationStrategy are not relevant for the local case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One idea is to only support the very basic init
in akka-actor-typed. No custom envelope, no custom message extractor, no allocationStrategy. For more advanced cases the init
in akka-cluster-sharding-typed` can be used.
That means that only EntityTypeKey
and EntityRef
have to be moved to akka-actor-typed
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, everything cluster/sharding related could be removed. But that also means that when moving to sharded entities, the user will need to adapt.
Also, what would that mean to declare an entity using the local API, but on a clustered system? Will it be unique on that node only? Or will it be a sharded one?
So maybe we should take this as a trade-off. We will have some settings that only make sense when clustered and when used in a non-clustered system they are simply ignored.
If we chose to have new APIs and deprecate the exiting sharding entity, then we could make it more explicitly as part of a cluster API. Something like:
Entity(...).withShardingSettings(...)
When not specified, fallback to some defaults. Actually, pretty much like now with Option
but behind an explicit sharding settings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that also means that when moving to sharded entities, the user will need to adapt.
Also, what would that mean to declare an entity using the local API, but on a clustered system? Will it be unique on that node only? Or will it be a sharded one?
I was thinking that the basic init
API would also work for a clustered system, and then it would init
ClusterSharding under the hood. All these options are optional in ClusterSharding.
Meaning that the API would only change if user needs something more advanced, then they would have to use the ClusterSharding.init instead.
def entityRefFor[M](typeKey: EntityTypeKey[M], entityId: String): EntityRef[M] | ||
} | ||
|
||
private[akka] object LocalEntityProvider extends EntityProvider { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be worth having a new implementation for the local case. It would be much simpler than the cluster variant. No shards at all. Still handling lifecycle and passivation.
The passivation strategies that Peter is working on can probably be used for the local case also.
Looked at this a few times since opened and, I also like the idea. Haven't dug in more to give any detailed feedback about the impl here though. |
@johanandren, the impl is more a sketch. My goal was to hacking something to find out which parts we will need. Probably everything in this PR should be re-worked. |
36d846a
to
ec227be
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I re-worked this a bit.
Now we have a copy of ClusterShardingSpec running entirely with the new API.
Still need lots of code massage, but I think it's proved that this can be done.
MiMa is working as well. So far, nothing broken.
"akka.actor.typed.EntityEnvelope" = akka-actor-typed-entity | ||
} | ||
serialization-identifiers { | ||
"akka.actor.typed.internal.entity.EntityEnvelopeSerializer" = 41 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I saw that we just added 39, so I tried 40, but it's already picked.
What are the rules for this? And how can have an overview of all identifiers in use?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
they are defined in reference.conf in section serialization-identifiers
, so the way is to search for a free spot
by the way, 41 is taken by PingSerializer in some test, but we can change that to a higher number
feels wrong to have serialization in this local module, though. It can be defined in Cluster Sharding (maybe in some of the existing serializers)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I was not so happy to have it here, but I think we can remove it all together.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@patriknw, it's not possible to remove the serialization. It's possible to workaround it when using the EntityRef
, but not when using the shard region ActorRef
(the one returned by initEntity
). Whatever we pass here can end up on the wire before we can do any conversion.
So, I kept the serialization, but now moved to cluster sharding. It's a little odd, because it's a class defined in akka-actor-typed
, but the serialization is akka-cluster-sharding-typed
. Anyway, I don't see any other use case where the serialization will be needed while not using sharding.
I think it's fair to put a limit on it and say that the new entity, if used over remote, must be in the context of cluster sharding. In other words, not remote entity if not sharded.
akka-actor-typed/src/main/scala/akka/actor/typed/internal/entity/EntityEnvelopeSerializer.scala
Outdated
Show resolved
Hide resolved
override def hashCode(): Int = delegate.hashCode() | ||
override def equals(obj: Any): Boolean = delegate.equals(obj) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This two are probably not needed. Also not sure if a good idea to do full delegation.
@@ -53,6 +55,7 @@ import akka.util.JavaDurationConverters._ | |||
extends ShardingMessageExtractor[Any, M] { | |||
override def entityId(message: Any): String = { | |||
message match { | |||
case EntityEnvelope(entityId, _) => entityId |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we will be able to remove this if we manage to do the full conversion and remove the serialization.
@@ -39,7 +41,7 @@ object ClusterSharding extends ExtensionId[ClusterSharding] { | |||
* | |||
* Not for user extension. | |||
*/ | |||
@DoNotInherit trait ShardCommand | |||
@DoNotInherit trait ShardCommand extends EntityCommand |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is probably not needed
import org.scalatest.wordspec.AnyWordSpecLike | ||
|
||
@ccompatUsedUntil213 | ||
object NewEntityWithClusterShardingSpec { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a copy of ClusterShardingSpec, but then using the new types. All tests are passing!
We may want to remove this and refactor ClusterShardingSpec to use new API.
...yped/src/main/scala/akka/cluster/sharding/typed/internal/ClusterShardingEntityProvider.scala
Outdated
Show resolved
Hide resolved
3941e78
to
3be15c8
Compare
866259d
to
2371988
Compare
e8a14d2
to
99039e3
Compare
50d54f1
to
27190a0
Compare
Hi @octonato, Thank you for your contribution! We really value the time you've taken to put this together. We see that you have signed the Lightbend Contributors License Agreement before, however, the CLA has changed since you last signed it. |
a80e867
to
f49a749
Compare
f49a749
to
26b6c75
Compare
This is just an experiment. Still very hacky and very incomplete.
The main idea is to have Entities directly available at ActorSystem level and not only when using cluster sharding.
This is very similar to Orleans' Virtual Actors where actor instantiation and lifecycle is managed by the actor system instead of by the user.
The basic API in this PR look as following:
Note:
akka.actor.typed.EntityTypeKey
andakka.actor.typed.Entity
. New types in akka-actor-typed, so not reusing the existing sharding types.Like with cluster sharding, the Entity is an Actor that is managed by Akka. Akka would know if there is one already instantiated for id "abc" and decide if the Actor needs to start or not. Akka would also be responsible for passivating that same Actor.
If the current Actor System is using cluster, then the above would result in a clustered entity (sharded) instead of a local Entity. So there is two possible code paths behind
system.initEntity
andsystem.entityRefFor
.akka.actor.typed.Entity
needs to be converted to aakka.cluster.sharding.typed.scaladsl.Entity
(or javadsl) and registered as a cluster sharding entity. The current conversion is incomplete and only support the most basic configs.The approach in this PR (duplicating and converting types) is a little heavy and fastidious, but gives a clean API to the users.
Another approach for local entities can be to not try to make then transparent. We could introduce a
LocalEntity
type to be used directly on the Actor System with similar functionality as a sharded entity (managed instantiation, passivation, etc), but explicitly local.That will simplify things a lot, IMO. Not sure if it's worth the complexity of duplicating the types and do all the conversion. If an user starts with a LocalEntity and later decide to move to Sharded Entity, the code refactoring should be minimal.
We could also imagine a situation in which we do have an Akka Cluster, but we want a particular actor to be a local entity. In that case, it will be good to have an explicit LocalEntity.
Also, such LocalEntity overlaps with some of the functionality provided by the Receptionist. If we can get an instance of an actor by using a type key and an ID, we may not need to use Receptionist. Or course, their might be valid cases for Receptionist.