Compartilhar via


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.