Notificar cliente quando houver alguma alteração no banco de dados

26 respostas
banco
Thallysson

Olá. Eu estou fazendo uma aplicação em JavaFX, e quando eu acabar ela vou fazer uma versão para Android e outra pra IOS. Todas precisam compartilhar informações através de uma banco de dados Mysql, até aí tudo bem, mas o que me intriga é que cada um dos clientes precisa saber quando o outro fez alguma alteração em determinada tabela. No momento não é necessário se preocupar porque eu ainda estou criando o primeiro cliente, mas eu já notei a necessidade de algo do tipo porque enquanto se enquanto minha aplicação estiver rodando, eu fazer alguma alteração no banco de dados, a aplicação precisa saber que eu fiz isso.
Alguém tem alguma ideia de como fazer isso? Um trigger? Como faço para enviar essas informações para os clientes?

26 Respostas

lvbarbosa

Existem algumas formas diferentes de fazer isso. Quão necessário é esse update? É em tempo real?

Você pode criar uma API (em Java ou em qualquer outra linguagem) para ser um ponto único de acesso ao banco de dados. Em outras palavras, todos os clientes vão acessar os dados através dessa API.

Dessa forma, você tem como saber quando as mudanças ocorrem, e pode notificar os clientes conectados. Vou te passar duas formas de fazer isso:

  1. Pooling: de tempos em tempos, o programa cliente consulta o servidor e verifica se há alguma alteração. Caso haja uma alteração, você baixa os dados novos e atualiza a interface.

    Vantagens: é simples de implementar
    Desvantagens: consultas o tempo inteiro podem rapidamente se tornar um problema de performance

  2. Socket: quando o usuário abre o cliente, uma conexão tcp é aberta entre o cliente e o servidor. O servidor deve manter as referências para as conexões de alguma forma que seja possível identificar quem é cada cliente. Dessa forma, toda vez que ocorre uma alteração, o servidor verifica se quem estaria possivelmente interessado naquela mudança está conectado, e envia uma notificação de atualização para os mesmos. Os clientes que recebem a notificação podem trabalhar de forma reativa, sendo que toda vez que chega uma notificação de atualização do servidor, eles devem baixar os novos dados.

    Vantagens: um pouco mais eficiente se bem implementado
    Desvantagens: complexo de se fazer, porém existem bibliotecas prontas que podem ajudar bastante, como Socket IO

Esse é um problema antigo, que não é muito simples de se resolver. Porém, há soluções propostas. Se você quiser saber mais sobre isso, procura livros sobre Sistemas Distribuídos.

Thallysson

Sim, tem que ser em tempo real, e são vários registros de várias tabelas que precisam ser atualizados.

“lvbarbosa:

Você pode criar uma API (em Java ou em qualquer outra linguagem) para ser um ponto único de acesso ao banco de dados. Em outras palavras, todos os clientes vão acessar os dados através dessa API.

Eu não preciso criar uma API, pois meu programa utiliza o modelo MVC. Quando eu for fazer para outras plataformas é só utilizar a mesma lógica na model não?

Dessa maneira pode até funcionar, mas como são muitos dados realmente iria resultar em um problema de performance. Além disso, como tu disse, essa verificação ocorre de tempos em tempos, dependendo desse “tempos em tempos” pode não ficar em tempo real.

É possível fazer isso no Mysql? Peço perdão pela ignorância, mas tenho pouca experiência em Java com redes.

lvbarbosa

Você está fazendo a aplicação para desktop com JavaFX, certo? O banco de dados é local?

Se você quer fazer a versão mobile e utilizar o mesmo banco de dados para todos os apps, você precisa necessariamente criar uma forma para distribuir a aplicação. Dessa forma, todos os clientes( O JavaFX, o iOS, o Android, o Browser, qualquer coisa) vão conectar na API para obter e enviar dados. As aplicações não devem conectar diretamente no banco de dados, isso pode gerar várias brechas de segurança e performance.

Todos esses problemas de sincronização somem se você faz tudo localmente no desktop, com apenas um cliente mexendo nos dados.

O ideal é construir uma camada em cima do banco de dados, com toda a lógica de negócio. Os clientes conectam nessa camada e são apenas uma especie de view.

Eu aposto que não, apesar de não ter certeza. Ficaria surpreso se soubesse que pode. Esse não é o propósito de um banco de dados. Essa lógica está muito mais relacionada ao negócio do que ao armazenamento de dados em si.

Essa que eu te falei é uma arquitetura bem utilizada hoje em dia. Na hora de fazer um sistema distribuido, ao invés de fazer um site e depois ficar tentando adaptar pra funcionar em mobile, criar desde o zero como uma API que pode ser utilizada universalmente.

Thallysson

Tá mas e a questão de notificar o cliente? Não tem como fazer um trigger para mandar uma mensagem por socket para os clientes conectados por exemplo?

lvbarbosa

Nativamente? Não. Porém, eu dei uma pesquisada aqui e parece que você pode chamar funções externas dentro de um trigger. Tem que implementar essas funções em C ou C++, então dá pra fazer qualquer coisa. Você vai ter que bolar um jeito de passar o ip e a porta do cliente para o MySQL, para que ele saiba qual cliente notificar.

Dá uma olhada aqui: https://dev.mysql.com/doc/refman/5.7/en/adding-udf.html

Eu acho que seria bem mais interessante implementar isso em uma camada à parte, na forma de uma API como conversamos antes, porque essas chamadas de rede podem ser bem custosas e comprometer a performance do DB. Mas depende do caso, as vezes numa aplicação pequena não faz tanta diferença.

pfk66

Acho que não existe uma solução multiplataforma para notificar clientes que rodam em plataformas diferentes. Algumas plataformas mais modernas como iOS oferecem suporte a push notifications para clientes iOS e até para aplicações que rodam no browser da Apple, o Safari, mas outras plataformas antigas como Javafx, acho que não existe o conceito de ser notificado no desktop, existe? isso é algo que surgiu agora com mobile. Não me lembro de ter visto uma aplicação desktop ser notificada.

resumindo, seu backend vai ter que usar APIs diferentes pra notificar clientes de cada plataforma (iOS, Android) e algumas plataformas (JavaFX) simplesmente não existe API, vc vai ter que criar algum framework de notificações para os clientes Javafx.

pfk66

Polling geralmente é suficiente para “tempo real”. Ex. O que o Donald Trump twita está disponível em “tempo real” para todos que acessam sua pagina no Twitter.

Thallysson

Bacana, vou estudar uma maneira de fazer assim. Vai demorar bastante porque eu só sei o básico do C, mas pode funcionar. Valeu pela dica.

Thallysson

Tem sim notificações no JavaFX, usando tray, na realidade é do AWT, mas eu tô usando. Eu não me importo de ter que fazer um sistema desses pra cada cliente, só preciso que funcione e não interfira na performance da aplicação e nem do banco.

Thallysson

É, mas se for muitas vezes pode gerar uma sobrecarga, e se for poucas pode não dar no “tempo real”, algo como verificar a cada segundo por exemplo (sem levar em conta o tempo de receber a resposta e interpretar).

pfk66

Acredito que o tray é apenas um componente de interface gráfica (por isso o pacote AWT), e não um sistema de notificações que eu estou falando… que você vai precisar usar no backend pra enviar uma notificação para a aplicação javafx.

Thallysson

Acho que eu entendi. Tu quer dizer para eu encontrar uma maneira de trocar as informações entre os clientes sem utilizar o banco? Tipo, o cliente Android alterou determinado registro, daí ele manda pro cliente IOS e para o cliente desktop uma mensagem por TCP dizendo que algo foi alterado?

pfk66

Não. Eles se comunicam apenas com um servidor no backend por meio de uma API. O backend então notifica os clientes por meio de APIs específicas pra cada plataforma.

Thallysson

Sim, mas essa comunicação teria que ser por algo como TCP não? Daí eu teria que ter uma tabela com os clientes ativos. Eu vou tentar desenvolver uma lógica desse tipo. Parece que assim não gera muito problema de performance, mas só testando todas as alternativas pra ver, eu vou fazer e depois eu volto e digo como foi.

pfk66

Isso mesmo, o backend onde esta o banco de dados sabe quando tem alguma coisa pra enviar, ele então se comunica com os sistema de notificações, que é quem trata de entregar as notificações para os clientes.

Thallysson

Eu estou montando o sistema. Eu vou fazer assim: Eu tenho uma tabela com todos os dispositivos que estão usando os dados do usuário, nessa tabela tem também o IP. Eu vou criar um sistema de comunicação por socket em cada cliente. Se o cliente Android modificar por exemplo, ele vai mandar uma mensagem por socket para todos os ips de dipositivos que estão com um cliente aberto, então eles vão atualizar a lista deles. Até aí tudo bem, o problema é a alteração direto no banco. Eu vou criar uma função em C++ assim como o @lvbarbosa citou, e sempre quando eu fizer uma atualização direto no banco eu vou chamar ela, então uma mensagem por socket deve ser enviada a cada cliente ativo. Mas eu não estou conseguindo de maneira alguma criar essa função. @lvbarbosa você poderia me dar um exemplo de sintaxe?

lvbarbosa

Eu não tenho nem ideia de como é que se implementa a função pra utilizar dentro do MySQL, só dei uma lida por cima e te mandei o link. Pelo que eu vi ontem, existem algumas estruturas que você deve seguir (tem naquele link que mandei) para funcionar direitinho. Tem exemplos lá.

Mas falando de C++: não existe biblioteca de socket nativa em C++. Porém, você pode utilizar as bibliotecas do C (socket.h, no caso de sistemas UNIX) para fazer a comunicação. Se quiser, também pode utilizar alguma biblioteca feita para C++ (que por sua vez vai utilizar socket.h por baixo dos panos).

Não acho que vai ser tão simples você simplesmente manter uma tabela com os endereços IP dos clientes no banco de dados. Não é só IP e porta, tem uma série de outras coisas envolvidas. Se o cliente estiver atras de um NAT, por exemplo, você não vai conseguir acesso a ele diretamente, mas ele que tem que se conectar a você (servidor) que tem um IP público. Da pra configurar isso no roteador, mas aí você teria que obrigar cada cliente a mexer em seu roteador para poder receber atualizações.

Quando você utiliza os chamados “WebSockets” em uma API pública (pública no sentido de estar na Internet), é muito mais simples manter uma estrutura em memória com os sockets de cada cliente dentro da API do que ter isso no banco de dados. Com o socket do cliente em mãos (já aberto), é muito mais fácil efetuar a transmissão de dados, porque o roteador do cliente vai saber pra qual dispositivo mandar os pacotes, já que ele é o NAT.

Em contraste, pensando no C++ chamado pelo MySQL, você vai ter que abrir um socket com o cliente (e o cliente vai ter que estar esperando para receber uma conexão), ou então de alguma maneira acessar o FD (file descriptor) do socket que o cliente abriu.

Sinceramente, não tenho a menor ideia de como fazer isso. É uma abordagem muito complexa.

Você me falou antes que não entende muito de redes. Talvez tudo que eu falei aqui não tenha feito o menor sentido kkkkkkkk Dá uma estudada nisso que você vai entender o meu argumento!

Thallysson

Cara, eu não acho que precisa de tudo isso, eu estou fazendo um aplicação simples para gerenciar dados pessoais. Por hora eu só preciso que essa sincronização funcione, essas coisas eu vejo mais tarde. O sistema de comunicação entre os clientes está pronto (pelo menos no Java, depois eu faço outro para o Android e para o IOS), mas o que me intriga é quando eu faço alterações pelo Workbench por exemplo. Daí eu preciso desse trigger para notificar que o administrador alterou algo. Eu tentei implementar um exemplo que eu encontrei na internet mas deu só erro de sintaxe. Eu não encontrei nada de exemplo dessa sintaxe no link. Eu notei que tinha algo de errado já quando o Workbench não deixou azul o extern.

Thallysson

Será que algo como uma tabela temporária com as alterações serviria? Seria muita manutenção né?

lvbarbosa

Você não vai implementar a função dentro do workbench, mas sim fora. É uma forma de adicionar funcionalidades ao MySQL, já que ele é open source. Funciona mais ou menos assim:

  1. Você vai baixar o source do MySQL;
  2. Dentro dos arquivos, tem um arquivo na pasta sql chamado de udf_example.cc, que tem exemplos de como essas funções são implementadas (link do github com esse arquivo: https://github.com/mysql/mysql-server/blob/5.7/sql/udf_example.cc);
  3. Você vai criar teu arquivo c++, incluir os cabeçalhos do mysql e programar tua função;
  4. Você vai gerar o object code do teu arquivo e em seguida (pelo que eu entendi) vai precisar recompilar o mysqld (o servidor do mysql), com a tua função junto.

Tem uns avisos lá que os cabeçalhos da standard library talvez não estejam disponíveis. Tem que ler o que tem na documentação pra entender tudo certinho.

Eu acredito que não é para esse teu propósito que esse mecanismo de extensibilidade foi feito. Escute o que eu to te falando não vá atrás disso, porque vai dar muito trabalho (principalmente se você não for um ninja de c/c++ e entender como funciona o processo de dynamic linking). Implemente uma API web, por onde absolutamente TODAS as alterações nos dados serão feitas (não pode atlerar nada pelo workbench, nem por nenhum outro lugar, só pela API).

Como eu te falei, não é impossível, mas vai dar muito trabalho e tem que estudar pra caramba. Com certeza é um exercício muito legal pra aprender bastante coisa, caso tu não saiba como esses esquemas funcionam.

Contudo, acho que é MUITO melhor pra ti aprender a fazer a API fazendo as notificações em tempo real como conversamos. Se tu coloca um negócio desse no teu GitHub como portifólio, pode ter certeza que você vai sair na frente de muita gente.

Thallysson

Isso é simplesmente fantástico!

“lvbarbosa:

Eu acredito que não é para esse teu propósito que esse mecanismo de extensibilidade foi feito. Escute o que eu to te falando não vá atrás disso, porque vai dar muito trabalho (principalmente se você não for um ninja de c/c++ e entender como funciona o processo de dynamic linking). Implemente uma API web, por onde absolutamente TODAS as alterações nos dados serão feitas (não pode atlerar nada pelo workbench, nem por nenhum outro lugar, só pela API).

Cara, a ideia da API web é boa, mas minha experiência com web é muito pouca. Eu aprendi as linguagens principais (HTML, CSS, JavaScript e PHP) mas não desenvolvi nada desde então. Vai dar o mesmo trabalho. Eu prefiro ter um model que faça isso. Eu já tenho o sistema para dizer quais dispositivos estão com o cliente ligado e eu simplesmente faço a comunicação via socket entre eles, mas eu queria essa função em C++ para quando eu estiver fazendo as alterações manualmente, mas se é tão difícil assim, pelo menos enquanto eu desenvolvo, eu vou manter um botão de atualizar em cada cliente.

lvbarbosa

@Thallysson eu te perguntei no começo, você me respondeu e eu esqueci de comentar, a respeito do quão importante é o update dos clientes o mais rápido possível.

Quanto mais rápido, mais chato é de implementar. Se você puder abrir mão da sincronia até um certo ponto, faça-o, porque vai simplificar bastante as coisas. Você pode simplesmente abandonar essa ideia de socket e usar um pooling menos agressivo (verificar se há atualizações quando o usuário abre uma certa tela, por exemplo).

A Apple, por exemplo, oferece várias soluções pros usuários que são sincronizadas. O aplicativo deles de notas serve de exemplo. Quando você edita uma nota em um dispositivo, todos os outros dispositivos que estão com tua conta logada devem ser atualizados com as mudanças efetuadas. Esse update não é feito freneticamente, demora alguns segundos. Não é algo que compromete a usabilidade do negócio! E outra, sempre dá pra apertar um botão ou fazer um gesto na tela pra pedir pro aplicativo dar um refresh e ver se tem alguma atualização.

Thallysson

A minha aplicação já tem esse tipo de atualização. Tipo quando troca de aba na TabPane é executada uma atualização, mas o problema é que a maior parte do tempo o usuário vai ficar com foco na caixa de texto, e a mesma já possui um listener que a cada tecla digitada altera algo no banco, e isso já consome bastante performance não? Bem, eu não percebi lentidão na interface e nem o fan do meu cooler girar mais rápido, mas acho que em um computador mais lento teria algum problema. Se eu for implementar a atualização assim, acha que teria problema? Tipo, a cada tecla também atualizar. Daria muita confusão?

Thallysson

Se eu precisasse fazer alguma parte em pooling, saberia me dar uma exemplo de em quanto em quanto tempo eu deveria fazer essa verificação? Um segundo? Menos?

lvbarbosa

Agora que eu vi que tava escrevendo polling errado esse tempo inteiro kkkkkk. São dois L, não dois O. Pooling tem a ver com pool de recursos (como conexões ou threads), e polling tem a ver com questionar. Mas enfim!

Depende da aplicação… quanto menor o intervalo, menor é o impacto na performance do servidor, mas também a atualização é mais lenta. Existe esse trade-off.

Coloca algo entre 5 e 15 segundos, acho que é aceitável. Você pode também fazer isso de forma dinâmica. Por exemplo:

  • Quando não há atualizações por um bom tempo (sei lá, um minuto), você pode diminuir a frequência (aumentando o tempo para 20 segundos);
  • Quando há uma atualização, você pode aumentar um pouco a frequência, porque a chance de novas atualizações ocorrerem pode ser um pouco maior;

Vai da tua criatividade e da necessidade da aplicação. Vai testando aí!

Thallysson

Oi. Eu resolvi o problema faz um tempinho, mas eu me lembro que disse que voltaria aqui pra dizer como foi. Bem, eu adicionei alguns listeners em algumas partes do programa como na troca de itens de uma lista, e coisas do tipo. Além disso, cada dado que depende do banco eu fiz um processo paralelo que executa o pooling. Todos no intervalo de 5 segundos, mas com algumas exceções, como naquela caixa de texto que eu citei. Quando o usuário não está digitando nela, o pooling é executado, porém caso ele comece a digitar, é dado um “pause”.

Criado 20 de fevereiro de 2017
Ultima resposta 10 de mar. de 2017
Respostas 26
Participantes 3