Partilhar via


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 ConfirmEventsexplicitamente, 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.