[2.0.0-beta1]
Using the local stoage emulator / vs2019
Given the following Entity
public interface ITestEntity
{
Task<string> GetFoo();
}
public class TestEntity: ITestEntity
{
public Task<string> GetFoo()
{
return Task.FromResult("Success");
}
[FunctionName(nameof(TestEntity))]
public static Task Run([EntityTrigger] IDurableEntityContext ctx)
=> ctx.DispatchAsync<TestEntity>();
}
I'm triggering a signal to an entity in the following way, and expecting a response :
public static class GetFoo
{
[FunctionName("GetFoo")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = "Foo/{id}")] HttpRequest req,
string id,
[OrchestrationClient] IDurableOrchestrationClient client,
ILogger log)
{
var target = new EntityId(nameof(TestEntity), id);
string result = string.Empty;
await client.SignalEntityAsync<ITestEntity>(target, async proxy =>
result = await proxy.GetFoo());
return (ActionResult)new OkObjectResult($"Status, {result}");
}
}
While debugging, after the call to await proxy.GetFoo(), my result object is null.
Some seconds later, a debugging breakpoint hits inside the GetFoo() method. Which implies that the trigger code is not waiting for the response from the SignalEntityAsync
I then re-read the docs and discovered this note
It's important to note that the ReadEntityStateAsync and SignalEntityAsync methods of IDurableOrchestrationClient prioritize performance over consistency. ReadEntityStateAsync can return a stale value, and **SignalEntityAsync can return before the operation has finished**.
This language, specifically "can return", implies that this kind of behaviour I'm experiencing is to be expected... _sometimes?_
@cgillum I'm unsure how to proceed now as the behaviour is not guaranteed. Is this design limitation in the Beta? Is this a problem just with the local emulator? Can you shed any light on this? I'm also aware I could be incorrectly using SignalEntityAsync<T>
I see that there is a ReadEntityStateAsync<T> method which could be used to get the state. However, I don't want to bleed out all the internal state of the Entity to the caller.
What I would like to do is present a curated model to the caller (using the GetFoo() method). I also need to pre-compute values in the GetFoo() method which I can't do using ReadEntityStateAsync<T>. I also need to emit business events from within the GetFoo() method, which I can't do with ReadEntityStateAsync<T>.
I'm not an Actor expert, but I do have experience using Service Fabric Actors, which would allow you to call a method such as GetFoo() on an actor and return a response. Is there a particular Actor design _style_ that Durable Entities requires us to comply with?
@cgillum any thoughts?
The behavior you're seeing is by design because SignalEntityAsync<T> is a one-way messaging API. The relevant documentation is this snippet:
Accessing entities from clients
Durable entities can be invoked from ordinary functions via the
orchestrationClientbinding (IDurableOrchestrationClientin .NET). The following methods are supported:
- ReadEntityStateAsync
: reads the state of an entity. - SignalEntityAsync: sends a one-way message to an entity, and waits for it to be enqueued.
- SignalEntityAsync
: same as SignalEntityAsync but uses a generated proxy object of type T.
Durable Entities only supports one-way messaging. This means that clients have no way of getting a response, similar to how it's not possible to synchronously invoke an orchestration from a client function. This is a key difference between us and Service Fabric actors: we use queues (one way) whereas they use TCP connections (two way RPC).
That said, it's clearly misleading that we allow you to invoke a proxy method that returns a result when we have no way of actually populating that result. I'll take it as an action item that we need to make this more clear in the docs and provide additional validation at runtime.
@cgillum Yeah, I think its misleading because in that same snippet it shows the ICounter Entity interface, which has an example of returning an int, which naturally makes me think that if I can invoke that method then I will get its response.
public interface ICounter
{
void Add(int amount);
void Reset();
int Get();
}
My use-case is that I want to wrap a public API around that Entity, and when I perform a GET I want to invoke GetFoo(id) on my Entity. Inside the GetFoo() I would perform some Business Logic on the entity state, and then return the result to the API layer.
This now means that I have to use ReadEntityStateAsync<T> to get that state into my API layer, and then move/copy some of the Business Logic into the API layer, which to me is wrong as the API layer should be all about presentation of data, not business logic.
Would it make sense to have a way of invoking a method on an Entity that allows a synchronous response, but then _doesn't perform any state updates at the end of the operation_? It might be operating on stale data, to avoid locking, which is fine in my use-case.
Since client functions are stateless and since we only use queues for communication, there is no clean way to create a synchronous experience for entity operations.
Adding @sebastianburckhardt in case he has any thoughts on this.
Creating a synchronous experience is not impossible, just not easy. We would have to create queues for the specific purpose of communicating results back to the clients, or use some alternate communication service (e.g. SignalR). We can consider adding something like that in the future (I have this feature implemented in the experimental EventHubs back-end).
For now, I think the very least we can do is to remove the Get call from the interface in the documentation of the client API. Because you can't actually call it! If we leave just the void Add() and void Reset() this should be a bit clearer.
I agree with the above. Having an Entity with some data and methods containing business logic to transform and return that for an external request. Today I cannot call an entity only from Orchestrators to get some data back.
This gives extra _essential complexity_ where my design needs to create a wrapper service that reads the state of an entity does the transformation of the data so that it can return it to the caller.
Wondering if the examples in the docs on this are what have had me spinning in circles all day. Switching form client apis to context apis, trying to get things to compile and then debugging through I'm getting no results. I started with context but ran into API issues for possible non-exposed methods like not being abl to call SignalEntityAsync on an IDurableOrchestrationContext. Is this related?
@julielerman, I found the mistake in the documentation you are referring to. It shows ctx.SignalEntityAsync instead of ctx.SignalEntity. Sorry for wasting your time! Will fix.