Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Las funciones de entidad definen operaciones que leen y actualizan pequeños fragmentos de estado, denominados entidades duraderas. Al igual que las funciones de orquestador, las funciones de entidad usan un tipo de desencadenador especial denominado desencadenador de entidad. A diferencia de las funciones de orquestador, las funciones de entidad administran explícitamente el estado de la entidad en lugar de representar el estado a través del flujo de control. Las entidades le ayudan a escalar horizontalmente las aplicaciones mediante la distribución del trabajo entre muchas entidades, cada una con un estado modesto.
Nota
Las funciones de entidad y las características relacionadas están disponibles en Durable Functions 2.0 y versiones posteriores. Se admiten en .NET en-proc, .NET aislado, JavaScript y Python, pero no en PowerShell ni en Java.
Las entidades definen operaciones que leen y actualizan pequeños fragmentos de estado, denominados entidades duraderas. A diferencia de los orquestadores, las entidades administran el estado explícitamente en lugar de representar el estado a través del flujo de control. Las entidades le ayudan a escalar horizontalmente las aplicaciones mediante la distribución del trabajo entre muchas entidades, cada una con un estado modesto.
Nota
Las entidades duraderas se admiten en los SDK de .NET y Python Durable Task. El SDK de Java no admite entidades.
Conceptos generales
Las entidades actúan como servicios pequeños que se comunican mediante mensajes. Cada entidad tiene una identidad única y, si es necesario, el estado interno. Las entidades ejecutan operaciones cuando se le solicite. Una operación puede actualizar el estado, llamar a servicios externos o esperar una respuesta. Las entidades se comunican con otras entidades, orquestaciones y clientes a través de mensajes que el entorno de ejecución envía a través de colas confiables.
Para evitar conflictos, una sola entidad ejecuta las operaciones en serie, una después de otra.
Nota
Al invocar una entidad, procesa la carga hasta la finalización y, a continuación, programa una nueva ejecución que se activa cuando llega una nueva entrada. Como resultado, es posible que los registros de ejecución de su entidad muestren una ejecución adicional después de cada invocación. Eso es lo esperado.
El identificador de entidad
Use el identificador de entidad para acceder a una entidad. Un identificador de entidad es un par de cadenas que identifica de forma única una instancia de entidad. Consta de:
-
Nombre de entidad, que identifica el tipo de entidad. Por ejemplo:
Counter. Este nombre coincide con el nombre de la función de entidad que implementa la entidad. No distingue mayúsculas de minúsculas. - Clave de entidad, que identifica de forma única la entidad entre todas las demás entidades con el mismo nombre. Por ejemplo, un GUID (Identificador Único Global).
Por ejemplo, una Counter función de entidad se puede usar para mantener la puntuación en un juego en línea. Cada instancia del juego tiene un identificador de entidad único, como @Counter@Game1 y @Counter@Game2. Para establecer como destino una entidad, especifique su identificador de entidad.
Operaciones de entidad
Para invocar una operación en una entidad, especifique:
- Identificador de entidad de la entidad de destino.
-
Nombre de la operación, que es una cadena que especifica la operación que se va a realizar. Por ejemplo, la entidad
Counterpodría soportar operaciones deadd,get, oreset. -
Entrada de operación, que es un parámetro opcional para la operación. Por ejemplo, la
addoperación toma una cantidad entera como entrada. - Hora programada, que es un parámetro opcional para especificar el tiempo de entrega de la operación. Por ejemplo, programe una operación para que se ejecute varios días después.
Las operaciones pueden devolver un valor de resultado o un resultado de error, como un error de JavaScript o una excepción de .NET. La orquestación que realiza la llamada recibe el resultado o el error.
Las operaciones pueden devolver un valor de resultado o un resultado de error, como una excepción de .NET o una excepción de Python. El autor de la llamada recibe el resultado o error.
Una operación de entidad también puede crear, leer, actualizar y eliminar el estado de la entidad. El tiempo de ejecución siempre conserva el estado de la entidad en el almacenamiento.
Definir entidades
Use una de estas dos API para definir entidades en .NET:
Sintaxis basada en funciones: en la sintaxis basada en funciones, se escribe cada entidad como una función y se envían operaciones en la aplicación. Esta sintaxis funciona bien para las entidades con estado simple, pocas operaciones o un conjunto dinámico de operaciones, como en marcos de aplicaciones. Pero puede ser tedioso mantener porque no detecta errores de tipo en tiempo de compilación.
Sintaxis basada en clases: En la sintaxis basada en clases, las clases y métodos de .NET modelan entidades y operaciones. Esta sintaxis facilita la lectura del código y permite invocar operaciones de manera segura en cuanto a tipos. Es una capa delgada sobre la sintaxis basada en funciones, por lo que puede mezclar ambas variantes en la misma aplicación.
Las API que use dependen de dónde se ejecuten las funciones de C#. Se recomienda un proceso de trabajo aislado , pero también puede ejecutarse en el proceso de host.
Ejemplo basado en funciones en proceso:
En este ejemplo se muestra una entidad simple Counter implementada como una función duradera. Define tres operaciones(add , resety get) que usan un estado entero.
[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
switch (ctx.OperationName.ToLowerInvariant())
{
case "add":
ctx.SetState(ctx.GetState<int>() + ctx.GetInput<int>());
break;
case "reset":
ctx.SetState(0);
break;
case "get":
ctx.Return(ctx.GetState<int>());
break;
}
}
Para obtener más información, consulte Sintaxis basada en funciones.
Ejemplo basado en clases en proceso:
En este ejemplo se muestra la misma Counter entidad implementada mediante clases y métodos.
[JsonObject(MemberSerialization.OptIn)]
public class Counter
{
[JsonProperty("value")]
public int CurrentValue { get; set; }
public void Add(int amount) => this.CurrentValue += amount;
public void Reset() => this.CurrentValue = 0;
public int Get() => this.CurrentValue;
[FunctionName(nameof(Counter))]
public static Task Run([EntityTrigger] IDurableEntityContext ctx)
=> ctx.DispatchAsync<Counter>();
}
Esta entidad almacena el estado en un Counter objeto que contiene el valor del contador actual. Durable Functions serializa y deserializa este objeto mediante la biblioteca Json.NET.
Para obtener más información, consulte Definición de clases de entidad.
Ejemplo basado en funciones del proceso de trabajo aislado:
En el ejemplo siguiente se muestra una entidad basada en una función en un proceso de trabajo aislado. Admite add, reset, get y delete.
[Function(nameof(Counter))]
public static Task Counter([EntityTrigger] TaskEntityDispatcher dispatcher)
{
return dispatcher.DispatchAsync(operation =>
{
if (operation.State.GetState(typeof(int)) is null)
{
operation.State.SetState(0);
}
switch (operation.Name.ToLowerInvariant())
{
case "add":
int state = operation.State.GetState<int>();
state += operation.GetInput<int>();
operation.State.SetState(state);
return new(state);
case "reset":
operation.State.SetState(0);
break;
case "get":
return new(operation.State.GetState<int>());
case "delete":
operation.State.SetState(null);
break;
}
return default;
});
}
Ejemplo de clase basado en clases de proceso de trabajo aislado:
En el ejemplo siguiente se muestra la implementación de la Counter entidad mediante clases y métodos.
public class Counter : TaskEntity<int>
{
readonly ILogger logger;
public Counter(ILogger<Counter> logger)
{
this.logger = logger;
}
public void Add(int amount) => this.State += amount;
public void Reset() => this.State = 0;
public int Get() => this.State;
[Function(nameof(Counter))]
public Task RunEntityAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
{
return dispatcher.DispatchAsync(this);
}
}
El SDK de Durable Task para .NET admite la definición de entidades mediante una sintaxis basada en clases. Puede implementar la clase base TaskEntity<TState> para definir su entidad.
En el ejemplo siguiente se muestra una Counter entidad implementada mediante el SDK de Durable Task:
using Microsoft.DurableTask.Entities;
public class Counter : TaskEntity<int>
{
public void Add(int amount) => this.State += amount;
public void Reset() => this.State = 0;
public int Get() => this.State;
}
Para registrar la entidad con el trabajador:
builder.Services.AddDurableTaskWorker()
.AddTasks(registry =>
{
registry.AddEntity<Counter>();
})
.UseDurableTaskScheduler(connectionString);
Para indicar o llamar a una entidad desde un orquestador:
public class EntityOrchestration : TaskOrchestrator<string, int>
{
public override async Task<int> RunAsync(TaskOrchestrationContext context, string entityKey)
{
var entityId = new EntityInstanceId(nameof(Counter), entityKey);
// Signal the entity (fire-and-forget)
await context.Entities.SignalEntityAsync(entityId, nameof(Counter.Add), 1);
// Call the entity and wait for response
int currentValue = await context.Entities.CallEntityAsync<int>(entityId, nameof(Counter.Get));
return currentValue;
}
}
Entidades de acceso
Acceda a las entidades mediante la comunicación unidireccional o bidireccional:
- Llamar a una entidad utiliza la comunicación bidireccional. Envíe un mensaje de operación a la entidad y espere el mensaje de respuesta antes de continuar. El mensaje de respuesta proporciona un valor de resultado o un error (por ejemplo, un error de JavaScript o una excepción de .NET).
- Señalización una entidad usa comunicación unidireccional (disparo y olvido). Envíe un mensaje de operación, pero no espere una respuesta. El tiempo de ejecución garantiza la entrega, pero el remitente no puede observar los valores de resultado ni los errores.
Acceda a entidades desde funciones cliente, funciones de orquestador o funciones de entidad. No todos los contextos admiten ambos tipos de comunicación:
- Las funciones del cliente admiten el soporte de señalización para entidades y la lectura del estado de las entidades.
- Las funciones de orquestador admiten la señalización y la invocación de entidades.
- Las funciones de entidad son compatibles con entidades que envían señales.
Acceda a las entidades mediante la comunicación unidireccional o bidireccional:
- Llamar a una entidad utiliza la comunicación bidireccional. Envíe un mensaje de operación a la entidad y espere el mensaje de respuesta antes de continuar. El mensaje de respuesta proporciona un valor de resultado o un error.
- La señalización de una entidad usa la comunicación unidireccional (fuego y olvido). Envíe un mensaje de operación, pero no espere una respuesta. El tiempo de ejecución garantiza la entrega, pero el remitente no puede observar los valores de resultado ni los errores.
Acceder a entidades desde clientes u orquestadores. No todos los contextos admiten ambos tipos de comunicación:
- Los clientes admiten la señalización de entidades y la lectura del estado de las entidades.
- Los orquestadores admiten la señalización y la llamada a entidades.
En los ejemplos siguientes se muestra cómo acceder a las entidades.
Ejemplo: el cliente señala una entidad
Para acceder a entidades desde una función de Azure normal, que también se conoce como función de cliente, use el enlace de cliente entity. En el ejemplo siguiente se muestra una función desencadenada por la cola que señala una entidad usando esta vinculación.
Nota
Para simplificar, en los siguientes ejemplos se muestra la sintaxis de tipo flexible para tener acceso a las entidades. En general, accede a las entidades a través de interfaces porque proporcionan más comprobación de tipos.
En proceso:
En proceso:
[FunctionName("AddFromQueue")]
public static Task Run(
[QueueTrigger("durable-function-trigger")] string input,
[DurableClient] IDurableEntityClient client)
{
// Entity operation input comes from the queue message content.
var entityId = new EntityId(nameof(Counter), "myCounter");
int amount = int.Parse(input);
return client.SignalEntityAsync(entityId, "Add", amount);
}
Proceso de trabajo aislado:
[Function("AddFromQueue")]
public static Task Run(
[QueueTrigger("durable-function-trigger")] string input,
[DurableClient] DurableTaskClient client)
{
// Entity operation input comes from the queue message content.
var entityId = new EntityInstanceId(nameof(Counter), "myCounter");
int amount = int.Parse(input);
return client.Entities.SignalEntityAsync(entityId, "Add", amount);
}
El término señal significa que la invocación de la API de entidad es unidireccional y asincrónica. Una función cliente no puede saber cuándo la entidad procesa la operación. La función cliente no puede observar los valores de resultado ni las excepciones.
Para acceder a las entidades desde un cliente, use el DurableTaskClient para señalar o leer el estado de la entidad.
// Signal an entity
var entityId = new EntityInstanceId(nameof(Counter), "myCounter");
await client.Entities.SignalEntityAsync(entityId, nameof(Counter.Add), 1);
El término señal significa que la invocación de la API de entidad es unidireccional y asincrónica. Un cliente no puede saber cuándo la entidad procesa la operación.
Ejemplo: un cliente lee el estado de una entidad
Consulta de un estado de entidad desde una función de cliente:
En proceso:
[FunctionName("QueryCounter")]
public static async Task<HttpResponseMessage> Run(
[HttpTrigger(AuthorizationLevel.Function)] HttpRequestMessage req,
[DurableClient] IDurableEntityClient client)
{
var entityId = new EntityId(nameof(Counter), "myCounter");
EntityStateResponse<JObject> stateResponse = await client.ReadEntityStateAsync<JObject>(entityId);
return req.CreateResponse(HttpStatusCode.OK, stateResponse.EntityState);
}
Proceso de trabajo aislado:
[Function("QueryCounter")]
public static async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Function)] HttpRequestData req,
[DurableClient] DurableTaskClient client)
{
var entityId = new EntityInstanceId(nameof(Counter), "myCounter");
EntityMetadata<int>? entity = await client.Entities.GetEntityAsync<int>(entityId);
if (entity is null)
{
return req.CreateResponse(HttpStatusCode.NotFound);
}
HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(entity);
return response;
}
Los clientes pueden consultar el estado de una entidad:
var entityId = new EntityInstanceId(nameof(Counter), "myCounter");
EntityMetadata<int>? entity = await client.Entities.GetEntityAsync<int>(entityId);
if (entity != null)
{
Console.WriteLine($"Current value: {entity.State}");
}
Las consultas de estado de entidad se envían al almacenamiento de seguimiento continuo y devuelven el estado más reciente persistido de la entidad. Este estado siempre es un estado "confirmado", es decir, nunca se presupone un estado intermedio temporal en medio de la ejecución de una operación. Pero este estado puede estar obsoleto en comparación con el estado en memoria de la entidad. Solo las orquestaciones pueden leer el estado en memoria de una entidad, tal y como se describe en la sección siguiente.
Ejemplo: señales de orquestación y llamadas a una entidad
Las funciones de orquestación pueden acceder a las entidades mediante las API en el enlace del desencadenador de orquestación. En el siguiente código de ejemplo se muestra una función orquestadora llamando y señalando una entidad Counter.
En proceso:
[FunctionName("CounterOrchestration")]
public static async Task Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
var entityId = new EntityId(nameof(Counter), "myCounter");
// Two-way call to the entity which returns a value - awaits the response
int currentValue = await context.CallEntityAsync<int>(entityId, "Get");
if (currentValue < 10)
{
// One-way signal to the entity which updates the value - does not await a response
context.SignalEntity(entityId, "Add", 1);
}
}
Proceso de trabajo aislado:
[Function("CounterOrchestration")]
public static async Task Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
var entityId = new EntityInstanceId(nameof(Counter), "myCounter");
// Two-way call to the entity which returns a value - awaits the response
int currentValue = await context.Entities.CallEntityAsync<int>(entityId, "Get");
if (currentValue < 10)
{
// One-way signal to the entity which updates the value - does not await a response
await context.Entities.SignalEntityAsync(entityId, "Add", 1);
}
}
Solo las orquestaciones pueden llamar a entidades y obtener una respuesta, que puede ser un valor devuelto o una excepción. Las funciones de cliente que usan el enlace de cliente solo pueden indicar entidades.
Los orquestadores pueden acceder a entidades mediante la API de entidades del contexto:
public class CounterOrchestration : TaskOrchestrator<string, int>
{
public override async Task<int> RunAsync(TaskOrchestrationContext context, string entityKey)
{
var entityId = new EntityInstanceId(nameof(Counter), entityKey);
// Two-way call to the entity which returns a value - awaits the response
int currentValue = await context.Entities.CallEntityAsync<int>(entityId, nameof(Counter.Get));
if (currentValue < 10)
{
// One-way signal to the entity - does not await a response
await context.Entities.SignalEntityAsync(entityId, nameof(Counter.Add), 1);
}
return currentValue;
}
}
Solo los orquestadores pueden llamar a entidades y obtener una respuesta, que puede ser un valor devuelto o una excepción. Los clientes solo pueden indicar entidades.
Nota
Llamar a una entidad desde un orquestador es similar a llamar a una actividad. La principal diferencia es que las entidades son objetos duraderos con una dirección (el identificador de entidad) y admiten la especificación de un nombre de operación. Las actividades no tienen estado y no tienen el concepto de operaciones.
Ejemplo: una entidad señala a una entidad
Una función de entidad puede enviar señales a otras entidades, o incluso a sí misma, mientras ejecuta una operación.
Por ejemplo, modifique el ejemplo de entidad anterior Counter para enviar una señal de "hito alcanzado" a una entidad de supervisión cuando el contador alcance 100.
En proceso:
case "add":
var currentValue = ctx.GetState<int>();
var amount = ctx.GetInput<int>();
if (currentValue < 100 && currentValue + amount >= 100)
{
ctx.SignalEntity(new EntityId("MonitorEntity", ""), "milestone-reached", ctx.EntityKey);
}
ctx.SetState(currentValue + amount);
break;
Proceso de trabajo aislado:
case "add":
var currentValue = operation.State.GetState<int>();
var amount = operation.GetInput<int>();
if (currentValue < 100 && currentValue + amount >= 100)
{
operation.Context.SignalEntity(new EntityInstanceId("MonitorEntity", ""), "milestone-reached", operation.Context.EntityInstanceId);
}
operation.State.SetState(currentValue + amount);
break;
Coordinación de entidades
A veces, es necesario coordinar las operaciones entre varias entidades. Por ejemplo, en una aplicación bancaria, las entidades pueden representar cuentas bancarias individuales. Al transferir fondos de una cuenta a otra, debe asegurarse de que la cuenta de origen tiene suficientes fondos. También debe actualizar ambas cuentas como una operación coherente.
Ejemplo: Transferencia de fondos (C#)
En el siguiente código de ejemplo se transfieren fondos entre dos entidades de cuenta mediante una función de orquestador. Para coordinar las actualizaciones de entidades, use el LockAsync método para crear una sección crítica en la orquestación.
Nota
Para simplificar, en este ejemplo se reutiliza la Counter entidad definida anteriormente. En una aplicación real, es mejor definir una entidad más detallada BankAccount .
// This is a method called by an orchestrator function
public static async Task<bool> TransferFundsAsync(
string sourceId,
string destinationId,
int transferAmount,
IDurableOrchestrationContext context)
{
var sourceEntity = new EntityId(nameof(Counter), sourceId);
var destinationEntity = new EntityId(nameof(Counter), destinationId);
// Create a critical section to avoid race conditions.
// No operations can be performed on either the source or
// destination accounts until the locks are released.
using (await context.LockAsync(sourceEntity, destinationEntity))
{
ICounter sourceProxy =
context.CreateEntityProxy<ICounter>(sourceEntity);
ICounter destinationProxy =
context.CreateEntityProxy<ICounter>(destinationEntity);
int sourceBalance = await sourceProxy.Get();
if (sourceBalance >= transferAmount)
{
await sourceProxy.Add(-transferAmount);
await destinationProxy.Add(transferAmount);
// the transfer succeeded
return true;
}
else
{
// the transfer failed due to insufficient funds
return false;
}
}
}
En .NET, LockAsync devuelve IDisposable. Al eliminarlo finaliza la sección crítica. Úselo con un using bloque para representar la sección crítica.
En el ejemplo anterior, una función de orquestador transfiere fondos de una entidad de origen a una entidad de destino. El LockAsync método bloqueó tanto las entidades de cuenta de origen como de destino. Este bloqueo garantiza que ningún otro cliente pueda consultar o modificar el estado de cualquiera de las cuentas hasta que la lógica de orquestación salga de la sección crítica al final de la using instrucción. Este comportamiento evita sobregráftos en la cuenta de origen.
Nota
Cuando finaliza una orquestación, normalmente o con un error, las secciones críticas en curso terminan implícitamente y el sistema libera todos los bloqueos.
Comportamiento de la sección crítica
El LockAsync método crea una sección crítica en una orquestación. Estas secciones críticas impiden que otras orquestaciones realicen cambios superpuestos en un conjunto especificado de entidades. Internamente, la LockAsync API envía operaciones de "bloqueo" a las entidades y devuelve tras recibir una respuesta de "bloqueo adquirido" de cada entidad. Bloquear y desbloquear son operaciones estándar soportadas por todas las entidades.
Otros clientes no pueden ejecutar operaciones en una entidad mientras están bloqueadas. Este comportamiento garantiza que solo una instancia de orquestación pueda bloquear una entidad a la vez. Si un llamante intenta invocar una operación en una entidad mientras está bloqueada por una orquestación, esa operación se coloca en una cola de operaciones pendientes. No se procesa ninguna operación pendiente hasta que la orquestación que la contiene libera el bloqueo.
Nota
Este comportamiento es ligeramente diferente de los primitivos de sincronización usados en la mayoría de los lenguajes de programación, como la lock instrucción en C#. Por ejemplo, en C#, todos los subprocesos deben usar la lock instrucción para garantizar la sincronización adecuada. Sin embargo, las entidades no requieren que todos los invocadores bloqueen explícitamente una entidad. Si algún llamador bloquea una entidad, todas las demás operaciones de esa entidad se bloquean y se ponen en cola detrás de ese bloqueo.
Los bloqueos en entidades son duraderos, por lo que se conservan incluso si se recicla el proceso de ejecución. El sistema conserva los bloqueos como parte del estado duradero de una entidad.
A diferencia de las transacciones, las secciones críticas no revierten automáticamente los cambios cuando se producen errores. En su lugar, escriba el control de errores, como la reversión o el reintento, por ejemplo, detectando errores o excepciones. Esta opción de diseño es intencionada. La reversión automática de todos los efectos de una orquestación es difícil o imposible en general, ya que las orquestaciones pueden ejecutar actividades y realizar llamadas a servicios externos que no se pueden revertir. Además, los intentos de revertir podrían fallar y requerir un mayor control de errores.
Reglas de secciones críticas
A diferencia de los primitivos de bloqueo de bajo nivel en la mayoría de los lenguajes de programación, se garantiza que las secciones críticas no interbloqueen. Para evitar los interbloqueos, se aplican las siguientes restricciones:
- Las secciones críticas no se pueden anidar.
- Las secciones críticas no pueden crear suborquestraciones.
- Las secciones críticas solo pueden invocar a las entidades que bloquean.
- Las secciones críticas no pueden llamar a la misma entidad mediante varias llamadas paralelas.
- Las secciones críticas solo pueden indicar entidades fuera del conjunto de bloqueos.
Las infracciones de estas reglas provocan un error en tiempo de ejecución, como LockingRulesViolationException en .NET, que incluye un mensaje que explica qué regla se ha interrumpido.
Comparación con actores virtuales
Las entidades duraderas usan muchas ideas del modelo de actor. Si está familiarizado con los actores, puede reconocer varios conceptos en este artículo. Las entidades duraderas son similares a los actores virtuales, también llamados granos, del proyecto orleans. Por ejemplo:
- Las entidades duraderas se direccionan mediante un identificador de entidad.
- Las operaciones de entidad duradera se ejecutan secuencialmente para prevenir condiciones de carrera.
- Llamar a una entidad o indicarla lo crea implícitamente.
- El tiempo de ejecución descarga entidades de la memoria cuando no ejecutan operaciones.
Entre las principales diferencias se incluyen:
- Las entidades duraderas priorizan la durabilidad sobre la latencia, por lo que es posible que no se ajusten a las aplicaciones con requisitos estrictos de latencia.
- Las entidades duraderas no provocan el vencimiento de los mensajes. Orleans agota el tiempo de espera de los mensajes después de un período configurable (30 segundos de forma predeterminada).
- Las entidades entregan mensajes de forma confiable y en orden. Orleans admite la entrega confiable y ordenada de los mensajes en flujo, pero no garantiza esto para todos los mensajes entre grains.
- Las orquestaciones son el único lugar en el que puede usar request-response con entidades. Dentro de una entidad, use mensajería unidireccional (señalización), como en el modelo de actor original.
- Las entidades duraderas no entran en interbloqueo. Orleans puede experimentar un bloqueo mutuo, y estos persisten hasta que los mensajes agoten el tiempo de espera.
- Las entidades duraderas funcionan con orquestaciones duraderas y admiten mecanismos de bloqueo distribuido.