Páginas filhas
  • Integração RM x SmartLink Behavior Sharing - Visão desenvolvedor

Versões comparadas

Chave

  • Esta linha foi adicionada.
  • Esta linha foi removida.
  • A formatação mudou.

Índice
maxLevel2

01. Apresentação

Este documento técnico tem por objetivo auxiliar os desenvolvedores dos segmentos na criação dos provedores de proteção de dados da linha TOTVS RM de classes que "publicam" e/ou "consomem" mensagens através da plataforma Totvs SmartLink.

02. Responsabilidades

O desenvolvimento e manutenção dos provedores de proteção de dados publicadores e consumidores de mensagens são de responsabilidade das equipes de segmentos. Portanto, a escrita das regras de negócio para atendimento a LEI Nº 13.709, DE 14 DE AGOSTO DE 2018 devem ser realizadas dentro dos métodos a serem sobrescritos nesses provedores.

...

Os artefatos genéricos que controlam a fila de execução das mensagens são de responsabilidade da equipe de Framework.

03. Implementando um publicador de mensagens

Para que uma classe seja um provedor publicador de proteção de dadosmensagens, ,   deve-se atender aos pré-requisitos listados abaixo:

Pré-requisitos

  1. Criar um projeto (classLibrary .NET) na solution do segmento em questão, com o seguinte padrão de nome: RM.[Segmento].XXX.DataProtectionSmartLink.ProviderService.
    1. Exemplo: RM.Glb.RHUSGDP.DataProtectionSmartLink.ProviderService.dll
  2. Adicionar referência para a dll "RM.GlbLib.DataProtection.ServiceSmartLink.dll";
  3. Criar uma classe herdando da ancestral "GlbDataProtectionProviderBase". O tipo genérico "Identifier" deve ser usado conforme descrito abaixo.

  4. A classe "GlbDataProtectionProviderBase" herda da classe RMSObject da Lib. Consequentemente, as classes de "providers" poderão chamar os métodos "CreateFacade" e "CreateModule" dentro de suas estruturas.
  5. Carimbar a classe com o atributo "GlbDataProtectionRoleAttr". Nesse atributo, devem ser informados os dados abaixo:
    1. Identificador único do provedor (novo guid);
    2. Nome do papel (essa informação será mostrada para o usuário no sistema LGDP que está sendo criado pela Totvs);
    3. Linha do Produto (RM,TREPORTS, etc)
    4. Aplicação (Folha de Pagament, Educacional, Globais, etc)
    5. ClassName (Nome do Menu Controller do segmento) - Pode ser localizado na tabela GDICDATAPROTECTION coluna CLASSNAME.
    6. CodSistema (Código Sistema do segmento) -Pode ser localizado na tabela GDICDATAPROTECTION coluna CODSISTEMA.
  6. Exemplo:

    Bloco de código
    languagec#
    firstline1
    linenumberstrue
    namespace RM.Glb.TesteUnitario
    {
      [GlbDataProtectionRoleAttr("7b886d91-86b1-4953-ad93-7da732d40515", "Funcionários", "RM", "Fop","FopFolhaActionModuleController", "P")]
      public class RHUDataProtectionProviderFuncionarios<FuncID> :
        GlbDataProtectionProviderBase<FuncionarioIDModel>
        where FuncID : FuncionarioIDModel, new()
      {
      }
    }
    
    
  7. Criar uma classe herdando de "IdentifierModelBase". Trata-se de uma estrutura contendo propriedades de pesquisa de um determinada pessoa.  Esse objeto será usado para localizar uma pessoa na base de dados. As propriedades dessa classe devem ser marcadas com o atributo "GlbDataProtectionIdentificationAttr". Nesse atributo, devem ser informados os seguintes campos:
    1. IdentificationName: Nome do campo físico  a ser pesquisado;
    2. TableName: Nome da tabela física no DB referente ao campo;
    3. Description: Descrição do identificador que aparecerá no SGDP;(Consultar na planilha https://docs.google.com/spreadsheets/d/1uPcsaDQlYOE-x1vAsTAtgpEeLTphI31yXq3Z84QZQ80/edit#gid=0 os tipos que devem ser informados)
    4. RelationTag: Marcação de relacionamento entre os campos. Exemplo: Se a pesquisa for feita pelo campo "CodColigada" + "Chapa" do funcionário,  nesse caso, essas duas propriedades devem ser marcadas com a mesma tag.
  8. Exemplo:

    Bloco de código
    languagec#
    firstline1
    linenumberstrue
    namespace RM.Glb.TesteUnitario
    {
      public class FuncionarioIDModel: IdentifierModelBase
      { 
        [GlbDataProtectionIdentifierAttr("CPF", "PPESSOA", "CPF")]
        public string CPF { get; set; }
       
        [GlbDataProtectionIdentifierAttr("CARTIDENTIDADE", "PPESSOA", "RG")]
        public string Rg { get; set; }
       
        [GlbDataProtectionIdentifierAttr("EMAIL", "PPESSOA", "E-MAIL")]
        public string Email { get; set; }
    
        [GlbDataProtectionIdentifierAttr("CARTMOTORISTA", "PPESSOA", "CNH")]
        public string CNH { get; set; }
      }
    }
    
  9. Métodos que podem ser sobrescritos no provedor:
  10. DoInitialize: método executado logo após a criação do objeto pelo gerenciador. Ele pode ser usado para inicializar dados de instancia da classe de provedor;
  11. DoCanAnonimize: método usado para verificar se uma determinada pessoa pode ou não ser anonimizada. 
  12. DoAfterAnonimize: método usado para executar regras de negócio que serão executadas imediatamente após a execução de todos os "updates" de anonimização realizados pela frame.
  13. DoFillIdentifier: método usado para para preenchimento de todas as propriedades do objeto "Identifier" repassado como parâmetro. Esse mecanismo será usado no processo de correlação entre os provedores. Nesse caso, todas as propriedades da classe de "IdentifierModel" do respectivo provider devem ser preenchidas.
  14. DoConfigAnonimize: método usado para repassar as configurações de tabelas necessárias para a camada de "manager". Através desse metado, a camada core tera condições de gerar automaticamente os updates a serem disparados pelo processo de anonimização.  Os campos anonimizados serão
  15. justamente os campos marcados na tabela GDicDataProtection por tabela.
  16. Informações

    Explicando melhor o método "DoFillIdentifier" que deve ser implementado pelos provedores.

    ***********************************************************************************************************

    Esse método deve ser utilizado para preenchimento das propriedades do objeto de Identifier.

    Esse método receberá como parametro uma instancia de um objeto do tipo "IdentifierModelBase"

    Se por exemplo, digamos que essa classe tenha as propriedades: [CPF, E-MAIL, RG]. 

    Se chegar uma instancia dessa classe com a propridade "CPF" preenchida: ex: CPF = "0154518548".

    Nesse caso, terá que existir um codigo para preenchimento das propriedades: [E-MAIL, RG]..

    Se chegar o "E-MAIL" preenchido, terá que existir um código para preenchimento: [CPF, RG]

    Esse ajuste é necessário para realização correta dos correlacimentos entre os provedores..

    Sem isso, o processo de anonimizçaão nao funcionará.

    Esse método deve ser utilizado para preenchimento das propriedades do objeto de Identifier.

    Esse método receberá como parametro uma instancia de um objeto do tipo "IdentifierModelBase"

    Se por exemplo, digamos que essa classe tenha as propriedades: [CPF, E-MAIL, RG]. 

    Se chegar uma instancia dessa classe com a propridade "CPF" preenchida: ex: CPF = "0154518548".

    Nesse caso, terá que existir um codigo para preenchimento das propriedades: [E-MAIL, RG]..

    Se chegar o "E-MAIL" preenchido, terá que existir um código para preenchimento: [CPF, RG]

  17. Exemplo:
  18. Bloco de código
    languagec#
    firstline1
    linenumberstrue
    using RM.Glb.DataProtection.Service.Domain;
    
    namespace RM.Glb.TesteUnitario
    {
      [GlbDataProtectionRoleAttr("7b886d91-86b1-4953-ad93-7da732d40515", "Funcionários", "RM", "Fop","FopFolhaActionModuleController", "P")]
      public class RHUDataProtectionProviderFuncionario<FuncID> :
      GlbDataProtectionProviderBase<FuncionarioIDModel>
        where FuncID : FuncionarioIDModel, new()
      {
        protected override void DoAfterAnonimize(FuncionarioIDModel identification)
        {
            //regras de negocio logo após a execuão dos updates de anonimização.
        }
    
        protected override CanAnonimizeResult DoCanAnonimize(FuncionarioIDModel identification)
        {
          CanAnonimizeResult result = new CanAnonimizeResult();
    
          if (!string.IsNullOrWhiteSpace(identification.Cpf))
          {
            result.CanAnonimize = false;
            RuleModel rule = new RuleModel();
            rule.Classificacao = "151/15";
            rule.Justificativa = "Não autorizada";
            result.Rules.Add(rule);
          }
          else if (!string.IsNullOrWhiteSpace(identification.Chapa))
          {
            result.CanAnonimize = true;
          }
          return result;
        }
    
        protected override void DoFillIdentifier(FuncionarioIDModel identifier)
        {
          identifier.Chapa = "00001";
          identifier.CodColigada = 1;
          identifier.Rg = "M71545";
          identifier.Nome = "Teste ok";
        }
    
        protected override void DoConfigAnonimize(AnonimizeMapperBuilder config)
        {
           config.AddTable(builder => builder
                            .WithTableName("PPESSOA")
                            .WithExcludedColumns("ESTADOCIVIL")
                            .WithPrimaryKey("CODIGO")
                            .WithDefaultValues("SEXO", "#")
                )
               .AddTable(builder => builder 
                            .WithTableName("PFUNC")
                            .WithPrimaryKey("CODCOLIGADA", "CHAPA")
                            .WithExcludedColumns("CODSECAO")
                            .WithDefaultValues("SITUACAOFGTS", "#")
                )
                .AddTable(builder => builder
                            .WithTableName("PFDEPEND")
                            .WithPrimaryKey("CODCOLIGADA", "CHAPA", "NRODEPENDENTE")
                            .WithExcludedColumns("CARTORIO")
                            .WithDefaultValues("LOCALNASCIMENTO", "ITAGUARA")
                )
                .AddRelation(builder => builder
                            .WithParentTable("PPESSOA")
                            .WithChildTable("PFUNC")
                            .WithParentColumns("CODCOLIGADA", "CODIGO")
                            .WithChildColumns("CODCOLIGADA", "CODPESSOA")
                )
                .AddRelation(builder => builder
                            .WithParentTable("PFUNC")
                            .WithChildTable("PFDEPEND")
                            .WithParentColumns("CODCOLIGADA", "CHAPA")
                            .WithChildColumns("CODCOLIGADA", "CHAPA")
                );
    
          //Para esse caso em especifico, serão gerados as seguinte sentenças sql.
          //Como exemplo, foram marcados os seguintes campos na GDicDataProtection.
          //PESSOA = DTNASCIMENTO, SEXTO, NACIONALIDADE
          //PFUNC = NOME, CODSECAO
          //PFDEPEND = NOME, DTDANSCIMENTO, ESTADOCIVIL
    
          //UPDATE PPESSOA SET DTNASCIMENTO = 01/01/1900, SEXO = '#', NACIONALIDADE = "X"
          //WHERE PPESSOA.CPF = '015454515'
    
          //UPDATE PFUNC SET NOME = 'XXX'
          //FROM PPESSOA
          //WHERE PPESSOA.CODIGO = PFUNC.CODPESSOA
          //AND PPESSOA.CPF = '015454515'
    
          //UPDATE PFDEPEND SET NOME = 'XXX', DTNASCIMENTO = '1999/01/01', ESTADOCIVIL = 'X'
          //FROM PFUNC INNER JOIN PPESSOA
          //  ON PFUNC.CODPESSOA = PPESSOA.CODIGO
          //WHERE PPESSOA.CPF = '015454515'
    
    
        }
      }
    }
    
    
    
  19. Informações

    No exemplo acima, o provider "RHUDataProtectionProviderFuncionario" usa a tabela "PPESSOA" porque no método "DoConfigAnonimize" está sendo usado o config.AddTable(builder => builder .WithTableName("PPESSOA"). Nesse caso, a tabela PPESSOA será anonimizada por ele porque as sentenças "updates" serão geradas para a tabela PPESSOA.

    Para desligar esse comportamento de geração automática da sentenças "UPDATE" basta usar o .WithExcludeUpdateClausule(). Sendo assim, usando o "WithExcludeUpdateClausule" esse provider funcionaria como "ponte" para correlacionamento com outros providers que usa a "PPESSOA".

    Image Removed

Foi criado o endpoint para teste (http://localhost:8051/api/framework/v1/dataprotection/person) para buscar dados de uma pessoa com base no nome do identificador específico do provedor.

Foi criado o endpoint para teste (http://localhost:8051/api/framework/v1/dataprotection/cananonimize) para recuperar se os dados de uma pessoa com base no nome do identificador específico do provedor podem ser anonimizados.

Está sendo criado o endpoint http://localhost:8051/api/framework/v1/dataprotection/anonimize para anonimizar os dados de uma pessoa (está em construção).

Ambos endpoints devem ser enviados por GET, com a seguinte estrutura no body:

{
  "role""Funcionários",
  "identifiers": [
      {
          "IdentifiedName""CPF",
          "Value""00251149641"
      }
  ]
}

Onde:

  • roles = nome do provedor dado pela implementação do produto;
  • IdentifiedName = nome do identificador do provedor de proteção de dados
  • Value = valor do identificador
  • Exemplo de resposta para a API dataprotection/person:

Image Removed

São retornados os dados da pessoa, de acordo com o provedor informado.

  • Exemplo de retorno para a APIdataprotection/cananonimize:

Image Removed

...

  1. que receba em seu construtor instâncias das seguintes interfaces:

    1. "IRMSSmartLinkPublisherService": interface de serviço responsável em incluir a mensagem na fila do RM.SmartLink.Client.

    2. "IRMSLogger": serviço usado para incluir logs relacionados à regra de negócio em questão. 

      Informações

      Todos os logs adicionados nesse serviço serão gravados automaticamente na tabela "GTotvsLinkLog"

    3. Para publicar uma mensagem na fila do RM.SmartLink.Client, basta chamar o método "AddMessage" da instância de interface "IRMSSmartLinkPublisherService". Os seguinte dados devem ser enviados através de uma classe de parâmetros do tipo "SmartLinkPublisherAddMessageParams":

      PropriedadeDescrição
      CommandNome do comando a ser incluído na fila do SmartLink. Ex: SGDPUpdateTenantMetada
      CorrelatedIdPode ser ser enviado nessa propriedade um identificador (guid) que correlacionam mensagens de envio e resposta.
      Data

      Dados da mensagem contendo informações relacionadas ao negócio. Pode ser em qualquer formato (json, xml, etc)  desde que o consumidor consiga interpretá-lo.

      RouterMessage

      Rota de envio da mensagem. Será concatenada ao endpoint do serviço do Totvs SmartLink.Server. ex: /api/v1/link/send/SGDPMaskResponse/SGDP

      Na versão 12.1.2402, que utiliza api REST para envio de mensagem, o valor do Audience é definido no final do RouterMessage. No caso desse exemplo o Audience é o SGDP.
      A partir da versão 12.1.2406 não é necessário passar o RouterMessage como parâmetro pois utiliza o protocolo gRPC para envio de mensagem.

      Audience

      Código do TOTVS App específico que irá receber a mensagem.

      Para versões 12.1.2406 e acima, que já utiliza o gRPC para envio de mensagem, o Audience deve ser passado por esse parâmetro.

  2. Diagrama de classes contendo um publicador de mensagem de exemplo:


Image Added


   5. Diagrama de sequência contendo um exemplo do ciclo de vida da inclusão da mensagem "SGDPTenantMetada"

Image Added

   6. Código fonte de exemplo (extraído da classe "GlbSGDPPublisherMessageService" localizada na solution de Globais, projeto "RM.Glb.SGDP.SmartLink.Service". Esse exemplo é para versões a partir da 12.1.2406 que já utiliza o protocolo gRPC.

Bloco de código
languagec#
firstline1
linenumberstrue
using RM.Glb.SGDP.SmartLink.Service.Domain;
using RM.Glb.SGDP.SmartLink.Service.Domain.Interfaces;
using RM.Lib.Log;
using RM.Lib.SmartLink.Domain;
using RM.Lib.SmartLink.Domain.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;

namespace RM.Glb.SGDP.SmartLink.Service
{
  /// <summary>
  /// SGDP Publisher Message Service class
  /// </summary>
  public class GlbSGDPPublisherMessageService : IGlbSGDPPublisherMessageService
  {
    private readonly IGlbSGDPResolverService _sgdpResolverService;
    private readonly IRMSSmartLinkPublisherService _smartLinkPublisherService;
    private readonly IRMSLogger _logService;

    private const string ctSGDPAudience = "SGDP"; // <-- SEU CÓDIGO DE AUDIENCE DEVE VIR AQUI. NO CASO DESSE EXEMPLO É SGDP. OBTER O CÓDIGO COM A EQUIPE DE TOTVS APPS.

    private const string ctSGDPUpdateTenantMetadata = "SGDPUpdateTenantMetadata";

    private const string ctSGDPDataCommand = "SGDPDataCommand";
    private const string ctSGDPResponseDataCommand = "SGDPDataResponse";

    private const string ctSGDPMaskCommand = "SGDPMaskCommand";
    private const string ctSGDPResponseMaskCommand = "SGDPMaskResponse";

    private const string ctSGDPLogsCommand = "SGDPLogsCommand";
    private const string ctSGDPResponseLogCommand = "SGDPLogsResponse";

    private const string ctSmartLinkSetupCommand = "setup";
    private const string ctSmartLinkUnSetupCommand = "unSetup";

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="smartLinkPublisherService"></param>
    /// <param name="sgdpResolverService"></param>
    /// <param name="logService"></param>
    public GlbSGDPPublisherMessageService(IRMSSmartLinkPublisherService smartLinkPublisherService,
      IGlbSGDPResolverService sgdpResolverService,
      IRMSLogger logService)
    {
      _smartLinkPublisherService = smartLinkPublisherService;
      _sgdpResolverService = sgdpResolverService;
      _logService = logService;
    }

    /// <summary>
    /// Adiciona uma mensagem de reposta para o comando "SGDPResponseDataCommand".
    /// </summary>
    /// <param name="parms"></param>
    public void AddResponseDataCommand(AddResponseDataCommandParms parms)
    {
      SmartLinkPublisherAddMessageParams parPublisher = new SmartLinkPublisherAddMessageParams
      {
        Command = ctSGDPResponseDataCommand,
        CorrelatedId = parms.CorrelatedId,
        Data = parms.Message,
        Audience = ctSGDPAudience
      };

      _logService.NotifyLogInfo(Properties.Resources.sconTotvsAppSGDPResponseDataCommandGravadoNaFila);
      _smartLinkPublisherService.AddMessage(parPublisher);
    }

    /// <summary>
    /// Adiciona uma mensagem de reposta para o comando "SGDPResponseMaskCommand".
    /// </summary>
    /// <param name="parms"></param>
    public void AddResponseMaskCommand(AddResponseMaskCommandParms parms)
    {
      SmartLinkPublisherAddMessageParams parPublisher = new SmartLinkPublisherAddMessageParams
      {
        Command = ctSGDPResponseMaskCommand,
        CorrelatedId = parms.CorrelatedId,
        Data = parms.Message,
        Audience = ctSGDPAudience
      };
      _logService.NotifyLogInfo(Properties.Resources.sconTotvsAppSGDPResponseMaskCommandGravadoNaFila);
      _smartLinkPublisherService.AddMessage(parPublisher);
    }
    /// <summary>
    /// Adiciona uma mensagem de reposta para o comando "SGDPResponseLogCommand".
    /// </summary>
    /// <param name="parms">Parms</param>
    public void AddResponseLogCommand(AddResponseLogCommandParms parms)
    {
      SmartLinkPublisherAddMessageParams parPublisher = new SmartLinkPublisherAddMessageParams
      {
        Command = ctSGDPResponseLogCommand,
        CorrelatedId = parms.CorrelatedId,
        Data = parms.Message,
        Audience = ctSGDPAudience
      };
      _logService.NotifyLogInfo(Properties.Resources.sconTotvsAppSGDPResponseLogCommandGravadoNaFila);
      _smartLinkPublisherService.AddMessage(parPublisher);
    }

    /// <summary>
    /// Adiciona uma mensagem para envio de um comando de sgldpdateapplicationmetada.
    /// </summary>
    /// <param name="parms"></param>
    public void AddMessageUpdateTenantMetadata(AddMessageUpdateTenantMetadataParms parms)
    {
      try
      {
        if (string.IsNullOrEmpty(parms.TenantId))
        {
          _logService.NotifyLogWarning(Properties.Resources.sSGDPTenantMetadataInvalidSGDPUpdateTenantMetadata);
          return;
        }
        List<string> jsons = GetUpdateTenandMetadata(parms.TenantId);
        foreach (string json in jsons)
        {
          SmartLinkPublisherAddMessageParams smartLinkParms = new SmartLinkPublisherAddMessageParams
          {
            Command = ctSGDPUpdateTenantMetadata,
            CorrelatedId = "",
            Data = json,
            Audience = ctSGDPAudience
          };
          _logService.NotifyLogInfo(Properties.Resources.sconTotvsAppSGDPTenantMetadaGravadoNaFila);
          _smartLinkPublisherService.AddMessage(smartLinkParms);
        }
      }
      catch (Exception ex)
      {
        _logService.NotifyLogWarning(ex, Properties.Resources.sconTotvsAppErroAoEnviarSGDPUpdateTenantMetadata);
      }
    }
    private List<string> GetUpdateTenandMetadata(string tenantId)
    {
      return _sgdpResolverService.GetSGDPTenantMetadata(
        new MetadataTenantParms() { TenantId = tenantId }
        )?.JsonsResult;
    }

    /// <summary>
    /// Adiciona mensagem UnSetup
    /// </summary>
    public void AddMessageUnSetup()
    {
      var pendingMessages = _smartLinkPublisherService.GetPendingMessages();
      var messagesSGDP =
        pendingMessages?.Where(y => y.TypeEvent.ToUpper() == ctSGDPUpdateTenantMetadata.ToUpper() &&
                                              y.TypeEvent.ToUpper() == ctSGDPDataCommand.ToUpper() &&
                                              y.TypeEvent.ToUpper() == ctSGDPMaskCommand.ToUpper() &&
                                              y.TypeEvent.ToUpper() == ctSGDPLogsCommand.ToUpper() &&
                                              y.TypeEvent.ToUpper() == ctSGDPResponseDataCommand.ToUpper() &&
                                              y.TypeEvent.ToUpper() == ctSGDPResponseMaskCommand.ToUpper()).ToList();

      if (messagesSGDP != null)
      {
        foreach (SmartLinkMessageEntity model in messagesSGDP)
        {
          _smartLinkPublisherService.RemoveMessageById(model.Id);
        }
      }

      SmartLinkPublisherAddMessageParams parms = new SmartLinkPublisherAddMessageParams
      {
        Command = ctSmartLinkUnSetupCommand,
        CorrelatedId = "",
        Audience = ctSGDPAudience,
        Data = _sgdpResolverService.GetSGDPSetup().JsonResult
      };
      _logService.NotifyLogInfo(Properties.Resources.sconTotvsAppUnSetupGravadoNaFila);
      _smartLinkPublisherService.AddMessage(parms);
    }

    /// <summary>
    /// Adiciona messagem setup
    /// </summary>
    public void AddMessageSetup()
    {
      SmartLinkPublisherAddMessageParams parms = new SmartLinkPublisherAddMessageParams
      {
        Command = ctSmartLinkSetupCommand,
        CorrelatedId = "",
        Audience = ctSGDPAudience,
        Data = _sgdpResolverService.GetSGDPSetup().JsonResult
      };
      _logService.NotifyLogInfo(Properties.Resources.sconTotvsAppSetupGravadoNaFila);
      _smartLinkPublisherService.AddMessage(parms);
    }
  }
}

7. Código fonte de exemplo (extraído da classe "GlbSGDPPublisherMessageService" localizada na solution de Globais, projeto "RM.Glb.SGDP.SmartLink.Service". Esse exemplo é para a versão 12.1.2402 que ainda utiliza api REST para envio de mensagem. Nesse caso o Audience é definido no final do endpoint passado como parâmetro para o RouterMessage.

Bloco de código
languagec#
firstline1
linenumberstrue
using RM.Glb.SGDP.SmartLink.Service.Domain;
using RM.Glb.SGDP.SmartLink.Service.Domain.Interfaces;
using RM.Lib.Log;
using RM.Lib.SmartLink.Domain;
using RM.Lib.SmartLink.Domain.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;

namespace RM.Glb.SGDP.SmartLink.Service
{
  public class GlbSGDPPublisherMessageService : IGlbSGDPPublisherMessageService
  {
    private readonly IGlbSGDPResolverService _sgdpResolverService;
    private readonly IRMSSmartLinkPublisherService _smartLinkPublisherService;
    private readonly IRMSLogger _logService;

    private const string ctSGDPUpdateTenantMetadataEndPoint = "/api/v1/link/send/SGDPUpdateTenantMetadata/SGDP";
    private const string ctSGDPDataResponseEndPoint = "/api/v1/link/send/SGDPDataResponse/SGDP";

    private const string ctSGDPUpdateTenantMetadata = "SGDPUpdateTenantMetadata";

    private const string ctSGDPDataCommand = "SGDPDataCommand";
    private const string ctSGDPResponseDataCommand = "SGDPDataResponse";

    private const string ctSGDPMaskResponseEndPoint = "/api/v1/link/send/SGDPMaskResponse/SGDP";
    private const string ctSGDPMaskCommand = "SGDPMaskCommand";
    private const string ctSGDPResponseMaskCommand = "SGDPMaskResponse";

    private const string ctSGDPLogResponseEndPoint = "/api/v1/link/send/SGDPLogsResponse/SGDP";
    private const string ctSGDPLogsCommand = "SGDPLogsCommand";
    private const string ctSGDPResponseLogCommand = "SGDPLogsResponse";

    private const string ctSmartLinkSetupCommand = "setup";
    private const string ctSmartLinkUnSetupCommand = "unSetup";


    public GlbSGDPPublisherMessageService(IRMSSmartLinkPublisherService smartLinkPublisherService,
      IGlbSGDPResolverService sgdpResolverService,
      IRMSLogger logService)
    {
      _smartLinkPublisherService = smartLinkPublisherService;
      _sgdpResolverService = sgdpResolverService;
      _logService = logService;
    }

    /// <summary>
    /// Adiciona uma mensagem de reposta para o comando "SGDPResponseDataCommand".
    /// </summary>
    /// <param name="parms"></param>
    public void AddResponseDataCommand(AddResponseDataCommandParms parms)
    {
      SmartLinkPublisherAddMessageParams parPublisher = new SmartLinkPublisherAddMessageParams
      {
        Command = ctSGDPResponseDataCommand,
        CorrelatedId = parms.CorrelatedId,
        Data = parms.Message,
        RouterMessage = ctSGDPDataResponseEndPoint
      };

      _logService.NotifyLogInfo(Properties.Resources.sconTotvsAppSGDPResponseDataCommandGravadoNaFila);
      _smartLinkPublisherService.AddMessage(parPublisher);
    }

    /// <summary>
    /// Adiciona uma mensagem de reposta para o comando "SGDPResponseMaskCommand".
    /// </summary>
    /// <param name="parms"></param>
    public void AddResponseMaskCommand(AddResponseMaskCommandParms parms)
    {
      SmartLinkPublisherAddMessageParams parPublisher = new SmartLinkPublisherAddMessageParams
      {
        Command = ctSGDPResponseMaskCommand,
        CorrelatedId = parms.CorrelatedId,
        Data = parms.Message,
        RouterMessage = ctSGDPMaskResponseEndPoint
      };
      _logService.NotifyLogInfo(Properties.Resources.sconTotvsAppSGDPResponseMaskCommandGravadoNaFila);
      _smartLinkPublisherService.AddMessage(parPublisher);
    }
    /// <summary>
    /// Adiciona uma mensagem de reposta para o comando "SGDPResponseLogCommand".
    /// </summary>
    /// <param name="parms">Parms</param>
    public void AddResponseLogCommand(AddResponseLogCommandParms parms)
    {
      SmartLinkPublisherAddMessageParams parPublisher = new SmartLinkPublisherAddMessageParams
      {
        Command = ctSGDPResponseLogCommand,
        CorrelatedId = parms.CorrelatedId,
        Data = parms.Message,
        RouterMessage = ctSGDPLogResponseEndPoint
      };
      _logService.NotifyLogInfo(Properties.Resources.sconTotvsAppSGDPResponseLogCommandGravadoNaFila);
      _smartLinkPublisherService.AddMessage(parPublisher);
    }

    /// <summary>
    /// Adiciona uma mensagem para envio de um comando de sgldpdateapplicationmetada.
    /// </summary>
    public void AddMessageUpdateTenantMetadata(AddMessageUpdateTenantMetadataParms parms)
    {
      try
      {
        if(string.IsNullOrEmpty(parms.TenantId))
        {
          _logService.NotifyLogWarning(Properties.Resources.sSGDPTenantMetadataInvalidSGDPUpdateTenantMetadata);
          return;
        }
        List<string> jsons = GetUpdateTenandMetadata(parms.TenantId);
        foreach (string json in jsons)
        {
          SmartLinkPublisherAddMessageParams smartLinkParms = new SmartLinkPublisherAddMessageParams
          {
            Command = ctSGDPUpdateTenantMetadata,
            CorrelatedId = "",
            Data = json,
            RouterMessage = ctSGDPUpdateTenantMetadataEndPoint
          };
          _logService.NotifyLogInfo(Properties.Resources.sconTotvsAppSGDPTenantMetadaGravadoNaFila);
          _smartLinkPublisherService.AddMessage(smartLinkParms);
        }
      }
      catch (Exception ex)
      {
        _logService.NotifyLogWarning(ex, Properties.Resources.sconTotvsAppErroAoEnviarSGDPUpdateTenantMetadata);
      }
    }
    private List<string> GetUpdateTenandMetadata(string tenantId)
    {
      return _sgdpResolverService.GetSGDPTenantMetadata(
        new MetadataTenantParms() { TenantId = tenantId }
        )?.JsonsResult;
    }

    public void AddMessageUnSetup()
    {
      var pendingMessages = _smartLinkPublisherService.GetPendingMessages();
      var messagesSGDP =
        pendingMessages?.Where(y => y.TypeEvent.ToUpper() == ctSGDPUpdateTenantMetadata.ToUpper() &&
                                              y.TypeEvent.ToUpper() == ctSGDPDataCommand.ToUpper() &&
                                              y.TypeEvent.ToUpper() == ctSGDPMaskCommand.ToUpper() &&
                                              y.TypeEvent.ToUpper() == ctSGDPLogsCommand.ToUpper() &&
                                              y.TypeEvent.ToUpper() == ctSGDPResponseDataCommand.ToUpper() &&
                                              y.TypeEvent.ToUpper() == ctSGDPResponseMaskCommand.ToUpper()).ToList();

      if (messagesSGDP != null)
      {
        foreach (SmartLinkMessageEntity model in messagesSGDP)
        {
          _smartLinkPublisherService.RemoveMessageById(model.Id);
        }
      }

      SmartLinkPublisherAddMessageParams parms = new SmartLinkPublisherAddMessageParams
      {
        Command = ctSmartLinkUnSetupCommand,
        CorrelatedId = "",
        Data = _sgdpResolverService.GetSGDPSetup().JsonResult
      };
      _logService.NotifyLogInfo(Properties.Resources.sconTotvsAppUnSetupGravadoNaFila);
      _smartLinkPublisherService.AddMessage(parms);
    }

    public void AddMessageSetup()
    {
      SmartLinkPublisherAddMessageParams parms = new SmartLinkPublisherAddMessageParams
      {
        Command = ctSmartLinkSetupCommand,
        CorrelatedId = "",
        Data = _sgdpResolverService.GetSGDPSetup().JsonResult
      };
      _logService.NotifyLogInfo(Properties.Resources.sconTotvsAppSetupGravadoNaFila);
      _smartLinkPublisherService.AddMessage(parms);
    }
  }
}

04. Implementando um consumidor de mensagens

Para que uma classe seja um consumidor de mensagens no SmartLink, deve-se atender aos pré-requisitos listados abaixo:

Pré-requisitos

  1. Criar um projeto (classLibrary .NET) na solution do segmento em questão, com o seguinte padrão de nome: RM.[Segmento].XXX.SmartLink.Service.
    1. Exemplo: RM.Glb.SGDP.SmartLink.Service.dll
  2. Adicionar referência para a dll "RM.Lib.SmartLink.dll";
  3. Criar uma classe herdando da ancestral "RMSSmartLinkConsumerMessageBase".

  4. A classe "RMSSmartLinkConsumerMessageBase" herda da classe RMSObject da Lib. Consequentemente, as classes de "consumers" poderão chamar os métodos "CreateFacade" e "CreateModule" dentro de suas estruturas.
  5. Carimbar a classe com o atributo "RMSSmartLinkConsumerMessageAttr". Nesse atributo, devem ser informados os dados abaixo:

    PropriedadeDescrição
    Cod.SistemaIdentificador da aplicação
    ComandoNome do Comando da mensagem
  6. Para realização de tratamento de exceções no caso de ocorrer erro no processamento da mensagem
    Quando for considerado um erro e um nova tentativa de processamento deve ser realizada: 
    A classe RMSSmartLinkConsumerMessageException ou outra, pode ser utilizada.
    Neste caso a mensagem será reenfileirada e novas tentativas de processamento serão realizadas.

    Sendo considerado que a mensagem deve ser descartada, ou seja, esta mensagem não deve ser processada neste momento ou apenas sob intervenção manual:
    A classe RMSSmartLinkConsumerFatalException, deverá ser utilizada e informado o motivo.
    Neste caso a mensagem será colocada em um fila de espera (DLQ) e somente será reprocessada por uma intervenção manual ou será descartada.

  7. Código fonte de exemplo (extraído da classe "GlbSGDPConsumerDataCommandMessage" localizada na solution de Globais, projeto "RM.Glb.SGDP.SmartLink.Service":

    Bloco de código
    languagec#
    firstline1
    linenumberstrue
    using RM.Glb.SGDP.SmartLink.Service.Domain.Interfaces;
    using RM.Lib;
    using RM.Lib.SmartLink.Domain;
    using RM.Lib.SmartLink.Domain.Consumer;
    using RM.Lib.SmartLink.Domain.Interfaces;
    using RM.Lib.SmartLink.Domain.Publisher;
    using System;
    using System.Runtime.Serialization;
    
    namespace RM.Glb.SGDP.SmartLink.Service.Domain
    {
      /// <summary>
      /// Regras de implementação do mecanismo de processamento da mensagem SGDDataCommand do SGDP
      /// </summary>
      [RMSSmartLinkConsumerMessageAttr(CodSistema.Glb, "SGDPDataCommand")]
      public class GlbSGDPConsumerDataCommandMessage : RMSSmartLinkConsumerMessageBase
      {
        protected override ConsumerMessageExecuteResult DoExecute(string message)
        {
          logService.NotifyLogInfo(Properties.Resources.sconTotvsAppInicioExecucaoDoConsumerSGDPDataCommandConsumer);
          ConsumerMessageExecuteResult result = new ConsumerMessageExecuteResult();
          IGlbSGDPResolverService resolver = GlbSGDPResolveFactory.NewSGDPResolve(this.DBS, this.logService);
          IGlbSGDPPublisherMessageService publisher = new GlbSGDPPublisherMessageService(this.SmartLinkPublisherService, resolver, logService);
    
          ExecuteDataCommandParms par = new ExecuteDataCommandParms();
          par.Message = message;
          try
          {
            var execDataResult = resolver.ExecuteSGDPDataCommand(par);
            logService.NotifyLogInfo(Properties.Resources.sconTotvsAppJSonSGDPDataResponseCommandForamGerados, 
              Properties.Resources.sconTotvsAppQuantidadeDeJSonsGerados, execDataResult?.JsonResults?.Count);
            result.CorrelatedID = execDataResult.RequestId;
            if (execDataResult?.JsonResults != null)
            {
              foreach (var jsonResult in execDataResult.JsonResults)
              {
                AddResponseDataCommandParms parResponse = new AddResponseDataCommandParms();
                parResponse.Message = jsonResult;
                parResponse.CorrelatedId = execDataResult.RequestId;
                publisher.AddResponseDataCommand(parResponse);
              }
            }
          }
          catch(Exception ex)
          {
            logService.NotifyLogError(new GlbSGDPConsumerDataCommandMessageException(
              Properties.Resources.sconTotvsAppErroAoProcessarSGDDataCommand, ex));
          }
          logService.NotifyLogInfo(Properties.Resources.sconTotvsAppFimExecucaoDoConsumerSGDPDataCommandConsumer);
          return result;
        }
      }
    
      [Serializable]
      public class GlbSGDPConsumerDataCommandMessageException : RMSApplicationException
      {
        public GlbSGDPConsumerDataCommandMessageException() : base()
        {
        }
    
        public GlbSGDPConsumerDataCommandMessageException(string message) : base(message)
        {
        }
    
        public GlbSGDPConsumerDataCommandMessageException(string message, Exception ex) : base(message, ex, string.Empty)
        {
        }
    
        public GlbSGDPConsumerDataCommandMessageException(SerializationInfo info, StreamingContext context)
      : base(info, context)
        {
        }
      }
    }

05. Diagrama de classes

Image Added


06. DER


Image Added


Tabela
GTOTVSLINKMESSAGE

Tabela utilizada para armazenar todos as mensagens geradas e enviadas para execução pelo SmartLink.

Sempre que uma mensagem é executa com sucesso, a linha referente a mensagem é excluída

dessa tabela , deixando essa tabela sempre leve. Esse procedimento melhora a performance do processo,

visto que vários "updates" são realizados nessa tabela.Quando menor o volume de uma tabela, mais rápido

será a operação do update.

GTOTVSLINKMESSAGEEXEC

Tabela contendo todos os dados de execução de uma determinada mensagem.


GTOTVSLINKLOGTabela contendo todos os logs de execução de uma mensagem.