Esta postagem mostra como você pode começar a replicar/sincronizar dados em dispositivos iOS usando o Couchbase Mobile. O Couchbase Mobile Stack é composto pelo Couchbase Server, pelo Sync Gateway e pelo banco de dados NoSQL incorporado Couchbase Lite. Em um artigo anterior postagemNa postagem anterior, discutimos como o Couchbase Lite pode ser usado como um banco de dados NoSQL autônomo incorporado em aplicativos iOS. Esta postagem o guiará por um aplicativo iOS de amostra em conjunto com um Sync Gateway que demonstrará os principais conceitos de replicação push e pull, autenticação e controle de acesso, canais e funções de sincronização.
Embora estejamos analisando a sincronização de dados no contexto de um aplicativo iOS em Swift, tudo o que for discutido aqui se aplica igualmente a aplicativos móveis desenvolvidos em qualquer outra plataforma (Android, iOS (ObjC), Xamarin). Os desvios serão especificados como tal.
OBSERVAÇÃO: discutiremos o Couchbase Mobile v1.4, que é a versão de produção atual. Há uma versão mais recente Visualização do desenvolvedor versão 2.0 do Couchbase Mobile, que tem muitos recursos novos e interessantes.
Couchbase Mobile
O Couchbase Mobile Stack inclui o Couchbase Server, o Sync Gateway e o banco de dados NoSQL incorporado Couchbase Lite. Esta postagem abordará os conceitos básicos do NoSQL replicação e sincronização de dados usando o Couchbase Mobile. Presumo que você esteja familiarizado com o desenvolvimento de aplicativos iOS, noções básicas de Swift, algumas noções básicas de NoSQL e tenha algum conhecimento do Couchbase. Se você quiser ler mais sobre o Couchbase Mobile, poderá encontrar muitos recursos no final deste post.
Gateway de sincronização do Couchbase
O Couchbase Sync Gateway é um mecanismo de sincronização voltado para a Internet que sincroniza com segurança os dados entre dispositivos, bem como entre dispositivos e a nuvem.
Ele expõe uma interface da Web que fornece
- Sincronização de dados entre dispositivos e na nuvem
- Controle de acesso
- Validação de dados
Você pode usar qualquer cliente HTTP para explorar melhor a interface. Dê uma olhada neste postagem sobre o uso do Postman para consultar a interface.
Há três conceitos principais relacionados à replicação ou sincronização de dados usando o Sync Gateway
Canal
Um canal pode ser visto como uma combinação de uma tag e uma fila de mensagens. Cada documento pode ser atribuído a um ou mais canais. Os documentos são atribuídos a canais que especificam quem pode acessar os documentos. Os usuários recebem acesso a um ou mais canais e só podem ler os documentos atribuídos a esses canais. Para obter detalhes, consulte a seção documentação sobre canais.
Função de sincronização
A função de sincronização é uma função JavaScript que é executada no Sync Gateway. Toda vez que um novo documento, revisão ou exclusão é adicionado a um banco de dados, a função de sincronização é chamada. A função de sincronização é responsável por
- Validação do documento,
- Autorização da mudança
- Atribuição de documentos a canais e
- Concessão de acesso dos usuários aos canais.
Para obter detalhes, consulte Documentação sobre a função Sync .
Replicação
A replicação, também conhecida como sincronização, é o processo de sincronização de alterações entre o banco de dados local e o Sync Gateway remoto. Há dois tipos: a replicação e a sincronização.
- A replicação push é usada para enviar alterações do banco de dados local para o remoto
- A replicação pull é usada para extrair alterações do banco de dados remoto para o local
Para obter detalhes, consulte documentação sobre réplicas.
Instalação do Couchbase Sync Gateway
Siga a Guia de instalação para instalar o Sync Gateway.
Inicie o Sync Gateway com o seguinte arquivo de configuração. O local exato do arquivo de configuração dependerá da plataforma. Consulte o guia de instalação para obter mais informações.
Arquivo de configuração do gateway de sincronização
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
{ "log": ["*"], "CORS": { "Origem":["*"] }, "bancos de dados": { "demo": { "servidor": "walrus:", "bucket" (balde): "default", "usuários": { "CONVIDADO": { "desativado": verdadeiro, "admin_channels": ["*"] } , "joe": {"senha":"senha" ,"desativado": falso, "admin_channels":["_public","_joe"]} , "jane":{"senha":"senha" ,"desativado": falso, "admin_channels":["_public","_jane"]} }, "sem suporte": { "user_views": { "habilitado":verdadeiro } }, "sync": ` função (doc, oldDoc){ // Verificar se o documento está sendo excluído Se (doc._deleted == undefined) { // Validação da versão atual com as chaves relevantes validateDocument(doc); } senão { // Validação do documento antigo com as chaves relevantes validateDocument(oldDoc); } var docOwner = (doc._deleted == undefined) ? doc.owner : oldDoc.owner; var publicChannel = "_public"; var privateChannel = "_"+docOwner; // Conceder ao usuário acesso de leitura aos canais públicos e ao próprio canal do usuário access(docOwner,[publicChannel,privateChannel]); // Verificar se essa foi uma atualização de documento (em vez de uma criação ou exclusão de documento) Se (doc._deleted == undefined && oldDoc != null && oldDoc._deleted == undefined) { Se (doc.tag != oldDoc.tag) { throw({forbidden: "Cannot change tag of document"}); } } // Verificar se o documento novo/atualizado está marcado como "público" var docTag = (doc._deleted == undefined) ? doc.tag : oldDoc.tag; Se (doc._deleted == undefined) { Se (docTag == "public") { // Todos os documentos marcados como públicos vão para o canal "público", que é aberto a todos channel(publicChannel); } senão { // Garantir que o proprietário do documento seja o usuário que está fazendo a solicitação requireUser(docOwner); // Todos os documentos marcados não públicos vão para um canal específico do usuário channel(privateChannel); } } senão { channel(doc.channels); } função validateDocument (doc) { // Validação básica do documento Se (!doc.tag ) { // Todo documento deve incluir uma tag throw({forbidden: "Tipo de documento inválido: tag não fornecida" + doc.tag}); } Se (!doc.owner) { // Todo documento deve incluir um proprietário throw({forbidden: "Tipo de documento inválido: Proprietário não fornecido" + doc.owner}); } } } ` } } } |
Aqui estão alguns pontos importantes a serem observados no arquivo de configuração
- Linha 8: O valor "walrus:" para "server" indica que o Sync Gateway deve manter os dados na memória e não tem o suporte de um servidor Couchbase.
- Linha 11: O acesso do usuário convidado está desativado
- Linha 12-13: Há dois usuários, "Jane" e "Joe", configurados no sistema. Ambos os usuários têm acesso a um canal "_public" e cada um tem acesso a seu próprio canal privado.
- Linha 22-100: Uma função de sincronização simples que faz o seguinte
- Linha 29-36: Validação de documento para garantir que o documento contenha as propriedades "tag" e "owner" definidas pelo usuário
- A propriedade "tag" é usada para especificar se o documento está disponível publicamente para qualquer usuário ou se é privado para um usuário
- A propriedade "owner" é usada para especificar se o documento está disponível publicamente para qualquer usuário ou se é privado para um usuário
- Linha 46: Dê ao usuário acesso ao "_public" e a um canal privado (identificado usando o proprietário do documento)
- Linhas 51-56: Se for uma atualização de documento, verifique se a propriedade "tag" não foi alterada nas revisões
- Linha 66: Atribuir todos os documentos com a tag "public" ao canal "_public"
- Linha 72: Atribuir todos os documentos com uma tag diferente de "public" ao canal privado
- Linha 75: Para documentos de canal privado, primeiro verifique se o proprietário do documento é a pessoa que está fazendo a solicitação
- Linha 29-36: Validação de documento para garantir que o documento contenha as propriedades "tag" e "owner" definidas pelo usuário
Couchbase Lite
O Couchbase Lite é um banco de dados NoSQL incorporado que é executado em dispositivos. O Couchbase Lite pode ser usado em vários modos de implantação. Primeiros passos com o Couchbase Lite postagem discute o modo de implantação autônomo. O Couchbase Lite pode ser usado em conjunto com um Sync Gateway remoto que permita a sincronização de dados entre dispositivos. Esta postagem discute o modo de implementação usando um Sync Gateway.
Há muitas opções para integrar a estrutura do Couchbase Lite em seu aplicativo iOS. Confira nosso Couchbase Mobile Guia de introdução para as várias opções de integração.
API nativa
O Couchbase Lite expõe uma API nativa para iOS, Android e Windows que permite que os aplicativos interajam facilmente com a plataforma Couchbase. Como desenvolvedor de aplicativos, você não precisa se preocupar com os aspectos internos do banco de dados incorporado do Couchbase Lite, mas pode se concentrar na criação de seu aplicativo incrível. A API nativa permite que você interaja com a estrutura do Couchbase Lite da mesma forma que faria com outras estruturas/subsistemas da plataforma. Novamente, discutiremos o Couchbase Mobile v1.4 nesta postagem do blog. Você pode obter uma lista completa das APIs em nosso site do Couchbase Desenvolvedor local.
Aplicativo de demonstração para iOS
Faça o download do projeto de demonstração do Xcode a partir deste Repositório do Github e mude para a ramificação "sync support". Usaremos esse aplicativo como exemplo no restante do blog. Esse aplicativo usa o Cocoapods para integrar a estrutura do Couchbase Lite.
1 2 |
git clone git@github.com:couchbaselabs/couchbase-leve-ios-autônomo-aplicativo de amostra.git git checkout suporte à sincronização |

Sincronização de documentos entre usuários
- Crie e inicie o aplicativo. Você deverá receber um alerta de login
- Digite o usuário "jane" e a senha de "password". Esse usuário foi configurado no arquivo de configuração do Sync Gateway
- Adicione o primeiro documento tocando no botão "+" no canto superior direito.
- Dê um nome ao documento e uma descrição de uma linha.
- Use a tag "private".
- Nos bastidores, o Replicador Push envia o documento para o Gateway de sincronização e é processado pela Função de sincronização. Com base na tag, a função Sync atribui o documento ao canal privado do usuário.
- Adicione um segundo documento tocando no botão "+" no canto superior direito.
- Dê um nome ao documento e uma descrição de uma linha
- Use a tag "public".
- Nos bastidores, o Replicador Push envia o documento para o Gateway de sincronização e é processado pela Função de sincronização. Com base na tag pública, a função Sync atribui o documento ao canal público
- Agora, faça o "logoff" da Jane. Você verá o alerta de login novamente
- Digite o usuário "joe" e a senha de "password". Esse usuário também foi configurado no arquivo de configuração do Sync Gateway
- O documento público que foi criado por Jane será listado.
- Nos bastidores, o Pull Replicator extrai todos os documentos do canal privado e do canal público de Joe. O documento público que foi criado por Jane é extraído. No entanto, como Joe não tinha acesso ao canal privado de Jane, o documento privado criado por Jane não é extraído.
Para verificar o estado das coisas no Sync Gateway, você pode consultar a interface Admin REST usando o Postman ou qualquer cliente HTTP.
Esta é a solicitação CURL para o Sync Gateway
1 2 3 4 5 |
enrolar -X OBTER \ 'http://localhost:4985/demo/_all_docs?access=false&channels=false&include_docs=true' \ -H 'accept: application/json' \ -H 'cache-control: no-cache' \ -H 'content-type: application/json' |
A resposta do Sync Gateway mostra os dois documentos atribuídos ao canal público e ao canal privado da Jane, respectivamente
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
{ "rows" (linhas): [ { "chave": "-6gCouN6jj0ScYgpMD7Qj1a", "id": "-6gCouN6jj0ScYgpMD7Qj1a", "valor": { "rev": "1-dfa6d453a1515ee3dd64012ccaf53046", "canais": [ "_jane" ] }, "doc": { "_id": "-6gCouN6jj0ScYgpMD7Qj1a", "_rev": "1-dfa6d453a1515ee3dd64012ccaf53046", "name" (nome): "doc101", "visão geral": "Este é um documento particular de Jane", "proprietário": "jane", "tag": "privado" } }, { "chave": "-A2wR44pAFCdu1Yufx14_1S", "id": "-A2wR44pAFCdu1Yufx14_1S", "valor": { "rev": "1-1a8cd0ea3b7574cf6f7ba4a10152a466", "canais": [ "_public" ] }, "doc": { "_id": "-A2wR44pAFCdu1Yufx14_1S", "_rev": "1-1a8cd0ea3b7574cf6f7ba4a10152a466", "name" (nome): "doc102", "visão geral": "Este é um documento público compartilhado por Jane", "proprietário": "jane", "tag": "público" } } ], "total_rows": 2, "update_seq": 5 } |
Explorando o código
Agora, vamos examinar os trechos de código relevantes do aplicativo de demonstração do iOS
Abertura/criação de um banco de dados por usuário
Aberto DocListTableViewController.swift e localize o arquivo openDatabaseForUser função.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
fazer { // 1: Definir opções do banco de dados deixar opções = CBLDatabaseOptions() opções.storageType = kCBLSQLiteStorage opções.criar = verdadeiro // 2: Criar um banco de dados para o usuário conectado, se ele não existir; caso contrário, retornar o identificador para o existente autônomo.db = tentar cbManager.openDatabaseNamed(usuário.com letras minúsculas(), com: opções) autônomo.showAlertWithTitle(NSLocalizedString("Sucesso!", comentário: ""), mensagem: NSLocalizedString("Banco de dados \(usuário) foi aberto com sucesso no caminho \(Gerenciador de CBL.defaultDirectory())", comentário: "")) // Iniciar a replicação com o Sync Gateway remoto startDatabaseReplicationForUser(usuário, senha: senha) retorno verdadeiro } captura { // tratar o erro } |
- Especifique as opções a serem associadas ao banco de dados. Explore as outras opções na classe CBLDatabaseOptions.
- Crie um banco de dados com o nome do usuário atual. Dessa forma, cada usuário do aplicativo terá sua própria cópia local do banco de dados. Se já existir um banco de dados com o nome, será retornado um identificador para o banco de dados existente; caso contrário, será criado um novo. Os nomes dos bancos de dados devem estar em letras minúsculas. Se for bem-sucedido, será criado um novo banco de dados local, caso ele não exista. Por padrão, o banco de dados será criado no caminho padrão (/Library/Application Support). Você pode especificar um diretório diferente ao instanciar o Gerenciador de CBL classe.
- Iniciar o processo de replicação do banco de dados para as credenciais de usuário fornecidas. Discutiremos o código de replicação em detalhes nas seções a seguir.
Obtenção de documentos
Abra o DocListTableViewController.swift e localize o arquivo getAllDocumentForUserDatabase função.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// 1. criar uma consulta para buscar todos os documentos. Você pode definir várias propriedades no objeto de consulta Consulta ao vivo = autônomo.db?.createAllDocumentsQuery().asLive() guarda deixar Consulta ao vivo = Consulta ao vivo mais { retorno } // 2: Opcionalmente, você pode definir várias propriedades no objeto de consulta. // Explore outras propriedades no objeto de consulta Consulta ao vivo.limite = UInt(UINT32_MAX) // Todos os documentos // query.postFilter = //3. Comece a observar as alterações no banco de dados autônomo.addLiveQueryObserverAndStartObserving() // 4: Executar a consulta para buscar documentos de forma assíncrona Consulta ao vivo.runAsync({ (enumerador, erro) em interruptor erro { caso nulo: // 5: O "enumerator" é do tipo CBLQueryEnumerator e é um enumerador para os resultados autônomo.docsEnumerator = enumerador padrão: autônomo.showAlertWithTitle(NSLocalizedString("Erro de busca de dados!", comentário: ""), mensagem: erro.localizedDescription (descrição localizada)) } }) } captura { // tratar o erro } |
- Obter o identificador do banco de dados com o nome especificado
- Crie um objeto de consulta. Essa consulta é usada para buscar todos os documentos. A função Sync no Sync Gateway garantirá que os documentos sejam extraídos apenas dos canais acessíveis ao usuário. Você pode criar um objeto de consulta regular ou um objeto de consulta "ao vivo". O objeto de consulta "ao vivo" é do tipo CBLLiveQuery que se atualiza automaticamente toda vez que o banco de dados é alterado de forma a afetar os resultados da consulta. A consulta tem várias propriedades que podem ser ajustadas para personalizar os resultados. Tente modificar as propriedades e veja o efeito nos resultados
- Você terá de adicionar explicitamente um observador ao objeto Live Query para ser notificado das alterações no banco de dados. Falaremos mais sobre isso na seção "Observação de alterações sincronizadas locais e remotas em documentos". Não se esqueça de remover o observador e parar de observar as alterações quando não precisar mais dele!
- Execute a consulta de forma assíncrona. Você também pode fazer isso de forma síncrona, se preferir, mas provavelmente é recomendável fazê-lo de forma assíncrona se os conjuntos de dados forem grandes.
Quando a consulta é executada com êxito, você obtém um objeto CBLQueryEnumerator. O enumerador de consultas permite enumerar os resultados. Ele se presta muito bem como fonte de dados para a Table View que exibe os resultados
Observação de alterações sincronizadas locais e remotas em documentos
Abra o DocListTableViewController.swift e localize a função addLiveQueryObserverAndStartObserving.
As alterações no banco de dados podem ser resultado das ações do usuário no dispositivo local ou podem ser resultado de alterações sincronizadas de outros dispositivos.
1 2 3 4 5 |
// 1. Específico do iOS. Adicionar observador ao objeto Query ativo Consulta ao vivo.addObserver(autônomo, forKeyPath: "rows" (linhas), opções: NSKeyValueObservingOptions.novo, contexto: nulo) // Comece a observar as mudanças Consulta ao vivo.iniciar() |
- Para ser notificado sobre as alterações no banco de dados que afetam os resultados da consulta, adicione um observador ao objeto Live Query. Em vez disso, aproveitaremos o padrão Key-Value-Observer do iOS para sermos notificados das alterações. Adicione um observador KVO ao objeto Live Query para começar a observar as alterações na propriedade "rows" do objeto Live Query Isso é tratado por meio de APIs de manipulador de eventos apropriadas em outras plataformas, como o addChangeListener no Android/Java.
- Comece a observar as mudanças.
Sempre que houver uma alteração no banco de dados que afete a propriedade "rows" do objeto LiveQuery, seu aplicativo será notificado das alterações. Ao receber a notificação de alteração, você pode atualizar a interface do usuário, que, nesse caso, seria recarregar a exibição da tabela.
1 2 3 4 |
se keyPath == "rows" (linhas) { autônomo.docsEnumerator = autônomo.Consulta ao vivo?.linhas tableView.recarregarDados() } |
Autenticação de solicitações de replicação
Aberto Arquivo DocListTableViewController.swift e localizar startDatabaseReplicationForUser função.
Todas as solicitações de replicação devem ser autenticadas. Neste aplicativo, usamos a autenticação básica HTTP.
1 |
deixar autenticação = Autenticador CBLA.basicAuthenticator(withName: usuário, senha: senha) |
Há vários tipos de autenticadores, a saber: Básico, Facebook, OAuth1, Persona, SSL/TLS Cert.
Replicação pull
Aberto Arquivo DocListTableViewController.swift e localizar startPullReplicationWithAuthenticator função.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// 1: Criar uma replicação Pull para começar a extrair da fonte remota deixar pullRepl = db?.createPullReplication(URL(string: kDbName, relativeTo: URL.inicial(string: kRemoteSyncUrl))!) // Definir o Authenticator para replicação pull pullRepl?.autenticador = autenticação // Procurar continuamente por mudanças pullRepl?.contínuo = verdadeiro // Opcionalmente, defina os canais dos quais extrair // pullRepl?.channels = [...] // Iniciar o replicador de tração pullRepl?.iniciar() |
- Crie um Pull Replicator para extrair alterações do Sync Gateway remoto. O kRemoteSyncUrl é o URL do ponto de extremidade do banco de dados remoto no Sync Gateway.
- Associar o Authenticator à replicação pull. Opcionalmente, é possível definir os canais dos quais os documentos devem ser extraídos
- A configuração da replicação como "contínua" permitirá que as atualizações de alterações sejam extraídas indefinidamente, a menos que sejam explicitamente interrompidas ou que o banco de dados seja fechado.
- Iniciar a replicação pull
Replicação por push
Aberto Arquivo DocListTableViewController.swift e localizar startPushReplicationWithAuthenticator função.
1 2 3 4 5 6 7 8 9 10 11 12 |
// 1: Criar uma replicação push para iniciar o push para a fonte remota deixar pushRepl = db?.createPushReplication(URL(string: kDbName, relativeTo: URL.inicial(string:kRemoteSyncUrl))!) // Definir o Authenticator para replicação por push pushRepl?.autenticador = autenticação // Enviar continuamente as alterações pushRepl?.contínuo = verdadeiro // Iniciar o replicador push pushRepl?.iniciar() |
- Crie um Replicador Push para enviar alterações para o Sync Gateway remoto. O kRemoteSyncUrl é o URL do ponto de extremidade do banco de dados remoto no Sync Gateway.
- Associe o Authenticator à replicação push.
- A configuração da replicação como "contínua" permitirá que as atualizações de alterações sejam enviadas indefinidamente, a menos que sejam explicitamente interrompidas ou que o banco de dados seja fechado.
- Iniciar a replicação push
Monitoramento do status da replicação
Abra o DBListTableViewController.swift e localize a função addRemoteDatabaseChangesObserverAndStartObserving.
1 2 3 4 5 6 7 |
// 1. Específico para iOS. Adicionar observador à Central de notificação para observar as alterações do replicador NotificationCenter.padrão.addObserver(forName: NSNotificação.Nome.cblReplicationChange, objeto: db, fila: nulo) { [sem dono autônomo] (notificação) em // Manipular alterações no status do replicador - Como a exibição do progresso // indicador quando o status é .running } |
Você pode monitorar o status da replicação adicionando um observador à Central de Notificações do iOS para ser notificado sobre cblReplicationChange notificações . Você pode usar o manipulador de notificações, por exemplo, para exibir indicadores de progresso apropriados para o usuário. Isso é tratado por meio de APIs de manipulador de eventos apropriadas em outras plataformas, como o addChangeListener no Android/Java.
E agora?
Gostaríamos muito de ouvir sua opinião. Portanto, se tiver dúvidas ou comentários, sinta-se à vontade para entrar em contato comigo pelo Twitter @rajagp ou envie-me um e-mail priya.rajagopal@couchbase.com. Se você quiser aprimorar o aplicativo de demonstração, envie uma solicitação pull para o diretório Github Repo.
O Fóruns de desenvolvimento do Couchbase Mobile Outro ótimo lugar para obter respostas para suas perguntas relacionadas a dispositivos móveis é o portal de desenvolvimento, que oferece detalhes sobre o Gateway de sincronização e Couchbase Lite . Tudo o que foi discutido aqui está no contexto do Couchbase Mobile 1.4. Há muitas mudanças novas e empolgantes a caminho do Couchbase Mobile 2.0. Não deixe de conferir o Visualização do desenvolvedor versão 2.0 do Couchbase Mobile.
Olá, Priya!!!!
Essa foi uma postagem muito boa sobre sincronização de dados em Swift. Se você puder fornecer também em objective C, agradeceria.
Obrigado!
Olá, Priya,
Baixei e instalei o aplicativo UserProfileDemo para Xamarin. Tentei testá-lo executando-o em dois simuladores simultaneamente, mas lamento dizer que o aplicativo não funciona como esperado. Os dados não são sincronizados quando se faz login com as mesmas credenciais no outro simulador. Ele sempre busca os dados do banco de dados local, mas nenhuma sincronização global funciona.
Seu comentário parece não estar relacionado a esta postagem do blog e ao tutorial (que, na verdade, é para iOS nativo no Couchbase Mobile 1.4). Você seguiu as instruções deste tutorial do Xamarin? https://docs.couchbase.com/userprofile-couchbase-mobile/sync/userprofile/xamarin/userprofile_sync.html. Se estiver tendo problemas com esse tutorial, publique em nossos fóruns de desenvolvimento (forums.couchbase.com) com os registros relevantes