Noções básicas do JournaledGrain
Os grãos registrados no diário derivam de , com os seguintes parâmetros de JournaledGrain<TGrainState,TEventBase>tipo:
- O
TGrainState
representa o estado do grão. Deve ser uma classe com um construtor padrão público. TEventBase
é um supertipo comum para todos os eventos que podem ser levantados para este grão, e pode ser qualquer classe ou interface.
Todos os objetos de estado e evento devem ser serializáveis (porque os provedores de consistência de log podem precisar mantê-los e/ou enviá-los em mensagens de notificação).
Para grãos cujos eventos são POCOs (objetos C# antigos simples), JournaledGrain<TGrainState> pode ser usado como uma abreviação para JournaledGrain<TGrainState,TEventBase>.
Leitura do estado do grão
Para ler o estado atual do grão e determinar seu número de versão, o JournaledGrain tem propriedades
GrainState State { get; }
int Version { get; }
O número da versão é sempre igual ao número total de eventos confirmados, e o estado é o resultado da aplicação de todos os eventos confirmados ao estado inicial. O estado inicial, que tem a versão 0 (porque nenhum evento foi aplicado a ele), é determinado pelo construtor padrão da classe GrainState.
Importante: O aplicativo nunca deve modificar diretamente o objeto retornado pelo State
. Destina-se apenas à leitura. Em vez disso, quando o aplicativo deseja modificar o estado, ele deve fazê-lo indiretamente, gerando eventos.
Aumentar eventos
O aumento de eventos é realizado chamando a RaiseEvent função. Por exemplo, um grão que representa um bate-papo pode gerar um PostedEvent
para indicar que um usuário enviou uma postagem:
RaiseEvent(new PostedEvent()
{
Guid = guid,
User = user,
Text = text,
Timestamp = DateTime.UtcNow
});
Observe que inicia uma gravação no acesso ao armazenamento, mas não espera que RaiseEvent
a gravação seja concluída. Para muitas aplicações, é importante esperar até termos a confirmação de que o evento foi persistido. Nesse caso, seguimos sempre aguardando ConfirmEvents:
RaiseEvent(new DepositTransaction()
{
DepositAmount = amount,
Description = description
});
await ConfirmEvents();
Observe que, mesmo que você não ligue ConfirmEvents
explicitamente, os eventos acabarão sendo confirmados - isso acontece automaticamente em segundo plano.
Métodos de transição de Estado
O tempo de execução atualiza o estado de grão automaticamente sempre que eventos são gerados. Não há necessidade de o aplicativo atualizar explicitamente o estado depois de gerar um evento. No entanto, o aplicativo ainda precisa fornecer o código que especifica como atualizar o estado em resposta a um evento. Isto pode ser feito de duas formas.
a) A GrainState classe pode aplicar um ou mais Apply
métodos no StateType
. Normalmente, criam-se várias sobrecargas e a correspondência mais próxima é escolhida para o tipo de tempo de execução do evento:
class GrainState
{
Apply(E1 @event)
{
// code that updates the state
}
Apply(E2 @event)
{
// code that updates the state
}
}
b) O grão pode sobrepor-se à TransitionState
função:
protected override void TransitionState(
State state, EventType @event)
{
// code that updates the state
}
Os métodos de transição são assumidos como não tendo efeitos colaterais além de modificar o objeto de estado, e devem ser determinísticos (caso contrário, os efeitos são imprevisíveis). Se o código de transição gerar uma exceção, essa exceção será capturada Orleans e incluída em um aviso no log, emitido pelo provedor de consistência de log.
Quando, exatamente, o tempo de execução chama os métodos de transição depende do provedor de consistência de log escolhido e sua configuração. Os aplicativos não devem depender de um tempo específico, exceto quando especificamente garantido pelo provedor de consistência de log.
Alguns provedores, como o provedor de Orleans.EventSourcing.LogStorage consistência de log, reproduzem a sequência de eventos toda vez que o grão é carregado. Portanto, contanto que os objetos de evento ainda possam ser desserializados corretamente do armazenamento, é possível modificar radicalmente a classe e os GrainState
métodos de transição. Mas para outros provedores, como o Orleans.EventSourcing.StateStorage provedor de consistência de log, apenas o GrainState
objeto é persistente, portanto, os desenvolvedores devem garantir que ele possa ser desserializado corretamente quando lido do armazenamento.
Gerar vários eventos
É possível fazer várias chamadas para RaiseEvent
antes de ligar ConfirmEvents
:
RaiseEvent(e1);
RaiseEvent(e2);
await ConfirmEvents();
No entanto, é provável que isso cause dois acessos sucessivos de armazenamento, e incorre no risco de que o grão falhe depois de escrever apenas o primeiro evento. Assim, geralmente é melhor levantar vários eventos ao mesmo tempo, usando
RaiseEvents(IEnumerable<EventType> events)
Isso garante que a sequência dada de eventos seja gravada para armazenamento atomicamente. Observe que, como o número da versão sempre corresponde ao comprimento da sequência de eventos, gerar vários eventos aumenta o número da versão em mais de um de cada vez.
Recuperar a sequência de eventos
O seguinte método da classe base JournaledGrain
permite que o aplicativo recupere um segmento especificado da sequência de todos os eventos confirmados:
Task<IReadOnlyList<EventType>> RetrieveConfirmedEvents(
int fromVersion,
int toVersion);
No entanto, ele não é suportado por todos os provedores de consistência de log. Se não houver suporte, ou se o segmento especificado da sequência não estiver mais disponível, um NotSupportedException será lançado.
Para recuperar todos os eventos até a última versão confirmada, deve-se ligar
await RetrieveConfirmedEvents(0, Version);
Somente eventos confirmados podem ser recuperados: uma exceção é lançada se toVersion
for maior do que o valor atual da propriedade Version
.
Como os eventos confirmados nunca mudam, não há corridas com as quais se preocupar, mesmo na presença de várias instâncias ou confirmação atrasada. No entanto, em tais situações, o valor do imóvel Version
pode ser maior no momento em que os await
currículos são chamados do que no momento RetrieveConfirmedEvents
em que é chamado, por isso pode ser aconselhável guardar seu valor em uma variável. Consulte também a secção sobre Garantias de Concorrência.