Publicações e Subscrições

Lateral 4.5

Porcentagem Traduzida

Neste capítulo, você irá:

  • Aprenda como publicações e subscrições funcionam.
  • Aprenda o que o pacote padrão Autopublish faz.
  • Veja mais alguns exemplos de padrões de publicações.
  • Publicações e subscrições são uns dos mais importantes conceitos no Meteor, mas pode ser difícil você entender enquanto está começando.

    Isso levou a uma série de mal-entendidos, como a crença de que o Meteor é inseguro, ou que as aplicações Meteor não podem lidar com grandes quantidades de dados.

    Uma grande parcela do motivo das pessoas inicialmente acharem estes conceitos um pouco confusos é a “mágica” que o Meteor faz por nós. Embora essa mágica seja muito útil, ela pode obscurecer o que está de fato acontecendo por trás das cenas (o que uma mágica tende a fazer). Então vamos analisar as camadas dessa mágica e entender o que está acontecendo.

    Os Velhos Tempos

    Mas primeiro, vamos olhar para os bons velhos tempos em 2011 quando o Meteor ainda não havia sido lançado. Vamos dizer que você está fazendo um simples aplicativo Rails. Quando um usuário entra no site, o cliente (i.e seu navegador) manda uma requisição para sua aplicação, que está vivendo no servidor.

    O primeiro trabalho da aplicação é perceber qual dado o usuário precisa de visualizar. Isso pode ser a página 12 dos resultados da busca, informação do perfil de usuário da Mary, os últimos 20 tweets de Bob, e por ai vai. Você pode nisso como sendo, basicamente, um atendente de livraria navegando entre os corredores para encontrar o livro que você pediu.

    Uma vez que o dado correto tenha sido selecionado, o segundo trabalho da aplicação é traduzir este dado em um belo e legível HTML (ou JSON no caso de uma API).

    Na metáfora da livraria, isso seria embrulhar o livro que você comprou em uma embalagem e colocá-lo em uma bela bolsa. Esta é a parte “View” (visão/vista) do famoso modelo Model-View-Controller (Modelo-Visão-Controlador).

    Finalmente, a aplicação pega o código HTML e manda para o navegador. O trabalho da aplicação é finalizado, e agora que tudo está fora de suas mãos virtuais você pode pegar uma cerveja enquanto espera pela próxima requisição.

    O Caminho do Meteor

    Vamos rever o que faz o Meteor ser tão especial em comparação. Como vimos, a principal inovação do Meteor é que enquanto aplicações Rails estão vivas apenas no servidor, um aplicativo Meteor inclui um componente no lado do cliente que vai rodar no cliente (o navegador).

    Colocando um subgrupo do banco de dados no cliente.
    Colocando um subgrupo do banco de dados no cliente.

    Isso é como um balconista que não apenas encontra o livro pra você, mas também te segue até em casa e lê o livro para você à noite (fato que vamos admitir soar um pouco assustador).

    Essa arquitetura permite ao Meteor fazer várias coisas legais, principalmente com o que o Meteor chama de database everywhere (banco de dados em todos os lugares). Simplesmente, Meteor vai pegar uma parte do seu banco de dados e copiá-lo para o cliente.

    Isso tem duas grandes implicações: primeira, ao invés de mandar código HTML para o cliente, a aplicação Meteor vai mandar o real, dado puro e o cliente vai lidar com ele (data on the wire - dado na rede). Segundo, você vai ser capaz de acessar o dado instantaneamente sem ter que esperar por uma ida e volta do servidor (latency compensation - compensação de latência).

    Publicação

    O banco de dados de um aplicativo pode conter dez mil documentos, sendo alguns destes privados ou sensíveis. Então obviamente não devemos simplesmente espelhar nosso banco de dados completamente no cliente, por razões de segurança e escabilidade.

    Então vamos precisar de uma forma de dizer ao Meteor quais subgrupos de dados podem ser enviados ao cliente, e vamos realizar isso através de uma publicação.

    Vamos voltar ao Microscope. Aqui estão todos os posts do no aplicativo situados no banco de dados:

    Todos os posts contidos no nosso banco de dados.
    Todos os posts contidos no nosso banco de dados.

    Embora esse recurso reconhecidamente não exista no Microscope, vamos imaginar que alguns de nossos posts tenham sido marcados por linguagem abusiva. Mesmo que queiramos que eles continuem em nosso banco de dados, eles não devem estar disponíveis para os usuários (i.e enviados ao cliente).

    Nossa primeira tarefa vai ser dizer ao Meteor qual dado nós vamos querer enviar ao cliente. Vamos dizer ao Meteor que desejamos publicar apenas os posts sem marcações:

    Excluindo posts marcados.
    Excluindo posts marcados.

    Aqui temos o código correspondente, que deve estar no servidor:

    // no servidor
    Meteor.publish('posts', function() {
      return Posts.find({flagged: false});
    });
    

    Isso assegura para que não exista uma forma de um cliente estar apto a acessar post marcados. Isso é exatamente a forma que você torna uma aplicação Meteor segura: apenas assegure-se de publicar dados que você queira que estejam disponíveis para acesso no cliente.

    DDP

    Fundamentalmente, você pode pensar sobre o sistema de publicação/subscrição como um funil que transfere dados de uma coleção no lado do servidor para uma coleção no lado do cliente.

    O protocolo que é utilizado neste funil é chamado DDP (que significa Distributed Data Protocol - Protocolo de Dados Distribuidos). Para aprender mais sobre DDP, você pode assistir essa palestra da Real-Time Conference por Matt DeBergalis (um dos fundadores do Meteor), ou este screencast por Chris Mather que conduz você por este conceito com um pouco mais de detalhe.

    Subscrição

    Mesmo que seja nossa intenção deixar qualquer publicação não marcada disponível para os clientes, nós não podemos simplesmente enviar milhares de publicações de uma vez. Nós precisamos de uma forma para os clientes especificarem qual subgrupo de dados eles precisam em um momento em particular, e é exatamente ai que as subscrições entram.

    Todo dado que você se subscrever vai ser espelhado no cliente graças ao Minimongo, a implementação do MongoDB feita pelo Meteor no lado do cliente.

    Por exemplo, vamos dizer que estamos atualmente navegando na página perfil de Bob Smith, e somente queremos mostrar seus posts.

    Subscrevendo nos posts de Bob irá espelhá-los no cliente .
    Subscrevendo nos posts de Bob irá espelhá-los no cliente .

    Primeiro, iríamos alterar nossa publicação para receber um parâmetro:

    // no servidor
    Meteor.publish('posts', function(author) {
      return Posts.find({flagged: false, author: author});
    });
    

    E podemos então definir este parâmetro quando nos subscrevermos nesta publicação no código do nosso aplicativo no lado do cliente:

    // no cliente
    Meteor.subscribe('posts', 'bob-smith');
    

    Assim é como torna uma aplicação Meteor escalável no lado do cliente: Ao invés de se subscrever para todos os dados disponíveis, você somente escolhe as partes que você precisa atualmente. Dessa forma, você vai evitar sobrecarga de memória no browser não importando o quão grande seja seu banco de dados no lado do servidor.

    Encontrando (finding)

    Agora os posts de Bob devem ser espalhados em mútiplas categorias (por exemplo: “JavaScript”, “Ruby” e “Python”). Talvez ainda queiramos carregar todos os posts de Bob na memória, mas nós queremos mostrar somente aqueles da categoria “JavaScript” por agora. Nessa hora que “encontrar” (find) aparece.

    Selecionando um subgrupo de documentos no cliente.
    Selecionando um subgrupo de documentos no cliente.

    Da mesma forma que fizemos no servidor, nós vamos usar a função Posts.find() para selecionar um subgrupo de nossos dados:

    // no cliente
    Template.posts.helpers({
      posts: function(){
        return Posts.find({author: 'bob-smith', category: 'JavaScript'});
      }
    });
    

    Agora que nós temos uma boa compreensão de como as publicações e subscrições funcionam, vamos mergulhar e rever alguns padrões comuns de implementação.

    Publicação Automática (autopublishing)

    Se você criar um projero Meteor do zero (i.e usando meteor create), ele vai ter automaticamente o pacote autopublish ativado. Para começar, vamos falar sobre o que ele exatamente faz.

    O objetivo do autopublish é deixar extremamente simples o início da codificação da sua aplicação Meteor, e ele faz isso espelhando automaticamente todos os dados do servidor para o cliente, tomando conta das publicações e subscrições para você.

    Autopublish
    Autopublish

    Como isso funciona? Suponha que você tenha uma coleção chamada 'posts' no servidor. Então autopublish vai automaticamente enviar cada post que encontrar na coleção posts no Mongo para uma coleção chamada 'posts' no cliente (assumindo que exista um).

    Se você estiver usando autopublish, você não precisa de pensar sobre publicações. Os dados serão ubíquos (presentes em todos os lugares), e as coisas se tornam simples. Claro, existem problema óbvios de ter uma cópia completa do banco de dados da sua aplicação cacheada na máquina de cada usuário.

    Por esse motivo, autopublish é apropriado somente quando você está começando, e ainda não pensou sobre as publicações

    Publicando Coleções Completas

    Uma vez que você remova o autopublish, você vai rapidamente perceber que todos os seus dados desapareceram do seu cliente. Uma forma simples de tê-los novamente é duplicando o que o autopublish faz, publicando uma coleção integralmente. Por exemplo:

    Meteor.publish('allPosts', function(){
      return Posts.find();
    });
    
    Publicando uma coleção completa
    Publicando uma coleção completa

    Nós ainda estamos publicando coleções completas, mas ao menos agora temos controle sobre quais coleções publicamos ou não. Neste caso, estamos publicando a coleção Posts mas Comments não.

    Publicando Coleções Parcialmente

    O próximo nível de controle é publicar somente parte de uma coleção. Por exemplo somente os posts que pertencem a um certo autor:

    Meteor.publish('somePosts', function(){
      return Posts.find({'author':'Tom'});
    });
    
    Publicando uma coleção parcialmente
    Publicando uma coleção parcialmente

    Nos Bastidores

    Se você leu a documentação do Meteor sobre publicação, você talvez deve estar sobrecarregado com as instruções de se usar added() e ready() para configurar os atributos de registros no cliente, e se deve ter se esforçado para conciliar isso com os aplicativos Meteor que você viu e nunca usam esses métodos.

    A razão é que o Meteor fornece uma importante conveniência: o método _publishCursor(). Você nunca viu seu uso? Talvez não diretamente, mas se você retornar um cursor (i.e. Posts.find({'author':'Tom'})) em uma função de publicação, isso é exatamente o que o Meteor está usando.

    Quando o Meteor vê que a publicação somePosts retornou um cursor, ele chama _publishCursor() também – você adivinhou – publicando este cursor automaticamente.

    Aqui o que o _publishCursor() faz:

    • Checa o nome da coleção no lado do servidor.
    • Puxa e encontra documentos a partir do cursor e o envia dentro de uma coleção no lado do cliente com o mesmo nome. (Usando .added() para fazer isso).
    • Sempre que um documento for adicionado, removido ou alterado, ele envia estas mudanças para a coleção no lado do cliente. (Ele usa .observe() no cursor e .added(), .changed() e .removed() para fazer isso).

    No exemplo acima, nós somos capazes de assegurar que o usuário tenha somente os posts que estiver interessado (os escritos por Tom) disponíveis em cache no lado do cliente.

    Publicando Propriedades Parciais

    Nós vimos como publicar somente alguns de nossos posts, mas nós podemos continuar cortando mais coisas! Vamos ver como publicar somente propriedades específicas.

    Como anteriormente, vamos usar find() para retornar um cursor, mas dessa vez vamos excluir alguns campos:

    Meteor.publish('allPosts', function(){
      return Posts.find({}, {fields: {
        date: false
      }});
    });
    
    Publicando propriedades parciais
    Publicando propriedades parciais

    Claro, nós também podemos combinar ambas as técnicas. Por exemplo, se nós quisermos retornar todos os posts de Tom enquanto deixamos de lado suas datas, podemos escrever assim:

    Meteor.publish('allPosts', function(){
      return Posts.find({'author':'Tom'}, {fields: {
        date: false
      }});
    });
    

    Resumindo

    Nós vimos como publicar todas as nossas propriedades de todos os documentos e de todas as coleções (com autopublish) até como publicar algumas propriedades de alguns documentos de algumas coleções.

    Isso cobre o básico do que você pode fazer com as publicações no Meteor, e estas simples técnicas devem ser suficientes para a vasta maioria dos casos.

    As vezes, você vai precisar ir além combinando, linkando ou fundindo publicações. Nós vamos cobrir isso em outro capítulo!