Sincronização de dados simples para aplicativos da Web e móveis (trabalhando offline)

Ser capaz de trabalhar offline é um recurso esperado dos aplicativos móveis. Isso significa que temos que implementar um mecanismo de sincronização de dados que mantenha nossos dados locais e do servidor em sincronia. Eu me deparei com esse problema recentemente, enquanto estava trabalhando em uma API para um aplicativo e consegui trabalhar usando a técnica a seguir.

Digamos que temos um aplicativo simples tanto para Web quanto para mobile.

Nossos usuários podem se registrar / fazer login e começar a construir suas listas de tarefas, orçamentos, convites etc. Em nosso banco de dados armazenamos nossos ids em formato UUID ( http://en.wikipedia.org/wiki/Universally_unique_identifier ), que simplifica todo o processo de sincronização uma vez que não teremos que nos preocupar que 2 entradas (no banco de dados de aplicativos móveis e / ou nosso banco de dados) irão compartilhar o mesmo ‘id’. Além disso, em ambos os bancos de dados (servidor / aplicativo móvel) nas tabelas, armazenamos o carimbo de data / hora em que a entrada foi criada e atualizada (criada em, atualizada em).

Portanto, nossas tabelas de amostra simples agora em ambos os bancos de dados se parecem com isto


Id dos itens (UUID), título (string), qualquer 1 (string), qualquer 2 (int), qualquer coisa 3 (int), criado em (timestamp), updated_at (timestamp)

* Bancos de dados locais também devem ter um sinalizador ‘sincronizado’. Sempre que uma nova entrada é adicionada localmente e o telefone / tablet não está conectado à internet, esse sinalizador deve ser ‘falso’. Isso nos ajudará mais tarde a determinar se essa entrada precisa ser excluída ou enviada ao servidor.

Está na hora de falar com o desenvolvedor que está construindo o aplicativo iOS ou Android. Vamos chamá-lo de ‘Mark’ 🙂

PASSO 1 | Me mostre o que você tem

Portanto, instruímos Mark a fazer uma chamada de API para /somethingawesome/fetch.json quando o usuário efetuar login no aplicativo.

Esta chamada dará a Mark uma resposta json (ou qualquer outra coisa) com o id e os timestamps de todos os itens armazenados no servidor.
Amostra:

{
"budget":[
{
"bid":"f426f326-1cfe-4669-9e74-3c063fba542a",
"last_update":"2013-05-01 15:42:50"
},
{
"bid":"86c62841-1eae-4014-a719-9c353de55f37",
"last_update":"2013-05-01 15:42:49"
},
{
"bid":"2858f2ec-eb41-4fd1-b9c6-335429db09d7",
"last_update":"2013-05-01 15:42:48"
},
{
"bid":"759f9727-2ffd-41b5-ba0a-fb4fd8bdae65",
"last_update":"2013-05-01 15:42:24"
},
{
"bid":"cf5dc95c-d132-4f93-bfdc-0f05e377efd1",
"last_update":"2013-05-01 15:36:06"
},
{
"bid":"d1a327c8-5050-4f63-a4ea-5c25843f2fd6",
"last_update":"2013-05-01 15:36:06"
},
{
"bid":"083dc833-4e73-4fb7-838e-efe1f379a1b1",
"last_update":"2013-05-01 15:36:06"
},
{
"bid":"f77efc1b-0826-453c-b2f7-103095fd8046",
"last_update":"2013-05-01 15:35:50"
}
],
"invitation_categories":[
{
"icid":"c672b45a-f63d-4ada-8cca-28875e0489d5",
"last_update":"2013-05-01 15:43:12"
},
{
"icid":"e13b0399-1368-46c0-8659-bdf791069b44",
"last_update":"2013-05-01 15:43:09"
},
{
"icid":"3766d8e0-b4c4-4c83-aaf8-91b1577298d7",
"last_update":"2013-05-01 15:43:06"
},
{
"icid":"91ac021d-7452-4f49-a83d-e6d0293b0e1f",
"last_update":"2013-05-01 15:43:02"
},
{
"icid":"3173a537-35c6-4290-8680-253420c7ac84",
"last_update":"2013-05-01 15:43:03"
}
],
"invitations":[
{
"iid":"44ad90ef-8d26-4ebc-b570-f1e00dda9589",
"last_update":"2013-05-01 15:43:42"
},
{
"iid":"91e14912-4529-4413-8ea4-d564973e1c6e",
"last_update":"2013-05-01 15:43:40"
},
{
"iid":"d1adbac8-b1c2-498e-9b1f-414b7a8eb5c7",
"last_update":"2013-05-01 15:43:39"
},
{
"iid":"5874b44a-1ff2-42fa-9149-c940548e9301",
"last_update":"2013-05-01 15:43:38"
},
{
"iid":"4617878f-864d-433a-980b-23092d55463b",
"last_update":"2013-05-01 15:43:38"
},
{
"iid":"e354e387-00df-4fbc-b1a9-7fd24f27ac3d",
"last_update":"2013-05-01 15:43:37"
}
],
"wishlist":[
{
"gid":"6dd3d070-e992-445e-93ef-1e5ffb6f2ca1",
"last_update":"2013-05-01 15:42:19"
},
{
"gid":"5959f225-de4b-43ee-911d-80f07c061d9b",
"last_update":"2013-05-01 15:42:17"
},
{
"gid":"a5fa30d7-3ad7-4a0a-b85d-72a8514f6ef4",
"last_update":"2013-05-01 15:42:15"
},
{
"gid":"53db6a0a-6115-47d9-ae2d-04dab103032a",
"last_update":"2013-05-01 15:42:13"
}
],
"gift_list":[

],
"todo":[
{
"id":"1e240b66-c984-4028-9562-a32477c3c44b",
"last_update":"2013-05-01 15:44:54"
},
{
"id":"f1966b50-fbf5-45d9-ace1-8a059f34aeff",
"last_update":"2013-05-01 15:44:52"
},
{
"id":"f4559157-bb59-4c8a-82a2-30bd914a94cb",
"last_update":"2013-05-01 15:44:51"
},
{
"id":"97c5d1d0-9b45-4c41-8a10-c3db8d19d00f",
"last_update":"2013-05-01 15:44:49"
},
{
"id":"8467b52f-a676-4b12-b1e3-14ae9ef3e382",
"last_update":"2013-05-01 15:44:41"
},
{
"id":"560a668f-e79c-420e-a494-7971949f69e8",
"last_update":"2013-05-01 15:44:39"
}
]
}

Neste ponto. Mark irá verificar todos os ids nas tabelas correspondentes.

Caso 1: Ele não tem entrada para esse id em seu banco de dados local. Ação: ele precisa inserir a entrada em seu banco de dados.

Caso 2: ele tem entrada para esse id em seu banco de dados local. Ação: Verifica o carimbo de data / hora; se for mais recente que o servidor, atualizaremos a entrada do servidor, caso contrário, atualizaremos a entrada local.

Caso 3: Mark tem alguns ids em seu banco de dados que estão faltando no fetch.json. Ação: Verifica o sinalizador ‘sincronizado’ se for falso, isso significa que a entrada foi criada offline e precisamos adicioná-la ao nosso servidor; se sincronizado, verdadeiro significa que a entrada foi excluída e Mark também precisa excluí-la localmente.

PASSO 2 | Podemos ser amigos agora?

Por enquanto, tudo bem. Mark sabe o que precisa ser adicionado / atualizado ou excluído local e remotamente.

Iniciando a fase 2 ….

Nós o instruímos a fazer uma chamada de API para /somethingawesome/synchronise.json que deve ser semelhante a esta. Em cada chave (adicionar, atualizar, excluir, obter), temos um array de objetos que precisamos adicionar / atualizar ou excluir em nosso servidor.
Usando a tecla ‘get’, Mark nos informa sobre as entradas de que precisa de mais informações para que possamos enviá-las a ele.

{
"add":{
"budget":[

{
"bid":"e162cf44-f6d4-4f55-9096-242cb0d2f9a4",
"expense_title":"ADDITIONAL PRINTS/VIDEOS",
"assigned_to":8,
"status":1,
"cost":200,
"couple_id":"ac646699-47b1-4b70-b794-e069891bad56",
"brideOrGroom":"1",
"created":"2013-05-02 10:24:23"
},
{
"bid":"725cd03b-7307-497e-912a-63ae421fb4c7",
"expense_title":"INVITATIONS & REPLY CARDS",
"assigned_to":8,
"status":1,
"cost":200,
"couple_id":"ac646699-47b1-4b70-b794-e069891bad56",
"brideOrGroom":"1",
"created":"2013-05-02 10:24:23"
},
{
"bid":"edd11083-5efd-4a04-9857-2ff7d16f533f",
"expense_title":"Wedding rings",
"assigned_to":8,
"status":1,
"cost":300,
"couple_id":"ac646699-47b1-4b70-b794-e069891bad56",
"brideOrGroom":"1",
"created":"2013-05-02 10:24:23"
},
{
"bid":"6d1ed956-3eb1-48d2-8de3-f1c61eb66860",
"expense_title":"LIMO(S)/CAR RENTAL",
"assigned_to":8,
"status":1,
"cost":230,
"couple_id":"ac646699-47b1-4b70-b794-e069891bad56",
"brideOrGroom":"1",
"created":"2013-05-02 10:24:23"
}
],
"invitations":{

},
"gifts":{

}
},
"update":{
"budget":[
{
"bid":"d9bd512f-f3c0-4aba-b3ea-e1eafb39a33a",
"expense_title":"Ceremony location fee",
"assigned_to":8,
"status":1,
"cost":200,
"couple_id":"ac646699-47b1-4b70-b794-e069891bad56",
"brideOrGroom":"1",
"created":"2013-05-02 10:24:23"
},
{
"bid":"22971134-2de0-41ed-b1a7-ce95e5953422",
"expense_title":"Officiant Fee/Donation",
"assigned_to":8,
"status":1,
"cost":200,
"couple_id":"ac646699-47b1-4b70-b794-e069891bad56",
"brideOrGroom":"1",
"created":"2013-05-02 10:24:23"
},

{
"bid":"725cd03b-7307-497e-912a-63ae421fb4c7",
"expense_title":"INVITATIONS & REPLY CARDS",
"assigned_to":8,
"status":1,
"cost":200,
"couple_id":"ac646699-47b1-4b70-b794-e069891bad56",
"brideOrGroom":"1",
"created":"2013-05-02 10:24:23"
},
{
"bid":"edd11083-5efd-4a04-9857-2ff7d16f533f",
"expense_title":"Wedding rings",
"assigned_to":8,
"status":1,
"cost":300,
"couple_id":"ac646699-47b1-4b70-b794-e069891bad56",
"brideOrGroom":"1",
"created":"2013-05-02 10:24:23"
},
{
"bid":"6d1ed956-3eb1-48d2-8de3-f1c61eb66860",
"expense_title":"LIMO(S)/CAR RENTAL",
"assigned_to":8,
"status":1,
"cost":230,
"couple_id":"ac646699-47b1-4b70-b794-e069891bad56",
"brideOrGroom":"1",
"created":"2013-05-02 10:24:23"
}
],
"invitations":{

},
"gifts":{

}
},
"delete":{
"budget":[
"ac646699-47b1-4b70-b794-e069891bad56",
"204efd52-a6e1-4b6d-85de-086502cfb95e",
"ac646699-47b1-4b70-b794-e069891bad56",
"204efd52-a6e1-4b6d-85de-086502cfb95e",
"ac646699-47b1-4b70-b794-e069891bad56"
]
},
"get":{
"budget":[
"ac646699-47b1-4b70-b794-e069891bad56",
"68279950-2dc2-48ae-a29e-b1b8cc8484f1"
]
}
}

Esta chamada retornará Mark todas as informações detalhadas para as entradas que ele precisa armazenar em seu banco de dados local e também nos informará sobre as entradas que precisamos adicionar / atualizar ou excluir.

Também podemos usar as mesmas chamadas (buscar e sincronizar) sempre que quisermos no aplicativo. Por exemplo, em ‘Liberar para atualizar’ ou sempre que a conectividade com a Internet for restaurada.
Também poderíamos fazê-lo funcionar por seção, não enviando dados de todas as tabelas, mas construindo o json para incluir apenas tabelas específicas (apenas orçamento, por exemplo).

Também é importante implementar um mecanismo de bloqueio para garantir que 2 aplicativos móveis (por exemplo, nosso aplicativo Android e nosso aplicativo iOS) não estejam sincronizando ao mesmo tempo.

Eu adoraria ouvir sua opinião sobre a sincronização de aplicativos da web e móveis. Como você conseguiu resolver os problemas de sincronização? Existem ferramentas úteis que podemos usar nessas situações?