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.
persistence.sync.preferLocalConflictHandler
, em caso de conflito sempre irá escolher as alterações locais;
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.