Conceitos básicos de JournaledGrain
A granularidade em diário deriva de JournaledGrain<TGrainState,TEventBase>, com os seguintes parâmetros de tipo:
- O
TGrainState
representa o estado da granularidade. Deve ser uma classe com um construtor padrão público. TEventBase
é um supertipo comum para todos os eventos que podem ser gerados para esse 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 persisti-los e/ou enviá-los em mensagens de notificação).
Para granularidade cujos eventos são POCOs (objetos C# simples e antigos), JournaledGrain<TGrainState> pode ser usado como um atalho para JournaledGrain<TGrainState,TEventBase>.
Lendo o estado da granularidade
Para ler o estado da granularidade atual e determinar seu número de versão, o JournaledGrain possui 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 por State
. Destina-se apenas à leitura. Em vez disso, quando o aplicativo deseja modificar o estado, ele deve fazê-lo indiretamente, gerando eventos.
Acionar eventos
A geração de eventos é realizada chamando a função RaiseEvent. Por exemplo, um grão representando um chat 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 RaiseEvent
inicia um acesso de gravação ao armazenamento, mas não espera a conclusão da gravação. Para muitos aplicativos, é importante esperar até termos a confirmação de que o evento persistiu. Nesse caso, sempre acompanhamos esperando por ConfirmEvents:
RaiseEvent(new DepositTransaction()
{
DepositAmount = amount,
Description = description
});
await ConfirmEvents();
Observe que mesmo que você não chame explicitamente ConfirmEvents
, os eventos eventualmente serão confirmados - isso acontece automaticamente em segundo plano.
Métodos de transição de estado
O tempo de execução atualiza o estado de granulação automaticamente sempre que os eventos são gerados. Não há necessidade de o aplicativo atualizar explicitamente o estado após gerar um evento. No entanto, o aplicativo ainda precisa fornecer o código que especifica como atualizar o estado em resposta a um evento. Isso pode ser feito de duas maneiras.
(a) A classe GrainState pode implementar um ou mais métodos Apply
no StateType
. Normalmente, seria criado 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) A granulação pode substituir a função TransitionState
:
protected override void TransitionState(
State state, EventType @event)
{
// code that updates the state
}
Supõe-se que os métodos de transição não tenham 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 e incluída em um aviso no log do Orleans, emitido pelo provedor de consistência de log.
Quando, exatamente, o runtime chama os métodos de transição depende do provedor de consistência de log escolhido e de sua configuração. Os aplicativos não devem depender de um tempo específico, exceto quando garantido especificamente pelo provedor de consistência de log.
Alguns provedores, como o provedor de consistência de log Orleans.EventSourcing.LogStorage, reproduzem a sequência de eventos sempre que a granulação é carregada. Portanto, desde que os objetos de evento ainda possam ser desserializados adequadamente do armazenamento, é possível modificar radicalmente a classe GrainState
e os métodos de transição. Mas para outros provedores, como o provedor de consistência de log Orleans.EventSourcing.StateStorage, apenas o objeto GrainState
é persistido, 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 chamar ConfirmEvents
:
RaiseEvent(e1);
RaiseEvent(e2);
await ConfirmEvents();
No entanto, é provável que isso cause dois acessos de armazenamento sucessivos e incorre no risco de que o grão falhe após gravar apenas o primeiro evento. Assim, geralmente é melhor gerar vários eventos de uma só vez, usando
RaiseEvents(IEnumerable<EventType> events)
Isso garante que a sequência de eventos fornecida seja gravada no armazenamento atomicamente. Observe que, como o número da versão sempre corresponde ao comprimento da sequência de eventos, o aumento de vários eventos aumenta o número da versão em mais de um por 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, 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á gerado.
Para recuperar todos os eventos até a última versão confirmada, pode-se chamar
await RetrieveConfirmedEvents(0, Version);
Somente eventos confirmados podem ser recuperados: uma exceção é lançada se toVersion
for maior que o valor atual da propriedade Version
.
Como os eventos confirmados nunca mudam, não há corridas para se preocupar, mesmo na presença de várias instâncias ou confirmação atrasada. No entanto, em tais situações, o valor da propriedade Version
pode ser maior no momento em que await
é retomado do que no momento em que RetrieveConfirmedEvents
é chamado, portanto, pode ser aconselhável salvar seu valor em uma variável. Consulte também a seção sobre Garantias de Simultaneidade.