/events-protocol

Primary LanguageKotlinApache License 2.0Apache-2.0

Events Protocol

Build Status Download

Dependencias

Para utilizar as dependências é necessário habilitar o repositório do jcenter no gradle:

repositories {
    mavenCentral()
    jcenter()  <---------
}

A biblioteca é composta de 3 modulos. Server, Client e Core.

 compile "br.com.guiabolso:events-client:2.0.0"
 compile "br.com.guiabolso:events-server:2.0.0"
 compile "br.com.guiabolso:events-core:2.0.0"

Geralmente as dependências a serem importadas são:

  • Server: Quando é necessário tratar requisições de eventos;
  • Client: Quando é necessário realizar requisições de evento;
  • Core: Quando é necessário acessar alguma classe específica usada pelo Server ou pelo Client, a partir de um modulo separado.

Motivação

Padronizar a comunicação entre as aplicações do GuiaBolso. Atualmente cada aplicação é livre para definir seus padrões de request/response e protocolo: Thrift, REST, "JSON over HTTP", etc. O que torna difícil trafegar dados padronizados (trackingID, por exemplo) entre as aplicações.

Essa proposta visa definir um padrão de comunicação único que permita:

  • Identificação única dos requests
  • Transações com requests/responses assíncronos ou síncronos
  • Envio de dados que devem passar por toda a stack (trackingID, por exemplo)
  • Versionamento de API
  • identificação de um usuário
  • campos relacionados a autenticação do usuário.
  • Metadados de transações (origin, timestamp, etc)

Arquitetura

Evento

Toda informação é transmitida utilizando um JSON padronizado com o seguinte formato. Qualquer comunicação entre sistemas é considerado um evento.

Exemplo:

{
	"name": "some event",
	"version": 42,
	"id": "2465a86f-3857-423e-af86-41f67880172f",
	"flowId": "d00a5c99-ea0e-4b39-bfdc-bf1028a9c95f",
	"payload": {
		"data1": "fsadfsdf",
		"an_int64": 65484548474984654,
		"some_float": 1864.4568
	},
	"identity": {
		"userId": 99999999
	},
	"auth": {
		"token": "fk20f2p9o2v2l923"
	}
	"metadata": {
		"origin": "Documentation",
		"originId": "RFC-GB 0001",
		"timestamp": 1482162952
	}
}

Como usar

Cliente

TODO

Servidor

O protocolo foi desenhado para ser facilmente acoplado em qualquer estrutura para receber requisições: sockets, chamadas HTTP. Para tanto, dois tipos de objetos precisam ser cadastrados na sua aplicação para que tudo funcione.

EventHandlerDiscovery

É o responsável por avaliar se, dado o nome e versão de um evento, o sistema é capaz de fazer o tratamento do mesmo. A implementação padrão da interface é SimpleEventHandlerRegistry que cadastra uma série de EventHandler's (ou lambdas) para fazer o tratamento de um determinado evento pelo seu nome e versão. Em ambos os casos, um RequestEvent é recebido e um ResponseEvent é gerado baseado na requisição e a resposta do processamento realizado (se necessário).

Exemplo:

simpleEventHandlerRegistry.add("say:hello", 13, (EventHandler) { RequestEvent event ->
	def firstname = event.payload.asJsonObject.get("firstname").asString
	def lastname = event.payload.asJsonObject.get("lastname").asString
	def eventBuilder = EventBuilder.javaResponseFor(event)
	eventBuilder.payload = helloService.to(firstname, lastname)
	eventBuilder.buildResponseEvent()
})

EventProcessor

Responsável por efetivamente processar os eventos. O evento nessa classe é representado por uma String que é traduzido para um objeto no modelo do protocolo de eventos e avaliado para garantir se há ou não suporte para que seja tratado. Um EventHandlerDiscovery que avalia qual o processador para um dado evento deve ser passado como parâmetro para esse objeto. É nele ainda que os tratamentos de erro e avaliação de métricas podem ser sobrescritos.

Exemplo:

post("/events", { request, response ->
	eventProcessor.processEvent(req.body())
})

Nota: no caso de chamadas via HTTP, recomenda-se que se crie uma única rota para tratamento de eventos (vide exemplo) para que todos os eventos sejam processados de maneira igual. Quem define a rota do evento nesse caso é o EventProcessor.

Tipos padrão de resposta

O protocolo de eventos se baseia na seguinte premissa. O cliente envia um evento para ser processado pelo servidor. O servidor devolve um outro evento como resposta ao cliente. Para isso existem tipos padrão de resposta.

Response

Tem o objetivo de definir o padrão das mensagens de sucesso da plataforma. Campos fixos no evento:

  • name: some:event:response
  • version: 1

Exemplo:

{
	"name": "some event:response",
	"version": 1,
	"id": "e577b9d8-5b39-4aa6-bd74-418ec2f74174",
	"flowId": "49689dcf-80c4-45a6-9f82-61a240e49a5c",
	"payload": {
		"someData": "this is the server response",
		"someOtherData": 42
	},
	"metadata": {
		"origin": "Lion",
		"timestamp": 1282162952
	}
}

Erro genérico:

Tem o objetivo de definir o padrão do payload das mensagens de erro genéricos da plataforma. Campos fixos no evento:

  • name: some:event:error
  • version: 1

Exemplo:

{
	"name": "some:event:error",
	"version": 1,
	"id": "e577b9d8-5b39-4aa6-bd74-418ec2f74174",
	"flowId": "49689dcf-80c4-45a6-9f82-61a240e49a5c",
	"payload": {
		"code": "INVALID_EMAIL",
		"parameters": {
			"email": "wrong@gbmail.com"
		}
	},
	"metadata": {
		"origin": "Midgard",
		"timestamp": 1282162952
	}
}

Erros contextualizados:

Tem o objetivo de definir o padrão do payload das mensagens de erros que carregam um contexto dentro da plataforma.

Esses erros podem ser:

Bad Request:

Indica que o request foi feito ao servidor de maneira incorreta. Provavelmente com algum parametro do request faltando ou e formato incorreto.

Exemplo:

{
	"name": "some:event:bad_request",
	"version": 1,
	"id": "e577b9d8-5b39-4aa6-bd74-418ec2f74174",
	"flowId": "49689dcf-80c4-45a6-9f82-61a240e49a5c",
	"payload": {
		"code": "MISSING_FIELDS",
		"parameters": {
			"fields": ["cpf", "phone"]
		}
	},
	"metadata": {
		"origin": "Midgard",
		"timestamp": 1282162952
	}
}

Unauthorized:

Indica que o cliente não está autorizado acessar aquela API no servidor. Provavelmente o cliente não está autenticado e precisa passar pelo processo de login.

Exemplo:

{
	"name": "some:event:unauthorized",
	"version": 1,
	"id": "e577b9d8-5b39-4aa6-bd74-418ec2f74174",
	"flowId": "49689dcf-80c4-45a6-9f82-61a240e49a5c",
	"payload": {
		"code": "MISSING_TOKENS",
		"parameters": {
		}
	},
	"metadata": {
		"origin": "Midgard",
		"timestamp": 1282162952
	}
}

Not Found:

Indica que o servidor não encontrou o recurso solicitado pelo cliente.

Exemplo:

{
	"name": "some:event:not_found",
	"version": 1,
	"id": "e577b9d8-5b39-4aa6-bd74-418ec2f74174",
	"flowId": "49689dcf-80c4-45a6-9f82-61a240e49a5c",
	"payload": {
		"code": "NO_OFFERS",
		"parameters": {
		}
	},
	"metadata": {
		"origin": "Midgard",
		"timestamp": 1282162952
	}
}

Forbidden:

Indica que o servidor impede o acesso a um recurso devido ser proibido por algum motivo.

Exemplo:

{
	"name": "some:event:forbidden",
	"version": 1,
	"id": "e577b9d8-5b39-4aa6-bd74-418ec2f74174",
	"flowId": "49689dcf-80c4-45a6-9f82-61a240e49a5c",
	"payload": {
		"code": "NOT_ALLOWED",
		"parameters": {
		}
	},
	"metadata": {
		"origin": "Midgard",
		"timestamp": 1282162952
	}
}

Failed Dependency:

Tipo padronizado para indicar que não foi possível responder com sucesso pois algum worker/dependência não respondeu corretamente (base de dados não são consideradas dependências/workers).

Exemplo:

{
	"name": "some:event:failed_dependency",
	"version": 1,
	"id": "d8cab3c2-b15b-439b-ae5b-c8a96c61d637",
	"flowId": "49689dcf-80c4-45a6-9f82-61a240e49a5c",
	"payload": {
		"code": "UNKNOWN_ERROR"
		"message": "Anubis returned an error",
	},
	"metadata": {
		"origin": "Ryzen",
		"timestamp": 1482165952
	}
}

Referências