Skip to content
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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

octonato
Copy link
Member

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:

val typeKey = akka.actor.typed.EntityTypeKey[Foo]("my-foo-entity")
system.initEntity(akka.actor.typed.Entity(typeKey)(ctx => behavior(ctx.shard)))

val entity: EntityRef[Foo] = system.entityRefFor(typeKey, "abc")

Note: akka.actor.typed.EntityTypeKey and akka.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 and system.entityRefFor.

  • For non-clustered actor systems, this entity will be a local actor managed by Akka. Not implemented yet.
  • For clustered actor system, the akka.actor.typed.Entity needs to be converted to a akka.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.

Comment on lines 267 to 297
"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()
}

Copy link
Member Author

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

Copy link
Member

@patriknw patriknw left a 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])
Copy link
Member

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.

Copy link
Member

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.

Copy link
Member Author

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.

Copy link
Member

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 {
Copy link
Member

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.

@johanandren
Copy link
Member

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.

@octonato
Copy link
Member Author

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.

@octonato octonato force-pushed the octonato/non-sharded-entities branch 2 times, most recently from 36d846a to ec227be Compare December 9, 2021 20:32
Copy link
Member Author

@octonato octonato left a 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/src/main/protobuf/EntityMessages.proto Outdated Show resolved Hide resolved
"akka.actor.typed.EntityEnvelope" = akka-actor-typed-entity
}
serialization-identifiers {
"akka.actor.typed.internal.entity.EntityEnvelopeSerializer" = 41
Copy link
Member Author

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?

Copy link
Member

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)

Copy link
Member Author

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.

Copy link
Member Author

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.

Comment on lines +138 to +149
override def hashCode(): Int = delegate.hashCode()
override def equals(obj: Any): Boolean = delegate.equals(obj)
Copy link
Member Author

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
Copy link
Member Author

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
Copy link
Member Author

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 {
Copy link
Member Author

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.

@octonato octonato force-pushed the octonato/non-sharded-entities branch from 3941e78 to 3be15c8 Compare December 27, 2021 18:00
@octonato octonato force-pushed the octonato/non-sharded-entities branch 2 times, most recently from 866259d to 2371988 Compare February 14, 2022 12:37
@octonato octonato force-pushed the octonato/non-sharded-entities branch 3 times, most recently from e8a14d2 to 99039e3 Compare March 29, 2022 07:22
@lightbend-cla-validator

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.
Please review the new CLA and sign it before we proceed with reviewing this pull request:

https://www.lightbend.com/contribute/cla

@octonato octonato closed this Sep 13, 2022
@octonato octonato reopened this Sep 13, 2022
@octonato octonato force-pushed the octonato/non-sharded-entities branch 2 times, most recently from a80e867 to f49a749 Compare September 28, 2022 14:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants