Aspectos básicos de JournaledGrain
Los granos en diario se derivan de JournaledGrain<TGrainState,TEventBase>, con los parámetros de tipo siguientes:
TGrainState
representa el estado del grano. Debe ser una clase con un constructor predeterminado público.TEventBase
es un supertipo común para todos los eventos que se pueden generar para este grano y puede ser cualquier clase o interfaz.
Todos los objetos de estado y evento deben ser serializables (ya que es posible que los proveedores de coherencia del registro necesiten conservarlos o enviarlos en mensajes de notificación).
En el caso de los granos cuyos eventos son POCO (objetos C# antiguos sin formato), JournaledGrain<TGrainState> se puede usar como abreviatura de JournaledGrain<TGrainState,TEventBase>.
Lectura del estado de grano
Para leer el estado de grano actual y determinar su número de versión, JournaledGrain tiene propiedades.
GrainState State { get; }
int Version { get; }
El número de versión siempre es igual al número total de eventos confirmados y el estado es el resultado de aplicar todos los eventos confirmados al estado inicial. El estado inicial, que tiene la versión 0 (porque no se le han aplicado eventos), viene determinado por el constructor predeterminado de la clase GrainState.
Importante: La aplicación nunca debe modificar directamente el objeto devuelto por State
. Está destinado únicamente a la lectura. Cuando la aplicación quiere modificar el estado, debe hacerlo indirectamente mediante la generación de eventos.
Generar eventos
La generación de eventos se realiza mediante una llamada a la función RaiseEvent. Por ejemplo, un grano que representa un chat puede generar un PostedEvent
para indicar que un usuario envió una publicación:
RaiseEvent(new PostedEvent()
{
Guid = guid,
User = user,
Text = text,
Timestamp = DateTime.UtcNow
});
Tenga en cuenta que RaiseEvent
inicia una escritura en el acceso de almacenamiento, pero no espera a que se complete la escritura. Para muchas aplicaciones, es importante esperar hasta que tengamos confirmación de que el evento se ha conservado. En ese caso, siempre seguimos esperando ConfirmEvents:
RaiseEvent(new DepositTransaction()
{
DepositAmount = amount,
Description = description
});
await ConfirmEvents();
Tenga en cuenta que aunque no llame explícitamente a ConfirmEvents
, los eventos se confirmarán finalmente; esto sucede automáticamente en segundo plano.
Métodos de transición de estado
El entorno de ejecución actualiza el estado del grano automáticamente cada vez que se generan eventos. No es necesario que la aplicación actualice explícitamente el estado después de generar un evento. Sin embargo, la aplicación todavía tiene que proporcionar el código que especifica cómo actualizar el estado en respuesta a un evento. Esto se puede llevar a cabo de dos maneras.
a) La clase GrainState puede implementar uno o varios métodos Apply
en StateType
. Normalmente, se crearían varias sobrecargas, y se elige la coincidencia más cercana para el tipo de entorno de ejecución del evento:
class GrainState
{
Apply(E1 @event)
{
// code that updates the state
}
Apply(E2 @event)
{
// code that updates the state
}
}
b) El grano puede invalidar la función TransitionState
:
protected override void TransitionState(
State state, EventType @event)
{
// code that updates the state
}
Se supone que los métodos de transición no tienen más efectos secundarios que la modificación del objeto de estado, y deben ser deterministas (de lo contrario, los efectos son imprevisibles). Si el código de transición produce una excepción, esa excepción se detecta e incluye en una advertencia en el registro de Orleans, emitida por el proveedor de coherencia de registros.
El momento exacto en que el entorno de ejecución llama a los métodos de transición depende del proveedor de coherencia de registros elegido y de su configuración. Las aplicaciones no deberían depender de un tiempo determinado, excepto cuando el proveedor de coherencia de registros lo garantice específicamente.
Algunos proveedores, como el proveedor de coherencia de registros Orleans.EventSourcing.LogStorage, reproducen la secuencia de eventos cada vez que se carga el grano. Por lo tanto, mientras los objetos de evento se puedan seguir deserializando correctamente desde el almacenamiento, es posible modificar radicalmente la clase GrainState
y los métodos de transición. Pero para otros proveedores, como el proveedor de coherencia de registros Orleans.EventSourcing.StateStorage, solo se conserva el objeto GrainState
, por lo que los desarrolladores deben asegurarse de que se puede deserializar correctamente cuando se lee desde el almacenamiento.
Generación de varios eventos
Es posible realizar varias llamadas a RaiseEvent
antes de llamar a ConfirmEvents
:
RaiseEvent(e1);
RaiseEvent(e2);
await ConfirmEvents();
Sin embargo, es probable que esto provoque dos accesos sucesivos al almacenamiento, e incurre en el riesgo de que el grano produzca un error después de escribir solo el primer evento. Por lo tanto, normalmente es mejor generar varios eventos a la vez, mediante
RaiseEvents(IEnumerable<EventType> events)
Esto garantiza que la secuencia de eventos especificada se escribe en el almacenamiento de forma atómica. Tenga en cuenta que, dado que el número de versión siempre coincide con la longitud de la secuencia de eventos, la generación de varios eventos aumenta el número de versión en más de uno cada vez.
Recuperación de la secuencia de eventos
El método siguiente de la clase base JournaledGrain
permite a la aplicación recuperar un segmento especificado de la secuencia de todos los eventos confirmados:
Task<IReadOnlyList<EventType>> RetrieveConfirmedEvents(
int fromVersion,
int toVersion);
Sin embargo, no es compatible con todos los proveedores de coherencia de registros. Si no se admite o si el segmento especificado de la secuencia ya no está disponible, se produce NotSupportedException.
Para recuperar todos los eventos hasta la versión confirmada más reciente, se llamaría a
await RetrieveConfirmedEvents(0, Version);
Solo se pueden recuperar eventos confirmados: se produce una excepción si toVersion
es mayor que el valor actual de la propiedad Version
.
Dado que los eventos confirmados nunca cambian, no hay carreras de las que preocuparse, incluso en presencia de múltiples instancias o de una confirmación retrasada. Sin embargo, en estas situaciones, el valor de la propiedad Version
puede ser mayor en el momento en que se reanuda await
que en el momento en que se llama a RetrieveConfirmedEvents
, por lo que puede ser aconsejable guardar su valor en una variable. Consulte también la sección sobre garantías de simultaneidad.