2015-08-20

Design Patterns em JavaScript – Parte 2 ( Constructor Pattern )

Continuando nossa série de Design Patterns em JavaScript que começou no  artigo passado vamos falar hoje sobre nosso primeiro padrão de projeto.

Em linguagens clássicas orientada a objetos, o constructor é um método especial usado para inicializar um objeto recém criado, uma vez que a memória já foi alocada para ele. Como em JavaScript quase tudo é um objeto, estamos interessados em objetos construtores.

Objetos construtores são usados para criar tipos especificos de objetos, preparando o objeto para uso e aceitar argumentos que um construtor pode usar para definir os valores da propriedade e métodos quando o objeto é criado pela primeira vez

Criando o Objeto

Segue uma das três maneiras comuns de se criar objeto em JavaScript:

// Opções de criar objetos vazios

var novoObjeto = {};

// ou
var novoObjeto = Object.create(Object.prototype);
// ou
var novoObjeto = new Object();

No último exemplo o "Object" constructor cria um objeto para um valor especificou ou quando não é passado nenhum valor ele cria um objeto vazio e retorna o mesmo.

Continuando, existem várias maneiras de atribuir a um objeto valores do tipo chave/valor, vamos ver algumas delas:

// Usando ponto (.)
 
// Setando propriedade
novoObjeto.chave= "Olá";
 
// Obtendo propriedade
var value = novoObjeto.chave;
 
 
// Usando colchetes([])
 
// Setando propriedade
novoObjeto["chave"] = "Olá";
 
// Obtendo propriedade
var value = novoObjeto["chave"];
 

Construtores básicos

Como vimos, JavaScript não suporta o conceito de classes mas ele simula com funções construtoras que trabalham com objetos O prefixo usado para chamar uma função contrutora é o new que cria o objeto com seus membros que foram definidos.

Nos construtores, a palavra this faz referência ao novo objeto que está sendo criado, vejamos:

// A RacionaisMCs "class"
function RacionaisMCs( music ) {
 
  this.music = music;
  this.year = "2014";
  // Adicionando comportamento a classe
  this.getInfo = function () {
    return this.music + " " + this.year;
  };
 
};
// Criando objetos
var primeiraMusica = new RacionaisMCs("Vida Loka");
var segundaMusica = new RacionaisMCs("Jesus Chorou");

console.log(primeiraMusica.getInfo()); 
console.log(segundaMusica.getInfo());

Este exemplo acima, não é a melhor maneira de criar objetos, pois sofre alguns problemas como fazer herança de forma dificil e performance pois o método getInfo é recriado e adicionado na memória toda vez que criamos um objeto do tipo RacionaisMCs

Como resolver isso? Com prototype!

Construtores com Prototype

Para quem não sabe o JavaScript é uma linguagem prototype-based. O que isso quer dizer? Quer dizer que ela é baseada em protótipos : Cada objeto tem um link interno para um outro objeto chamado prototype. Esse objeto prototype também tem um atributo prototype, e assim por diante até que null seja encontrado como prototype (como em uma implementação de fila em C, por exemplo). null, por definição, não tem prototype, e age como um link final em um encadeamento de protótipos (prototype chain).

Quando instanciamos um objeto, este já tem uma referência(__proto__) para o prototype da função que o construiu . Ou seja, só haverá um método na memória: o definido no prototype da função construtora! Assim ganhamos em memória e performance.

Sendo assim daqui pra frente vamos adotar o uso do prototype em nossas funções. Continuando nossa Classe ficaria assim:

// A RacionaisMCs "class"
function RacionaisMCs( music ) {
   this.music = music;
   this.year = "2014";
 };
 // Adicionando comportamento a classe
 RacionaisMCs.prototype.getInfo = function () {
   return this.music + " " + this.year;
 };

Agora temos uma única instância de getInfo que pode ser compartilhado entre todos objetos de RacionaisMCs

É isto pessoal, logo mais eu volto com nosso segundo Padrão.

router

Brincando com o novo Router do AngularJS – Parte 1

O novo Router do AngularJS é um dos pontos chaves dessa fase de transição que o framework tem passado, pois ele será o cerne de uma das estratégias de migração das aplicações já existentes. Sendo uma robusta solução para gerenciamento de rotas de uma aplicação e oferecendo basicamente os mesmos recursos do UI-Router tem como diferencial a possibilidade de controlar as rotas tanto do 1.x quanto do 2.0 simultaneamente, permitindo assim a migração iterativa incremental das aplicações já escritas com o Angular 1.x.

Essa estratégia de migração vem está sendo especificada na sessão "Component Router Interop Strategy" do documento: AngularJS 1 to Angular 2 Upgrade Strategy.

Porém, tendo em vista que o Angular 2.0 ainda está em fase beta e provavelmente ninguém irá migrar suas aplicações agora, vamos dar uma olhada em como esse novo Router irá funcionar no Angular 1.4, que é a versão mais recente do framework que temos no momento.

Primeiros passos

A primeira mudança notada em relação ao antigo Router é o fato de agora termos que fazer nossas configurações de rotas em um controller e não mais no config de nossa aplicação, o que acaba por nos obrigar a ter pelo menos um controller na aplicação.

 

Nesse controller nós devemos injetar a dependência do nosso $router e nela passar as configurações de nossas rotas. Essas configurações são compostas basicamente de uma lista de objetos contendo as informações de cada rota, onde o path é o caminho da rota e o component é o nome do controller que será usado em nossa rota.

function AppController($router){
  $router.config([
    { path: '/', component: 'home' },        // Será linkado para um controller
    { path: '/sobre', component: 'sobre' }   // Será linkado para um template
  ]);
}
AppController.$inject = ['$router'];

E assim como no antigo Router ainda é necessário especificar para nossa aplicações onde o conteúdo das rotas será renderizado e para isso foi introduzida a diretiva ng-viewport.

 

Certo, agora basta incluir os controllers e/ou templates especificados em nossas rotas, o único porém é que o novo Router é mais rígido quanto a localização desses componentes. Nos obrigando a seguir a recomendação de estrutura de aplicações descritas no documento: Best Practice Recommendations for Angular App Structure.

Ok, mas o que isso quer dizer? Isso quer dizer que os seus componentes devem seguir o padrão de diretórios especificado nesse documento, ou seja, em nossa aplicação ficaria algo como:

.
+– sampleapp/
|    +– app.js
|    +– components/
|    |    +– sobre/
|    |    |    +– sobre.html
|    |    |    +– sobre.js
|    |    +– home/
|    |    |    +– home.html
|    |    |    +– home.js
|    +– index.html

Agora que já conhecemos a nova estrutura de diretórios é bom lembrar que por padrão nossos controllers devem começar com uma letra maiúscula e terminar com o sufixo Controller, logo, os controllers dos componentes especificados anteriormente ficariam:

home.js

function HomeController(){}

sobre.js

function SobreController(){}

Nós podemos alterar os padrões descritos anteriormente através da utilização do serviço $componentLoaderProvider.

Mas e para mudar de rota? Bom, para navegar para uma nova rota nós podemos simplesmente fazer da forma antiga com com uma âncora <a href="#/sobre"> ou utilizar a nova diretiva ng-link:

Home
Sobre

Note que em nosso segundo link nós passamos um objeto com a propriedade appNome. As propriedades desse objeto serão recebidas como parâmetros pelo controller de nossa rota sobre.

Para acessar esses parâmetros no controller nós utilizamos o serviço $routeParams:

function SobreController($routeParams){
  this.autor = 'Jean Lucas de Carvalho';
  this.app = $routeParams.appNome;
}
SobreController.$inject = ['$routeParams'];

No novo Router também é possível realizar redirecionamentos de uma rota para outra através da opção redirectTo e atribuir apelidos para as rotas através as como no exemplo a seguir:

function AppController($router){
  $router.config([
    { path: '/', redirectTo: 'home'},
    { path: '/home', component: 'home', as: 'index' },
    { path: '/sobre/:appNome', component: 'sobre' }
  ]);

  this.nome = "Exemplo";
}
AppController.$inject = ['$router'];

Após utilizar um alias através do as nós podemos alterar nosso link para:

Home

Exemplo

 

 

Conclusão

O Novo Router ainda nos trás outras possibilidades como rotas filhas de um componente ou carregamento de múltiplos componentes utilizando diferentes viewports por rota mas eu deixarei para abordá-los no próximo artigo.

ps.: Originalmente esse artigo era gigante, mas devido a uma pane no meu editor markdown eu perdi 3/4 dele. Acabei resolvendo complementar o que eu não tinha perdido e publicar em duas partes, pois assim terei tempo para reescrever todo o conteúdo perdido ;)

sync

Sincronização SQL com persistence.js

Continuando a série sobre o persistence.js nesse artigo iremos entender como ele consegue realizar a sincronização entre dois bancos de dados. O persistence.js vem com um componente de sincronização para o client-side (persistence.sync.js) e um exemplo de implementação em Node.js para o server-side (persistence.sync.server.js). No entanto, nós podemos implementar a sincronização no backend em qualquer linguagem, desde que se obedeça os padrões definidos pelo ORM.

Client-side

No lado do cliente (no navegador ou em nosso aplicativo cordova/ionic), para fazer nossa aplicação sincronizar nós precisamos seguir alguns passos que, no caso de nosso exemplo do artigo anterior, ficaria assim:

var Feed = persistence.define('Feed', function(t){
    t.text('nome');
    t.text('conteudo');
    t.text('url');
    t.boolean('favoritado');
});

Feed.enableSync('/feedChanges');

No exemplo anterior nós definimos uma entidade Feed, que não havíamos definido no outro artigo, e note que para especificar que queremos sincronizá-la nós só precisamos chamar o método enableSync() passando o endpoint onde o servidor irá expor as mudanças daquela entidade.

Para iniciarmos uma sincronização nós chamamos o método syncAll(conflictHandler, successCallback, errorCallback), que recebe 3 callbacks como parâmetros, sendo as duas últimas opcionais. A primeira callback será executada quando ocorrer um conflito em alguma tupla da entidade, a segunda será executada caso a sincronização ocorra com sucesso e a terceira caso ocorra algum erro (como um erro 500 no servidor por exemplo).

Por padrão o persistence.sync.js já vem com duas estratégias de resolução de conflitos pré definidas e nós podemos simplesmente utilizar uma das duas. Mas a biblioteca é flexível o suficiente para nos permitir implementar o tipo de resolução de conflitos que acharmos conveniente.

  1. persistence.sync.preferLocalConflictHandler, em caso de conflito sempre irá escolher as alterações locais;
  2. persistence.sync.preferRemoteConflictHandler, em caso de conflito sempre irá escolher as alterações remotas.
Feed.syncAll(
        persistence.sync.preferLocalConflictHandler,
        function(){
            // Sincronização realizada com sucesso
        },
        function(err){
            // Ocorreu algum erro na sincronização
        });

É importante ressaltarmos que a responsabilidade de manter a base de dados consistente é do desenvolvedor. Se tivermos um relacionamento entre duas entidades e sincronizarmos apenas uma o banco de dados poderá ficar incosistente enquanto não sincronizarmos a outra.

Atualmente essa sincronização não consegue sincronizar relacionamentos muitos-para-muitos (many-to-many). Mas como nós sabemos como esse tipo de relacionamento funciona nos bancos relacionais nós podemos simplesmente fazer na unha.

Server-side

Aqui eu poderia focar na implementação para alguma linguagem específica, mas tendo em vista que já existem implementações (em Node.js, PHP e Java com Slim3 e AppEngine ), resolvi focar em como a sincronização funciona, pois assim poderemos criar a sincronização para qualquer linguagem e framework que quisermos. Basicamente para que nossa sincronização funcione todos os nossos endpoints tem que implementar os métodos GET e POST.

GET

O método GET deve possuir um parâmetro since que receberá o timestamp da última mudança realizada no banco local.

GET /feedChanges??since=1279888110373

A resposta da chamada acima deverá ser algo como:

{
    "now":1279888110421,
    "updates": [
        {
            "id": "F89F99F7B887423FB4B9C961C3883C0A",
            "favoritado": true,
            "_lastChange": 1279888110370
        }
    ]
}

Note que o servidor deverá nos retornar através da propriedade now o timestamp da hora atual do servidor para que possamos passar para o servidor em uma próxima atualização. O servidor também deve retornar um array updates que contenha as atualizações que ocorreram no servidor desde o timestamp que informamos em nossa requisição.

Todos os objetos retornados pelo servidor devem, obrigatoriamente, retornar pelo menos a propriedade id e a propriedade _lastChange que é o timestamp da última atualização daquele objeto.

Em alguns casos é necessário que o cliente sincronize apenas alguns dados do servidor, como na sincronização de dados do usuário. Para efetuarmos esse tipo de sincronização basta filtrarmos as respostas de nosso método GET conforme as nossas necessidades.

POST

O método de sincronização do persistence.sync.js irá efetuar uma requisição POST onde o corpo da requisição (também conhecido como body) será um array no formato JSON contendo todos os novos objetos e os objetos alterados desde a última sincronização. Todos os objetos devem possuir pelo menos a propriedade id.

POST /feedChanges

Corpo da requisição

[
    {
        "id":"F89F99F7B887423FB4B9C961C3883C0A",
        "favoritado": false
    }
]

Após a requisição o servidor deverá persistir as mudanças adicionando a cada objeto a propriedade _lastChange contendo o timestamp atual do servidor. Caso a persistência de dados seja efetuada com sucesso, esse timestamp será retornado para o cliente:

{
    "status": "ok",
    "now": 1279888110797
}

É importante que a propriedade _lastChange de todos os objetos inseridos/atualizados na mesma requisição sejam o mesmo.

Conclusão

A sincronização de dados do persistence.js não cobre todos os pontos que possam ser necessários em algumas aplicações, mas em grande parte dos casos (desde que o desenvolvedor tome as devidas precauções) ela é mais que o suficiente.

Acredito que nesses dois artigos eu cobri as partes mais interessantes da biblioteca, no entanto senti falta de um artigo introdutório sobre o ORM que eu procurarei expor em uma próxima ocasião.

2015-08-20

Design Patterns em JavaScript

Olá pessoal tenho o prazer de apresentar para vocês o começo de uma série de artigos sobre Design Patterns em JavaScript.

Definição

O que é isso? Porque usar?

Design Patterns ou Padrões de projetos são padrões que descrevem problemas que ocorrem frequentemente e então descreve uma solução ao problema, a fim de poder reusar essa solução milhares de vezes.

Ok, mas no que isso me ajuda?

Ajuda atribuindo responsabilidade a objetos, deixando o projeto fácil de compreender, reusar e dar manutenção. E claro dando flexibilidade ao mesmo.

Legal mas quando eu vou usar esses padrões?

Você chegou onde eu queria, como é a estrutura de um padrão? Basicamente ele tem quatro elementos essenciais são eles:

  • Nome
  • Descrição do problema ou contexto para os quais o padrão se aplica
  • Descrição da solução genérica proposta
  • Descrição da aplicação do padrão (custos e beneficios)

Pois bem, o entendimento de como funciona um padrão de projeto pode nos oferecer uma série de benefícios úteis, como por exemplo um raciocínio mais apreciado sobre determinado problema, saber se a solução proposta é ou não um padrão de projeto e até mesmo rever se o padrão utilizado é mesmo necessário.

Escrever padrões de projetos é uma tarefa desafiadora. Padrões não precisam somente de uma quantidade substancial de material para usuários finais, mas também de uma defesa convicente do porque eles são necessários

Depois de sabermos a definição do que é um padrão de projeto, as vezes pensamos que isso é o suficiente para indentificarmos padrões. Mas não nos enganemos, nem sempre um pedaço de código que estamos olhando está usando um padrão, as vezes ele apenas se parece com um padrão.

Quando olhamos um trecho de código que achamos estar usando algum padrão, devemos listar alguns aspectos do código em que acreditamos que não se trata de um padrão ou conjunto de padrões. Em muitos casos de análise olhamos apenas se o código segue boas práticas e princípios de design que podem coincidir com alguns padrões por acidente. Mas o fato é: qualquer solução que não faça interações e nem tenha regras definidas, essas soluções não tem padrões

Anti-Patterns

Se nós consideramos que padrões são uma boa prática, obviamente anti-patterns representa uma má pratica. Isto é descreve uma má solução para um problema particular que desencadou um mal comportamento.

Alguns exemplos de anti-patterns em JavaScript são:

Categorias de Design Patterns

Os padrões de projeto são dividos em dois parâmetros: propósito (criacional, estrutural, compotamental) e escopo (classes e objetos)

categorias design patters

Propósito

  • Criacional : Processo de criação de objetos
  • Estrutural : Composição de classes e objetos
  • Comportamental : Interação e distribuição de responsabilidades entre objetos e classes

Escopo

  • Classe: Relação entre classes e subclasses (herança)
  • Objetos: Relação entre objetos (associação, composição)

Uma breve introdução a Classes

JavaScript não tem classes, no entanto elas são simuladas usando funções.

A maneira mais comum de fazer isso é definir uma function e depois instanciar com o operador new. Nós podemos usar o this para definir novas propriedades e métodos como no exemplo abaixo:

// A Racionais "class"
function RacionaisMCs( music ) {

  this.music = music;
  this.year = "2014";

  this.getInfo = function () {
    return this.music + " " + this.year;
  };

}

Agora podemos instância um objeto usando a função construtora RacionaisMCs que acabamos de definir:

 

var musicaPreferida = new RacionaisMCs("Negro Drama");

musicaPreferida.year = "2002";

console.log(musicaPreferida.getInfo()); // "Negro Drama 2002"

 

Para mais maneiras de definir classes, olhe este link

Veja a parte 2 dessa série!

analytics_and_ionic

Google Analytics e Ionic Framework

O Google Analytics é um serviço gratuito do Google para monitoramento e análise do comportamento dos usuários de sua aplicação, seja ela um site ou um app mobile. Quem já trabalhou com web provavelmente já o utilizou em algum momento, mas no contexto de aplicativos, principalmenete os híbridos, pouco se é dito a respeito dele.

Entender os fluxos que o usuário segue, quais ações ele executa, quanto tempo ele passa na aplicação, etc. são de extrema importância para sabermos onde e o que melhorar em nossas aplicações para atingirmos um maior engajamento e uma maior taxa de conversão.

Para utilizarmos o Google Analytics em nossa aplicação Cordova/Ionic Framework nós precisamos adicioná-lo ao projeto usando o comando:

cordova plugin add https://github.com/danwilson/google-analytics-plugin.git

Após adicionado ao projeto nós devemos setar nosso tracking Id como no código a seguir:

var myApp = angular.module('myApp', ['ionic'])
    .run(function($ionicPlatform, $ionicPopup) {
        $ionicPlatform.ready(function() {
            if(typeof analytics !== undefined) {
                // No lugar de "UA-XXXXXXXX-XX" você deve colocar o seu tracking id
                analytics.startTrackerWithId("UA-XXXXXXXX-XX");
            } else {
                console.log("Google Analytics indisponível");
            }
        });
    });

O plugin do Google Analytics só funciona nas plataformas Android e iOS, por isso, nesse artigo, nós sempre verificamos se o analytics está disponível para que o código não quebre em outras plataformas e/ou no ambiente de desenvolvimento no browser.

Para realizarmos o acompanhamento de nossa aplicação podemos fazer como no exemplo a seguir:

myApp.controller('HomeController', function($scope) {
    if(typeof analytics !== undefined) {
        analytics.trackView("Home Controller");
    }

    $scope.doTheHarlemShake = function() {
        if(typeof analytics !== undefined) {
            analytics.trackEvent("Category", "Action", "Label", 25);
        }
    }
});

No código acima, assim que o HomeController é chamado nós o reportamos como uma view para o Analytics. E quando o usuário executa o método doTheHarlemShake (que pode ser disparado ao clicar em um botão, ao carregar mais itens em um infinite scroll, etc.) nós o reportamos como um evento para o Analytics.

O plugin possui outras opções de tracking que podem ser consultados na documentação e como cada aplicação possui próprias métricas e metas, caberá ao desenvolvedor saber quais dos métodos presentes no Google Analytics serão mais úteis para o contexto de sua aplicação.

pouchdb

Consultas rápidas no PouchDB

Como uma continuidade do último post sobre PouchDB, nesse artigo iremos aprofundar nas formas de realizar queries no banco de dados. Basicamente existem três formas de realizarmos consultas no PouchDB, uma é através da API allDocs() e as outras duas são através da API query(), onde temos as queries temporárias e as queries persistentes.

allDocs()

Utilizando a API allDocs() sem passar parâmetros nós basicamente estamos buscando por todos os registros de nosso banco de dados. Porém, embora pareça simples, essa API é bastante poderosa, pois ela nos fornece algumas opções para realizar nossas consultas. Dentre elas, as que eu mais utilizo são:

  • include_docs: caso essa propriedade não seja especificada todos os documentos serão retornados apenas com _id e _rev;
  • startkey & endkey: busca os documentos cujo IDs estão inclusos no intervalo especificado por essas opções;
  • limit: número máximo de documentos a serem retornados;
  • skip: número de documentos que iremos “pular” em nossa consulta;
  • descending: retorna os documentos em ordem inversa;
  • para outras opções acesse a documentação do allDocs().

Mas como podemos usar essas opções em nosso favor? Bom, para fazer uma paginação é bem claro que podemos usar as opções limit e skip, mas e se eu não quiser que todos os documentos do meu banco de dados sejam retornados, é possível filtrá-los?

Utilizando o startkey e o endkey nós podemos buscar apenas os documentos que estão em um determinado intervalo de IDs, certo? Imagine uma situação onde precisamos cadastrar “pessoas” e “empresas” em nosso banco de dados e queremos poder filtrar por cada um desses dois tipos de documentos.Utilizando as opções startkey e endkey nós podemos fazer isso desde que a gente defina os IDs de nossos documentos ao invés de deixar que o PouchDB faça isso.

var db = new PouchDB('mydb');

db.put({
    _id: 'pessoa-' + new Date().getTime(),
    nome: 'Jean Lucas de Carvalho',
    empresa: 'Futuring'
});

db.put({
    _id: 'pessoa-' + new Date().getTime(),
    nome: 'Michael Villander',
    empresa: 'Front In Brazil'
});

db.put({
    _id: 'empresa-' + new Date().getTime(),
    nome: 'Futuring',
    cidade: 'Goiânia'
});

No exemplo acima nós criamos nosso banco de dados e cadastramos três documentos nele: dois do tipo “pessoa” e um do tipo “empresa”. Agora realizaremos uma consulta utilizando o allDocs() buscando apenas os documentos com o tipo “pessoa”:

db.allDocs({
    include_docs: true,
    startkey: 'pessoa-',
    endkey: 'pessoa-\uffff'
}).then(function (result) {
    console.dir(result);
});

O exemplo acima nos retornará todos os documentos cujo _id começa com pessoa-. Essa consulta funciona graças ao caractere Unicode \uffff, pois é através dele que nós fazemos buscas por prefixo, ou seja, estamos dizendo ao PouchDB "me retorne todos os documentos cujo _id começa com pessoa-".

Essa é uma das formas de definir tipos de documentos no PouchDB, mas as vezes nós não podemos alterar a forma padrão como o banco de dados trata os IDs e pra esses casos uma outra forma bastante usada para definir tipos de documentos é atribuir uma propriedade ao documento especificando seu tipo como veremos na sessão a seguir.

query()

As vezes o allDocs() não é o suficiente para realizar consultas em nossas aplicações e aí nós precisamos de uma outra API que nos dê mais possibilidades, como fazer um Map/Reduce por exemplo. No PouchDB essa API é a query() e ela possui algumas peculiaridades.

Temporary queries

Como o próprio nome diz queries temporárias são temporárias, ou seja, elas são criadas no momento da consulta e depois deixam de existir. Por esse motivo elas não são indexadas o que acaba tornando esse tipo de query bem lenta se compararmos às Persistent Queries ou ao allDocs().

Para implementar o exemplo anterior utilizando a API query() nós não precisamos alterar a forma como o PouchDB trabalha com IDs. Na verdade nós devemos adicionar a todos os documentos uma propriedade que o identifique, como no exemplo a seguir, onde definimos uma propriedade type para cada documento e depois fazemos uma consulta que nos retorna todos os documentos cujo valor da propriedade type é igual a pessoa.

var db = new PouchDB('mydb');

db.post({
    nome: 'Jean Lucas de Carvalho',
    empresa: 'Futuring',
    type: 'pessoa'
});

db.post({
    nome: 'Michael Villander',
    empresa: 'Front In Brazil',
    type: 'pessoa'
});

db.post({
    nome: 'Futuring',
    cidade: 'Goiânia',
    type: 'empresa'
});

db.query(function (doc, emit) {
    emit(doc.type);
}, {key: 'pessoa', include_docs: true}).then(function (result) {
    // retorna todos os documentos cujo valor da propriedade "type" é igual a "pessoa"
});

A função emit faz parte da API de map/reduce do CouchDB. O que essa função basicamente faz é emitir, para cada documento, doc.type como key.

Persistent queries

Persistente queries são a forma recomendada de se usar a API query() segundo a documentação do PouchDB. E a diferença básica desse tipo de queries para as Temporary queries é o fato de que elas são salvas no banco e indexadas. Pelo fato de serem indexadas elas são bem mais performáticas que as queries temporárias. Para criarmos uma query persistente nós devemos seguir 2 passos:

  1. Criar um design document
var ddoc = {
    _id: '_design/typeIndex',
    views: {
        by_type: {
            map: function (doc) { emit(doc.type); }.toString()
        }
    }
};

db.put(ddoc).then(function () {
    // Persistent query criada com sucesso
});

O método .toString() ao final de nossa função map é necessário para transformar o objeto em um JSON válido.

  1. E então nós podemos usá-la de forma semelhante às queries temporárias:
db.query('typeIndex/by_type', {key: 'pessoa'}).then(function (result) {
    // retorna todos os documentos cujo valor da propriedade "type" é igual a "pessoa"
});

É interessante frisar que nossa primeira consulta realizada será relativamente lenta pois os índices só são criados quando nós realizamos uma consulta. A documentação recomenda a realização de uma consulta com limit: 0 para criar os índices sem retornar os documentos.

db.query('typeIndex/by_type', {limit: 0}).then(function (result) {
    // índices criados
});

Extra: Pouchdb-find

Agora que já conhecemos as principais formas de realizarmos consultas no Pouchdb é interessante conhecermos o Pouchdb-find, plugin que busca trazer uma forma de fazer consultas inspiradas na API do MongoDB. O plugin faz a mesma coisa que os métodos anteriormente apresentados, porém com menos código.

Para reproduzirmos o exemplo anterior utilizando o Pouchdb-find, nós precisamos criar um índice utilizando o método createIndex e então podemos utilizar o método find para realizar nossas consultas.

db.createIndex({
    index: {fields: ['type']}
}).then(function () {
    return db.find({
        selector: {type: 'pessoa'}
    });
}).then(function(result){
    // retorna todos os documentos cujo valor da propriedade "type" é igual a "pessoa"
});

O Pouchdb-find ainda não possui todos os métodos que necessitamos em muitas ocasiões (eu mesmo já precisei utilizar o $in), mas já é possível utilizá-lo. Para maiores informações acesse a documentação do plugin.

Conclusão

Existem muitas formas de realizarmos consultas no PouchDB e cabe ao desenvolvedor ter a sensibilidade de saber qual a melhor opção para cada caso, mas para sabermos qual a melhor opção é necessário conhecermos cada uma delas e espero ter conseguido apresentar o tema e esclarecer possíveis dúvidas.

Agora que já conhecemos as Persistent queries no próximo artigo a respeito do PouchDB irei apresentar as formas de sincronizar apenas determinados documentos no nosso banco de dados.

persistencejs

Dicas para trabalhar com o Ionic Framework – Persistence.js Migrations

Mesmo com todas as vantagens de bancos não relacionais como o PouchDB ainda existem aqueles que ainda preferem os bancos relacionais por fatores como familiaridade, curva de aprendizagem, equipe, backend já é relacional, etc. ou simplesmente por que o não relacional não atende ou não é a melhor indicação para o projeto em questão.

Quando essas pessoas vão trabalhar com o Ionic elas normalmente optam pelo plugin de SQLite do Cordova e não tem nada de errado nisso, porém se formos fazer um paralelo com desenvolvimento em Java ou PHP é como se utilizássemos os drives nativos de comunicação com o banco de dados sem utilizar um ORM como o Hibernate ou o Doctrine.

E essa forma de trabalhar sem um ORM pode acarretar alguns problemas quando nossos bancos mudam. Quando projetamos um banco de dados relacional nós esperamos que ele nunca vá mudar, pois estamos criando estruturas para armazenar os dados muito rígidas e altamente acopladas umas as outras. No entanto, conforme a aplicação vai crescendo normalmente é necessário alterar essa estrutura ou schema de dados que definimos e nesse momento iremos precisar de alguma ferramenta para fazer a migração do antigo schema de dados para o novo. E esse é um dos problemas que os ORM’s buscam solucionar.

E durante um dos meus cursos na Futuring sobre Apps híbridos eu fui questionado por meus alunos sobre a melhor forma de trabalhar com dados relacionais no Ionic pensando nessas migrações de dados. E essa pergunta me levou a fazer uma pesquisa para encontrar essa melhor forma para que eu pudesse responder aquela pergunta. Foi então que eu encontrei o persistence.js.

persistence.js

O persistence.js é um ORM escrito em javascript que trabalha de forma assíncrona. Ele pode ser usado tanto no navegador quanto no Node.js. Quem já trabalhou ou trabalha com algum tipo de ORM não terá dificuldades para se adaptar a ele. Mas o que torna essa biblioteca a melhor opção para quem quer trabalhar com bancos de dados relacionais são seus plugins. Basicamente ela possui 3 plugins que me chamaram a atenção:

  • persistence.search.js: adiciona a possibilidade realizar consultas do tipo FullText em nossos registros.
  • persistence.migrations.js: adiciona suporte a migração de dados (alterações no schema do banco de dados).
  • persistence.sync.js: implementa a capacidade de sincronização com servidores remotos desde que o backend respeite algumas regras.

Um outro ponto que vale a pena ressaltar é o fato de que quando o plugin de SQLite do Cordova está instalado em nossa aplicação, automaticamente o pesistence.js irá tentar trabalhar com ele (assim como o PouchDB) e se por algum motivo ele não consiguir, irá fazer o fallback para o WebSQL.

Embora a biblioteca possua inúmeros outros recursos especificamente nesse artigo iremos focar na migração de dados.

Migrations

Agora que já conhecemos a biblioteca e os motivos pelos quais eventualmente precisaremos realizar migrações de dados que tal darmos uma olhada em como fazê-lo com o persistence.js?

A primeira coisa que temos que saber é que o plugin de migrations foi inspirado nas migrations do Ruby on Rails, logo, quem já utiliza o Rails se sentirá ainda mais confortável com a ferramenta.

persistence.defineMigration(1, {
  up: function() {
    this.createTable('Feed', function(t){
      t.text('nome');
      t.text('conteudo');
      t.text('url');
      t.boolean('favoritado');
    });
  },
  down: function() {
    this.dropTable('Feed');
  }
});

De forma simplista uma migration pode ser definida como descrita acima, onde estamos adicionando uma tabela com o nome Feed que possui 3 colunas textuais (nome, conteudo e url respectivamente) e uma coluna booleana chamada favoritado. Uma coluna chamada id que será nossa chave primária também será adicionada, porém, como esse é um comportamento default nós não precisamos descrevê-la.

O primeiro argumento passado para nossa migration é a versão da mesma, logo, quando quisermos voltar para uma migration específica basta passar essa versão como parâmetro.

Como rodar as migrations?

Primeiro é necessário iniciar o plugin de migrations:

persistence.migrations.init(function() {
  // Callback que será executada após a inicialização
});

Depois precisaremos carregar nossas migrations. A documentação recomenda utilizar o RequireJS para isso. Após carregá-las nós devemos executá-las como no exemplo a seguir:

requirejs(['migrations'], function() {
    // Irá executar as migrações para a versão mais recente
    persistence.migrate(function(){
        // Callback que será executada após migração ser concluída
    });
});

Caso seja necessário voltar versões o primeiro parâmetro do método migrate será o número da versão a ser restaurada:

requirejs(['migrations'], function() {
    // Irá executar as migrações para a versão 1
    persistence.migrate(1, function(){
        // Callback que será executada após migração ser concluída
    });
});

Agora vocês conhecem o persistence.js e como realizar migrations com ele. O plugin de migrations também pode ser utilizado para tratar dados inconsistente ou popular novos campos como vocês podem consultar em sua documentação.

Enfim

Escolhi escrever sobre bancos de dados relacionais dessa vez pois notei que é um tópico recorrente na comunidade Ionic Framework Brasil. Nesse artigo vimos que é fácil trabalhar com dados relacionais em projetos Cordova e em meu próximo artigo sobre o persistence.js irei dissecar a forma de realizar sincronização em bancos de dados relacionais.

pouchdb

Dicas para trabalhar com o Ionic Framework – PouchDB

Originalmente essa era pra ser só mais uma dica para se trabalhar com o Ionic, e viria acompanhada de diversas outras, mas eu decidi discorrer sobre o assunto para poder argumentar e expressar os motivos pelos quais eu escolhi trabalhar com o PouchDB em meus apps.

Introdução

Quando pensamos em persistência de dados em dispositivos móveis logo pensamos no SQLite, mas as vezes uma mudança de paradigma pode tornar o desenvolvimento mais fácil.

Quando comecei a desenvolver com o Ionic eu tinha alguns requisitos básicos e um deles era sincronização com servidor sem complicação. Então eu queria ser capaz de persistir os dados no dispositivo móvel e quando necessário sincronizar com o servidor sem esquentar muito a cabeça com estratégias de sincronização.

E foi nesse contexto que conheci o PouchDB. O PouchDB é um SGBD não relacional, o famoso NoSQL, baseado em documentos que busca ser uma implementação em JavaScript do CouchdDB. O motivo dele seguir a mesma API do CouchDB é o fato dele ter sido concebido para sincronizar com o CouchDB como se fosse uma outra instância do CouchDB.

Como o PouchDB faz uso do IndexedDB e/ou do WebSQL para armazenamento dos dados ele possui uma limitação de tamanho máximo determinada por cada browser. Por isso é interessante utilizarmos ele em conjunto com o SQLite, pois o SQLite não possui tais limitações. O PouchDB irá utilizar o SQLite automaticamente caso você tenha instalado o plugin do mesmo através do comando:

cordova plugin add io.litehelpers.cordova.sqlitestorage

E para instalar o PouchDB nós utilizamos o bower:

bower install pouchdb --save

E depois importamos o mesmo em nosso index.html:

<script src="lib/pouchdb/dist/pouchdb.min.js"></script>

Trabalhar com o PouchDB é relativamente fácil e ele possui um guia básico bem completo em seu site.

Quem já trabalhou com o Mongo não terá muitos problemas para se habituar com o PouchDB, as diferenças mais gritantes entre os dois são: o protocolo de comunicação, o CouchDB utiliza o protocolo HTTP para realizar suas operações; e o fato do CouchDB não possuir Collections como o Mongo, ou seja, todos os documentos são armazenados no mesmo “nível” e nós precisamos de uma outra ferramenta para identificar o tipo daquele documento. E é aí que entram as Persistent Queries.

Mas isso é conversa pra um artigo mais aprofundado sobre o assunto. E o foco desse texto é mostrar o quão simples é sincronizar dois bancos de dados utilizando o PouchDB.

Atualmente o Couchbase e o Cloudant são outras plataformas de banco de dados que compartilham o mesmo protocolo de sincronização. Isso significa que o PouchDB também é capaz de sincronizar com essas plataformas.

Sincronização

É possível sincronizar os dados entre dois ou mais bancos através da replicação, ou replication, onde estamos replicando um banco para outro banco, ou seja, criando uma “cópia”.

var localDB = new PouchDB('mylocaldb')
var remoteDB = new PouchDB('http://localhost:5984/myremotedb')

localDB.replicate.to(remoteDB).on('complete', function () {
  // Sincronizado!
}).on('error', function (err) {
  // Ops, tivemos um erro!
});

No exemplo acima estamos replicando os dados do banco local para o banco remoto, porém o contrário (do remoto para o local não acontece). Replicamos do remoto para o local utilizando o replicate.from:

var localDB = new PouchDB('mylocaldb');
var remoteDB = new PouchDB('http://localhost:5984/myremotedb');

localDB.replicate.from(remoteDB).on('complete', function () {
  // Sincronizado!
}).on('error', function (err) {
  // Ops, tivemos um erro!
});

Nesse caso estamos replicando do banco remoto para o local. Mas e se quisermos sincronizar tudo? Enviar as alterações do local para o remoto e as alterações do remoto para o local?

localDB.replicate.to(remoteDB);
localDB.replicate.from(remoteDB);

No entanto, o PouchDB possui um atalho pra tornar ainda mais fácil esse caso específico:

localDB.sync(remoteDB).on('complete', function () {
  // Sincronizado!
}).on('error', function (err) {
  // Ops, tivemos um erro!
});

Mas e se quisermos manter os dois bancos sempre sincronizados?

localDB.sync(remoteDB, {
  live: true
}).on('change', function () {
  // Ocorreu uma alteração!
}).on('error', function (err) {
  // Ops, tivemos um erro!
});

No entanto, quando o usuário ficar offline nós teremos um erro e a sincronização será quebrada. Para resolver esse problema nós precisamos dizer ao PouchDB para tentar novamente:

localDB.sync(remoteDB, {
  live: true,
  retry: true
}).on('change', function (change) {
  // Ocorreu uma alteração!
}).on('paused', function (info) {
  // replicação pausada, normalmente por que o usuário está offline
}).on('active', function (info) {
  // replicação continuada
}).on('error', function (err) {
  // erro desconhecido, normalmente não deveria acontecer
})));

Também é possível sincronizar apenas partes específicas do nosso banco utilizando as Persistent Queries, mas vou deixar para uma próxima oportunidade.

Conclusão

Uma abordagem não relacional pode não encaixar em todos os tipos de aplicações e normalmente essa mudança de paradigma em nossa forma de desenvolver leva certo tempo para assimilação, então temos que saber pesar as vantagens e desvantagens de utilizar de cada tipo de banco de dados para podermos extrair o melhor de cada.

Para entender melhor como funciona essa abordagem dos bancos não relacionais baseados em documentos eu recomendo esses artigos do Jean Carlo Nascimento (@osuissa):

MongoDb – Como mudar seu jeito relacional de pensar – Parte 1

MongoDb – Como mudar seu jeito relacional de pensar – Parte 2

Aprendendo MongoDb a partir do Relacional