{"id":2929,"date":"2017-03-09T23:13:48","date_gmt":"2017-03-10T07:13:48","guid":{"rendered":"https:\/\/www.couchbase.com\/blog\/?p=2929"},"modified":"2025-06-13T19:58:39","modified_gmt":"2025-06-14T02:58:39","slug":"couchbase-mobile-changes-explorer-part-2","status":"publish","type":"post","link":"https:\/\/www.couchbase.com\/blog\/pt\/couchbase-mobile-changes-explorer-part-2\/","title":{"rendered":"Couchbase Mobile Changes Explorer - Parte. 2"},"content":{"rendered":"<h2>Introdu\u00e7\u00e3o<\/h2>\n<p>O <a href=\"https:\/\/www.couchbase.com\/blog\/pt\/developers\/mobile\/?utm_source=blogs&amp;utm_medium=link&amp;utm_campaign=blogs\">Couchbase Mobile<\/a> <a href=\"https:\/\/developer.couchbase.com\/documentation\/mobile\/current\/guides\/sync-gateway\/index.html?utm_source=blogs&amp;utm_medium=link&amp;utm_campaign=blogs\">Gateway de sincroniza\u00e7\u00e3o<\/a> O feed de altera\u00e7\u00f5es oferece uma maneira de monitorar eventos em uma implanta\u00e7\u00e3o m\u00f3vel. O feed torna vi\u00e1vel a cria\u00e7\u00e3o de l\u00f3gica comercial sofisticada. Escrevi uma ferramenta para ajudar a examinar e entender o feed. Voc\u00ea pode ler uma introdu\u00e7\u00e3o e uma descri\u00e7\u00e3o em <a href=\"https:\/\/www.couchbase.com\/blog\/pt\/couchbase-mobile-changes-explorer-part-1\/\">primeira parte<\/a> desta s\u00e9rie de duas partes. O c\u00f3digo tamb\u00e9m serve como exemplo de escuta do feed.<\/p>\n<h2>O c\u00f3digo<\/h2>\n<p>Inclu\u00ed aqui as principais classes do c\u00f3digo do aplicativo. Esta \u00e9 a primeira vers\u00e3o, portanto, ela pode receber muitos aprimoramentos. Os par\u00e2metros est\u00e3o todos conectados. Confira o projeto <a href=\"https:\/\/github.com\/couchbaselabs\/CBM-Changes-Explorer\">aqui no GitHub<\/a> para obter atualiza\u00e7\u00f5es.  Voc\u00ea tamb\u00e9m pode encontrar instru\u00e7\u00f5es para criar, executar e empacotar o aplicativo l\u00e1.<\/p>\n<h3>JavaFX: A classe Controller<\/h3>\n<p>O JavaFX divide aplicativos simples em uma classe controladora e uma interface de usu\u00e1rio declarativa. Vamos examinar o controlador.<\/p>\n<pre class=\"lang:java decode:true\">pacote com.couchbase.mobile;\r\n\r\nimport com.couchbase.lite.*;\r\nimport com.fasterxml.jackson.core.JsonProcessingException;\r\nimport javafx.application.Platform;\r\nimport javafx.beans.value.ChangeListener;\r\nImportar javafx.beans.value.ObservableValue;\r\nimport javafx.collections.FXCollections;\r\nImportar javafx.collections.ObservableList;\r\nImportar javafx.event.ActionEvent;\r\nimport javafx.fxml.FXML;\r\nimport javafx.scene.control.*;\r\nimport javafx.scene.control.TextField;\r\n\r\nImportar java.io.IOException;\r\nImportar java.net.MalformedURLException;\r\nimport java.net.URL;\r\nimport java.util.Map;\r\n\r\nimport static com.couchbase.mobile.Runtime.mapper;\r\n\r\npublic class Controller implements LiveQuery.ChangeListener, ChangeListener,\r\n    SGMonitor.ChangesFeedListener, DBService.ReplicationStateListener {\r\n  private static final String SYNC_GATEWAY_HOST = \"https:\/\/localhost\";\r\n  private static final String SG_PUBLIC_URL = SYNC_GATEWAY_HOST + \":4984\/\" + DBService.DATABASE;\r\n  private static final String SG_ADMIN_URL = SYNC_GATEWAY_HOST + \":4985\/\" + DBService.DATABASE;\r\n\r\n  private static final String TOGGLE_INACTIVE = \"-fx-background-color: #e6555d;\";\r\n  private static final String TOGGLE_ACTIVE = \"-fx-background-color: #ade6a6;\";\r\n  private static final String TOGGLE_DISABLED = \"-fx-background-color: #555555;\";\r\n\r\n  @FXML private ListView documentList;\r\n  private ObservableList documents = FXCollections.observableArrayList();\r\n  @FXML private TextArea contentsText;\r\n  @FXML private TextArea changesFeed;\r\n  @FXML private TextField usernameText;\r\n  @FXML private TextField passwordText; @FXML private TextField passwordText;\r\n  @FXML private ToggleButton applyCredentialsBtn;\r\n  @FXML private ToggleButton syncBtn;\r\n\r\n  Servi\u00e7o DBService privado = DBService.getInstance();\r\n  private Database db = service.getDatabase();\r\n  SGMonitor privado changesMonitor;\r\n  LiveQuery privado liveQuery;\r\n<\/pre>\n<p>Essa primeira listagem mostra um monte de c\u00f3digo padr\u00e3o. Implemento v\u00e1rios ouvintes para a interface do usu\u00e1rio dentro da pr\u00f3pria classe para reduzir o n\u00famero de arquivos. Isso \u00e9 para fins ilustrativos.<\/p>\n<p>O <code>@FXML<\/code> marcam todos os campos que a estrutura associar\u00e1 automaticamente a partes da interface do usu\u00e1rio.<\/p>\n<p>Em seguida, vem a inicializa\u00e7\u00e3o. O JavaFX chama esse m\u00e9todo como parte de seu ciclo de vida padr\u00e3o.<\/p>\n<pre class=\"lang:java decode:true\">  @FXML private void initialize() {\r\n    documentListInitialize();\r\n    documentList.setItems(documents);\r\n\r\n    setState(applyCredentialsBtn, false);\r\n    setState(syncBtn, false);\r\n\r\n    service.addReplicationStateListener(this);\r\n\r\n    changesMonitor = new SGMonitor(SG_ADMIN_URL, \"false\", \"true\", \"0\", \"all_docs\", this);\r\n    changesMonitor.start();\r\n  }\r\n\r\n  private void documentListInitialize() {\r\n    Query query = db.createAllDocumentsQuery();\r\n    query.setAllDocsMode(Query.AllDocsMode.INCLUDE_DELETED);\r\n    liveQuery = query.toLiveQuery();\r\n    liveQuery.addChangeListener(this);\r\n    liveQuery.start();\r\n\r\n    documentList.getSelectionModel().selectedItemProperty().addListener(this);\r\n  }<\/pre>\n<p>Separei a inicializa\u00e7\u00e3o da lista de documentos em sua pr\u00f3pria rotina. A lista de documentos \u00e9 vinculada ao <code>documentList<\/code> vari\u00e1vel. Por sua vez <code>documentList<\/code> atualizar\u00e1 a interface do usu\u00e1rio sempre que a lista de itens que passamos for alterada.<\/p>\n<p>Configurei uma consulta em tempo real para monitorar o banco de dados do cliente quanto a quaisquer altera\u00e7\u00f5es. Isso acontece por meio de uma consulta de \"todos os documentos\". Uma consulta de todos os documentos n\u00e3o requer uma visualiza\u00e7\u00e3o associada. Eu defino <code>INCLUDE_DELETED<\/code> para que a ferramenta possa mostrar a apar\u00eancia de um documento exclu\u00eddo no banco de dados.<\/p>\n<p>Com os outros v\u00ednculos em vigor, s\u00f3 precisamos atualizar o <code>documentos<\/code> lista. Veremos o ouvinte de consulta ao vivo que faz isso mais adiante.<\/p>\n<p>As pr\u00f3ximas linhas definem o estado inicial de alguns bot\u00f5es de altern\u00e2ncia. Preciso de um ouvinte extra para manter o <code>Sincroniza\u00e7\u00e3o<\/code> consistente com o estado real das replica\u00e7\u00f5es. Mais informa\u00e7\u00f5es sobre isso ao longo do artigo.<\/p>\n<p>Escrevi uma classe separada para monitorar o Sync Gateway. O c\u00f3digo de inicializa\u00e7\u00e3o foi conclu\u00eddo com a cria\u00e7\u00e3o de uma nova inst\u00e2ncia de monitor e seu in\u00edcio.<\/p>\n<p>A pr\u00f3xima se\u00e7\u00e3o cont\u00e9m v\u00e1rios ouvintes.<\/p>\n<pre class=\"lang:java decode:true\">  \/\/ LiveQuery.ChangeListener\r\n  @Override\r\n  public void changed(LiveQuery.ChangeEvent event) {\r\n    if (event.getSource().equals(liveQuery)) {\r\n      Platform.runLater(() -&gt; {\r\n        QueryEnumerator rows = event.getRows();\r\n\r\n        documents.clear();\r\n\r\n        rows.forEach(queryRow -&gt; documents.add(queryRow.getDocumentId()));\r\n      });\r\n    }\r\n  }<\/pre>\n<p>Aqui est\u00e1 o ouvinte de consulta ao vivo que \u00e9 chamado sempre que o banco de dados local \u00e9 alterado. Eu n\u00e3o projetei a ferramenta para trabalhar com bancos de dados enormes. Portanto, sempre que os dados mudavam, eu simplesmente adotava a abordagem de for\u00e7a bruta de reler todos os documentos. O <code>getRows<\/code> retorna um enumerador que indexar\u00e1 e far\u00e1 exatamente isso. O JavaFX se encarrega de atualizar a interface do usu\u00e1rio quando <code>documentos<\/code> mudan\u00e7as.<\/p>\n<pre class=\"lang:java decode:true\">  \/\/ ListView ChangeListener\r\n  @Override\r\n  public void changed(ObservableValue observable, String oldId, String newId) {\r\n    if (null == newId) return;\r\n\r\n    Map properties = db.getDocument(newId).getProperties();\r\n\r\n    try {\r\n      String json = mapper.writeValueAsString(properties);\r\n\r\n      contentsText.setText(prettyText(json));\r\n    } catch (JsonProcessingException ex) {\r\n      ex.printStackTrace();\r\n      Dialog.display(ex);\r\n    }\r\n  }<\/pre>\n<p>Esse ouvinte cuida do rastreamento quando um usu\u00e1rio clica em uma entrada na lista de documentos. As entradas s\u00e3o os IDs dos documentos, portanto, podemos usar uma sele\u00e7\u00e3o para extrair o documento diretamente do banco de dados.<\/p>\n<pre class=\"lang:java decode:true\">  \/\/ SGMonitor.ChangesFeedListener\r\n  @Override\r\n  public void onResponse(String body) {\r\n    changesFeed.appendText(prettyText((String) body));\r\n  }<\/pre>\n<p>Usei uma abordagem de retorno de chamada para obter os resultados do feed de altera\u00e7\u00f5es. A interface \u00e9 definida na se\u00e7\u00e3o <code>SGMonitor<\/code> classe. Ela tem apenas um m\u00e9todo. Nessa implementa\u00e7\u00e3o, simplesmente pego o corpo da resposta do feed e o coloco sobre o texto existente no painel de texto do feed de altera\u00e7\u00f5es. Tamb\u00e9m foi feita uma pequena formata\u00e7\u00e3o para facilitar a leitura.<\/p>\n<pre class=\"lang:java decode:true\">  \/\/ DBService.ReplicationStateListener\r\n  @Override\r\n  public void onChange(boolean isActive) {\r\n    setState(syncBtn, isActive);\r\n  }<\/pre>\n<p>Por fim, adicionei um ouvinte para a atividade de replica\u00e7\u00e3o. A interface vem da classe auxiliar DBService. Escrevi um pouco sobre como detectar o estado de uma replica\u00e7\u00e3o <a href=\"https:\/\/www.couchbase.com\/blog\/pt\/determining-status-replication-couchbase-lite\/\">aqui<\/a>. Para este aplicativo, s\u00f3 preciso saber se uma replica\u00e7\u00e3o est\u00e1 sendo executada ou n\u00e3o para manter a <code>Sincroniza\u00e7\u00e3o<\/code> estado do bot\u00e3o consistente. Isso lida com casos em que um usu\u00e1rio tenta iniciar uma sincroniza\u00e7\u00e3o, mas ela falha. Isso pode ocorrer se o usu\u00e1rio precisar fornecer credenciais de autentica\u00e7\u00e3o, mas n\u00e3o as tiver fornecido, por exemplo.<\/p>\n<p>Em seguida, temos v\u00e1rios m\u00e9todos vinculados a elementos da interface do usu\u00e1rio. O JavaFX cuida de grande parte da fia\u00e7\u00e3o.<\/p>\n<pre class=\"lang:java decode:true\">  @FXML private void applyCredentialsToggled(ActionEvent event) {\r\n    String nome de usu\u00e1rio = null;\r\n    String password = null;\r\n\r\n    Se (applyCredentialsBtn.isSelected()) {\r\n      username = usernameText.getText();\r\n      password = passwordText.getText();\r\n    }\r\n\r\n    DBService.getInstance().setCredentials(username, password);\r\n    applyCredentialsBtn.setStyle(applyCredentialsBtn.isSelected() ? TOGGLE_ACTIVE : TOGGLE_INACTIVE);\r\n  }<\/pre>\n<p>Aqui eu defino o uso de credenciais de autentica\u00e7\u00e3o sempre que o bot\u00e3o correspondente for acionado.<\/p>\n<pre class=\"lang:java decode:true\">  @FXML private void saveContentsClicked(ActionEvent event) {\r\n    Map properties = null;\r\n    Document document;\r\n\r\n    try {\r\n      properties = mapper.readValue(contentsText.getText(), Map.class);\r\n    } catch (IOException ex) {\r\n      ex.printStackTrace();\r\n      Dialog.display(ex);\r\n    }\r\n\r\n    se (properties.containsKey(\"_id\")) {\r\n      document = db.getDocument((String) properties.get(\"_id\"));\r\n    } else {\r\n      document = db.createDocument();\r\n    }\r\n\r\n    try {\r\n      document.putProperties(properties);\r\n    } catch (CouchbaseLiteException ex) {\r\n      ex.printStackTrace();\r\n      Dialog.display(ex);\r\n    }\r\n  }\r\n<\/pre>\n<p>Esse c\u00f3digo mostra alguns itens interessantes. Eu uso um Jackson <code>Mapeador de objetos<\/code> para converter o texto no painel de conte\u00fado em um mapa de propriedades.<\/p>\n<p>Em seguida, verifico se h\u00e1 uma entrada <code>_id<\/code>. O Couchbase Mobile reserva a maioria das propriedades que come\u00e7am com um \"_\" para uso do sistema (com exce\u00e7\u00f5es especiais). Se o texto que estivermos tentando converter contiver <code>_id<\/code>Se o documento n\u00e3o for editado, presumo que seja uma edi\u00e7\u00e3o de um documento existente. Caso contr\u00e1rio, crio um novo documento.<\/p>\n<p>Portanto, em poucas palavras, temos um exemplo de cria\u00e7\u00e3o e atualiza\u00e7\u00e3o de documentos. Essa n\u00e3o \u00e9 a maneira preferida de atualizar, embora seja suficiente em muitos casos. Voc\u00ea pode ler mais sobre atualiza\u00e7\u00f5es <a href=\"https:\/\/www.couchbase.com\/blog\/pt\/better-updates-couchbase-lite\/\">aqui<\/a>.<\/p>\n<pre><code class=\"language-java\">  @FXML private void syncToggled(ActionEvent event) {\r\n    try {\r\n      syncBtn.setDisable(true);\r\n      syncBtn.setStyle(TOGGLE_DISABLED);\r\n      service.toggleReplication(new URL(SG_PUBLIC_URL), true);\r\n    } catch (Exception ex) {\r\n      ex.printStackTrace();\r\n      Dialog.display(ex);\r\n      syncBtn.setDisable(false);\r\n    }\r\n  }<\/code><\/pre>\n<p>Isso reage \u00e0 altern\u00e2ncia do <code>Sincroniza\u00e7\u00e3o<\/code> bot\u00e3o. Lembre-se, por\u00e9m, de que usamos um ouvinte para verificar o estado em outro lugar.<\/p>\n<pre class=\"lang:java decode:true\">  @FXML private void exitClicked(ActionEvent event) {\r\n    \/\/ Tentar encerrar tudo graciosamente\r\n    changesMonitor.stop();\r\n    liveQuery.stop();\r\n    service.stopReplication();\r\n    db.close();\r\n    db.getManager().close();\r\n\r\n    Platform.exit();\r\n  }\r\n\r\n  private void setState(ToggleButton btn, boolean active) {\r\n    btn.setSelected(active);\r\n    btn.setStyle(active ? TOGGLE_ACTIVE : TOGGLE_INACTIVE);\r\n    btn.setDisable(false);\r\n  }\r\n\r\n  private String prettyText(String json) {\r\n    String out = null;\r\n\r\n    try {\r\n      Object object = mapper.readValue(json, Object.class);\r\n\r\n      out = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);\r\n    } catch (Exception ex) {\r\n      ex.printStackTrace();\r\n    }\r\n\r\n    return out;\r\n  }\r\n}\r\n<\/pre>\n<p>O restante do c\u00f3digo aqui s\u00e3o apenas bits auxiliares e uma parte para encerrar tudo antes de sair.<\/p>\n<h3>A classe Database Helper<\/h3>\n<p>Isso mostra o c\u00f3digo de uma classe auxiliar de banco de dados simples. Na maioria das vezes, considero essa classe um bom pacote das opera\u00e7\u00f5es t\u00edpicas necess\u00e1rias para gerenciar um banco de dados e iniciar um conjunto bidirecional padr\u00e3o de replica\u00e7\u00f5es. Estou incluindo-a aqui porque a considero \u00fatil e para maior clareza.<\/p>\n<p>Eu implemento o <code>Replica\u00e7\u00e3o.ChangeListener<\/code> interface. Isso talvez seja um pouco incomum. Mencionei o motivo anteriormente. Este link leva voc\u00ea \u00e0 p\u00e1gina <a href=\"https:\/\/www.couchbase.com\/blog\/pt\/determining-status-replication-couchbase-lite\/\">postagem no blog<\/a> sobre isso.<\/p>\n<pre class=\"lang:java decode:true\">pacote com.couchbase.mobile;\r\n\r\nimport com.couchbase.lite.Database;\r\nimport com.couchbase.lite.JavaContext;\r\nimport com.couchbase.lite.Manager;\r\nImportar com.couchbase.lite.auth.Authenticator;\r\nimport com.couchbase.lite.auth.AuthenticatorFactory;\r\nimport com.couchbase.lite.replicator.Replication;\r\nimport com.couchbase.lite.replicator.ReplicationState;\r\n\r\nimport java.net.URL;\r\nimportar java.util.ArrayList;\r\nImportar java.util.List;\r\n\r\npublic class DBService implements Replication.ChangeListener {\r\n  public static final String DATABASE = \"db\";\r\n\r\n  private static final String DB_DIRECTORY = \"data\";\r\n\r\n  private Manager manager;\r\n  private Database database;\r\n  private Replication pushReplication = null;\r\n  private Replication pullReplication = null;\r\n  private boolean replicationActive = false;\r\n  private List stateListeners = new ArrayList();\r\n  private String nome de usu\u00e1rio = null;\r\n  private String password = null;\r\n\r\n  private DBService() {\r\n    try {\r\n      manager = new Manager(new JavaContext(DB_DIRECTORY), Manager.DEFAULT_OPTIONS);\r\n      banco de dados = manager.getDatabase(DATABASE);\r\n    } catch (Exception ex) {\r\n      ex.printStackTrace();\r\n    }\r\n  }\r\n\r\n  classe est\u00e1tica privada Holder {\r\n    private static DBService INSTANCE = new DBService();\r\n  }\r\n\r\n\r\n  interface p\u00fablica ReplicationStateListener {\r\n    void onChange(boolean isActive);\r\n  }\r\n\r\n  public static DBService getInstance() {\r\n    return Holder.INSTANCE;\r\n  }\r\n\r\n  banco de dados p\u00fablico getDatabase() {\r\n    return database;\r\n  }\r\n\r\n  public void setCredentials(String username, String password) {\r\n    this.username = nome de usu\u00e1rio;\r\n    this.password = password;\r\n  }\r\n\r\n  public void toggleReplication(URL gateway, boolean continuous) {\r\n    se (replicationActive) {\r\n      stopReplication();\r\n    } else {\r\n      startReplication(gateway, continuous);\r\n    }\r\n  }\r\n\r\n  public void startReplication(URL gateway, boolean continuous) {\r\n    se (replicationActive) {\r\n      stopReplication();\r\n    }\r\n\r\n    pushReplication = database.createPushReplication(gateway);\r\n    pullReplication = banco de dados.createPullReplication(gateway);\r\n    pushReplication.setContinuous(continuous);\r\n    pullReplication.setContinuous(continuous);\r\n\r\n    Se (nome de usu\u00e1rio != null) {\r\n      Authenticator auth = AuthenticatorFactory.createBasicAuthenticator(nome de usu\u00e1rio, senha);\r\n      pushReplication.setAuthenticator(auth);\r\n      pullReplication.setAuthenticator(auth);\r\n    }\r\n\r\n    pushReplication.addChangeListener(this);\r\n    pullReplication.addChangeListener(this);\r\n\r\n    pushReplication.start();\r\n    pullReplication.start();\r\n  }\r\n\r\n  public void stopReplication() {\r\n    se (!replicationActive) return;\r\n\r\n    pushReplication.stop();\r\n    pullReplication.stop();\r\n\r\n    pushReplication = null;\r\n    pullReplication = null;\r\n  }\r\n\r\n  public void addReplicationStateListener(ReplicationStateListener listener) {\r\n    stateListeners.add(listener);\r\n  }\r\n\r\n  public void removeReplicationStateListener(ReplicationStateListener listener) {\r\n    stateListeners.remove(listener);\r\n  }\r\n\r\n  \/\/ Replication.ChangeListener\r\n  @Override\r\n  public void changed(Replication.ChangeEvent changeEvent) {\r\n    if (changeEvent.getError() != null) {\r\n      Throwable lastError = changeEvent.getError();\r\n\r\n      Dialog.display(lastError.getMessage());\r\n\r\n      return;\r\n    }\r\n\r\n    Se (changeEvent.getTransition() == null) return;\r\n\r\n    ReplicationState dest = changeEvent.getTransition().getDestination();\r\n\r\n    replicationActive = ((dest == ReplicationState.STOPPING || dest == ReplicationState.STOPPED) ? false : true);\r\n\r\n    stateListeners.forEach(listener -&gt; listener.onChange(replicationActive));\r\n  }\r\n}<\/pre>\n<h3>A classe Sync Gateway Monitor<\/h3>\n<p>Por fim, vamos dar uma olhada na classe auxiliar para monitorar o Sync Gateway. Tamb\u00e9m vou analisar isso em partes.<\/p>\n<pre class=\"lang:java decode:true\">pacote com.couchbase.mobile;\r\n\r\nimport com.fasterxml.jackson.databind.JsonNode;\r\nimport okhttp3.*;\r\n\r\nimport java.io.IOException;\r\nimport java.net.SocketException;\r\nimport java.util.concurrent.TimeUnit;\r\n\r\nimport static com.couchbase.mobile.Runtime.mapper;\r\n\r\nclasse p\u00fablica SGMonitor {\r\n  cliente OkHttpClient final est\u00e1tico privado = novo OkHttpClient.Builder()\r\n      .readTimeout(1, TimeUnit.DAYS)\r\n      .build();\r\n  listener ChangesFeedListener privado;\r\n  HttpUrl.Builder urlBuilder privado;\r\n  private Thread monitorThread;\r\n  private String since = \"0\";\r\n  private Call call;\r\n\r\n  SGMonitor(String url, String activeOnly, String includeDocs, String since, String style,\r\n            ChangesFeedListener listener) {\r\n    this.since = since;\r\n\r\n    urlBuilder = HttpUrl.parse(url).newBuilder()\r\n        .addPathSegment(\"_changes\")\r\n        .addQueryParameter(\"active_only\", activeOnly)\r\n        .addQueryParameter(\"include_docs\", includeDocs)\r\n        .addQueryParameter(\"style\", style)\r\n        .addQueryParameter(\"since\", since)\r\n        .addQueryParameter(\"feed\", \"longpoll\")\r\n        .addQueryParameter(\"timeout\", \"0\");\r\n\r\n    this.listener = listener;\r\n  }<\/pre>\n<p>Eu uso o <a href=\"https:\/\/square.github.io\/okhttp\/\">Biblioteca OkHttp da Square<\/a>. Atualmente, o Couchbase Lite tamb\u00e9m usa essa biblioteca internamente. O OkHttp usa um padr\u00e3o de construtor. Preparo uma inst\u00e2ncia do builder que usarei durante o restante do c\u00f3digo no construtor da classe. Voc\u00ea pode ler sobre o significado de todos os par\u00e2metros na se\u00e7\u00e3o <a href=\"https:\/\/developer.couchbase.com\/documentation\/mobile\/current\/references\/sync-gateway\/index.html\">Documenta\u00e7\u00e3o do Sync Gateway<\/a>.<\/p>\n<pre><code class=\"language-java\">  public interface ChangesFeedListener {\r\n    void onResponse(String body);\r\n  }\r\n\r\n  public void start() {\r\n    monitorThread = new Thread(() -&gt; {\r\n      while (!Thread.interrupted()) {\r\n        Request request = new Request.Builder()\r\n            .url(urlBuilder.build())\r\n            .build();\r\n\r\n        call = client.newCall(request);\r\n\r\n        try (Response response = call.execute()) {\r\n          if (!response.isSuccessful()) throw new IOException(\"Unexpected code \" + response);\r\n\r\n          String body = response.body().string();\r\n\r\n          JsonNode tree = mapper.readTree(body);\r\n\r\n          since = tree.get(\"last_seq\").asText();\r\n          urlBuilder.setQueryParameter(\"since\", since);\r\n\r\n          listener.onResponse(body);\r\n        } catch (SocketException ex) {\r\n          return;\r\n        } catch (IOException ex) {\r\n          ex.printStackTrace();\r\n          Dialog.display(ex);\r\n        }\r\n      }\r\n    });\r\n\r\n    monitorThread.setDaemon(true);\r\n    monitorThread.start();\r\n  }<\/code><\/pre>\n<p>O <code>iniciar<\/code> tem a parte mais interessante do c\u00f3digo. Ele ativa um thread em segundo plano. Sob a configura\u00e7\u00e3o do thread e o c\u00f3digo de controle, executo um loop cont\u00ednuo. O loop faz chamadas de rede s\u00edncronas. O tratamento de erros \u00e9 simples. Basta lan\u00e7ar uma exce\u00e7\u00e3o se algo der errado.<\/p>\n<p>O Sync Gateway responde com cadeias de caracteres JSON. Voc\u00ea pode ver que o c\u00f3digo separa a resposta e analisa o JSON em um arquivo <code>JsonNode<\/code> objeto. Isso tudo \u00e9 para chegar ao <code>\u00faltima_seq<\/code> na resposta.<\/p>\n<p>Para rastrear o que deve ser enviado em seguida, o feed de altera\u00e7\u00f5es depende de um mecanismo de sequ\u00eancia simples. Voc\u00ea deve tratar isso como um objeto opaco. Pegue o valor de <code>\u00faltima_seq<\/code> da resposta anterior e defina o <code>desde<\/code> para esse mesmo valor na pr\u00f3xima solicita\u00e7\u00e3o.<\/p>\n<p>N\u00e3o h\u00e1 nenhum dano real em n\u00e3o fornecer o <code>desde<\/code> par\u00e2metro. O Sync Gateway apenas reproduzir\u00e1 todas as altera\u00e7\u00f5es desde o in\u00edcio se ele estiver faltando. \u00c9 por isso que voc\u00ea ver\u00e1 que, neste exemplo, eu trapaceio um pouco e sempre crio a inst\u00e2ncia da classe com <code>desde<\/code> definido como a string \"0\".<\/p>\n<p>Em um aplicativo do mundo real, talvez voc\u00ea queira ter uma maneira de salvar a \u00faltima sequ\u00eancia de caracteres que seu aplicativo processou, em vez de ficar percorrendo o hist\u00f3rico de altera\u00e7\u00f5es todas as vezes.<\/p>\n<p>O restante do c\u00f3digo \u00e9 apenas um par de m\u00e9todos curtos.<\/p>\n<pre class=\"lang:java decode:true\">  public void stop() {\r\n    monitorThread.interrupt();\r\n    call.cancel();\r\n  }\r\n\r\n  public String getSince() {\r\n    return since;\r\n  }\r\n}<\/pre>\n<p>E isso \u00e9 tudo para as classes principais. H\u00e1 outras necess\u00e1rias para o aplicativo completo.<\/p>\n<p>Confira o <a href=\"https:\/\/github.com\/couchbaselabs\/CBM-Changes-Explorer\">Reposit\u00f3rio do GitHub<\/a> para ver todo o c\u00f3digo e as instru\u00e7\u00f5es para cri\u00e1-lo.<\/p>\n<p>Leia uma discuss\u00e3o sobre o aplicativo e como us\u00e1-lo em <a href=\"https:\/\/www.couchbase.com\/blog\/pt\/couchbase-mobile-changes-explorer-part-1\/\">primeira parte<\/a>.<\/p>\n<h2>P\u00f3s-escrito<\/h2>\n<p>Voc\u00ea pode encontrar mais recursos em nosso <a href=\"https:\/\/www.couchbase.com\/blog\/pt\/developers\/community\/?utm_source=blogs&amp;utm_medium=link&amp;utm_campaign=blogs\">portal do desenvolvedor<\/a> e nos siga no Twitter <a href=\"https:\/\/twitter.com\/CouchbaseDev\">@CouchbaseDev<\/a>.<\/p>\n<p>Voc\u00ea pode postar perguntas em nosso <a href=\"https:\/\/www.couchbase.com\/blog\/pt\/forums\/?utm_source=blogs&amp;utm_medium=link&amp;utm_campaign=blogs\">f\u00f3runs<\/a>. E participamos ativamente de <a href=\"https:\/\/stackoverflow.com\/questions\/tagged\/couchbase\">Estouro de pilha<\/a>.<\/p>\n<p>Entre em contato comigo pelo Twitter com perguntas, coment\u00e1rios, t\u00f3picos que voc\u00ea gostaria de ver etc. <a href=\"https:\/\/twitter.com\/HodGreeley\">@HodGreeley<\/a><\/p>","protected":false},"excerpt":{"rendered":"<p>Introduction The Couchbase Mobile Sync Gateway changes feed provides a way to monitor events in a mobile deployment. The feed makes it feasible to write sophisticated business logic. I wrote a tool to help examine and understand the feed. You [&hellip;]<\/p>","protected":false},"author":73,"featured_media":2888,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"inline_featured_image":false,"footnotes":""},"categories":[1810,1818,2366],"tags":[1867,1868,1809],"ppma_author":[9042],"class_list":["post-2929","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-couchbase-mobile","category-java","category-sync-gateway","tag-business-logic","tag-changes-feed","tag-document"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.5 (Yoast SEO v26.5) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Couchbase Mobile Changes Explorer \u2013 Part. 2 - The Couchbase Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.couchbase.com\/blog\/pt\/couchbase-mobile-changes-explorer-part-2\/\" \/>\n<meta property=\"og:locale\" content=\"pt_BR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Couchbase Mobile Changes Explorer \u2013 Part. 2\" \/>\n<meta property=\"og:description\" content=\"Introduction The Couchbase Mobile Sync Gateway changes feed provides a way to monitor events in a mobile deployment. The feed makes it feasible to write sophisticated business logic. I wrote a tool to help examine and understand the feed. You [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.couchbase.com\/blog\/pt\/couchbase-mobile-changes-explorer-part-2\/\" \/>\n<meta property=\"og:site_name\" content=\"The Couchbase Blog\" \/>\n<meta property=\"article:published_time\" content=\"2017-03-10T07:13:48+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-14T02:58:39+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/02\/ChangesExplorer.gif\" \/>\n\t<meta property=\"og:image:width\" content=\"480\" \/>\n\t<meta property=\"og:image:height\" content=\"275\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/gif\" \/>\n<meta name=\"author\" content=\"Hod Greeley, Developer Advocate, Couchbase\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@HodGreeley\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Hod Greeley, Developer Advocate, Couchbase\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/\"},\"author\":{\"name\":\"Hod Greeley, Developer Advocate, Couchbase\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/9b62593c8a13531e53d52fcd5aabbca4\"},\"headline\":\"Couchbase Mobile Changes Explorer \u2013 Part. 2\",\"datePublished\":\"2017-03-10T07:13:48+00:00\",\"dateModified\":\"2025-06-14T02:58:39+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/\"},\"wordCount\":1261,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/02\/ChangesExplorer.gif\",\"keywords\":[\"Business Logic\",\"Changes Feed\",\"document\"],\"articleSection\":[\"Couchbase Mobile\",\"Java\",\"Sync Gateway\"],\"inLanguage\":\"pt-BR\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/\",\"name\":\"Couchbase Mobile Changes Explorer \u2013 Part. 2 - The Couchbase Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/02\/ChangesExplorer.gif\",\"datePublished\":\"2017-03-10T07:13:48+00:00\",\"dateModified\":\"2025-06-14T02:58:39+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#breadcrumb\"},\"inLanguage\":\"pt-BR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"pt-BR\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#primaryimage\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/02\/ChangesExplorer.gif\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/02\/ChangesExplorer.gif\",\"width\":480,\"height\":275,\"caption\":\"Couchbase Mobile Changes Explorer Animated Gif\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.couchbase.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Couchbase Mobile Changes Explorer \u2013 Part. 2\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#website\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"name\":\"The Couchbase Blog\",\"description\":\"Couchbase, the NoSQL Database\",\"publisher\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"pt-BR\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#organization\",\"name\":\"The Couchbase Blog\",\"url\":\"https:\/\/www.couchbase.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"pt-BR\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"contentUrl\":\"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png\",\"width\":218,\"height\":34,\"caption\":\"The Couchbase Blog\"},\"image\":{\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/9b62593c8a13531e53d52fcd5aabbca4\",\"name\":\"Hod Greeley, Developer Advocate, Couchbase\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"pt-BR\",\"@id\":\"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/21eb69cb5d4a401fb23b149e4f4e9e87\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/52d0018695c0ced0d1c68cf64a6195c81dbac03dce5983f98eb209e7c84350df?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/52d0018695c0ced0d1c68cf64a6195c81dbac03dce5983f98eb209e7c84350df?s=96&d=mm&r=g\",\"caption\":\"Hod Greeley, Developer Advocate, Couchbase\"},\"description\":\"Hod Greeley is a Developer Advocate for Couchbase, living in Silicon Valley. He has over two decades of experience as a software engineer and engineering manager. He has worked in a variety of software fields, including computational physics and chemistry, computer and network security, finance, and mobile. Prior to joining Couchbase in 2016, Hod led developer relations for mobile at Samsung. Hod holds a Ph.D. in chemical physics from Columbia University.\",\"sameAs\":[\"https:\/\/hod.greeley.org\/blog\",\"https:\/\/x.com\/HodGreeley\"],\"url\":\"https:\/\/www.couchbase.com\/blog\/pt\/author\/hod-greeley\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"O Couchbase Mobile muda o Explorer - Part. 2 - O Blog do Couchbase","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.couchbase.com\/blog\/pt\/couchbase-mobile-changes-explorer-part-2\/","og_locale":"pt_BR","og_type":"article","og_title":"Couchbase Mobile Changes Explorer \u2013 Part. 2","og_description":"Introduction The Couchbase Mobile Sync Gateway changes feed provides a way to monitor events in a mobile deployment. The feed makes it feasible to write sophisticated business logic. I wrote a tool to help examine and understand the feed. You [&hellip;]","og_url":"https:\/\/www.couchbase.com\/blog\/pt\/couchbase-mobile-changes-explorer-part-2\/","og_site_name":"The Couchbase Blog","article_published_time":"2017-03-10T07:13:48+00:00","article_modified_time":"2025-06-14T02:58:39+00:00","og_image":[{"width":480,"height":275,"url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/02\/ChangesExplorer.gif","type":"image\/gif"}],"author":"Hod Greeley, Developer Advocate, Couchbase","twitter_card":"summary_large_image","twitter_creator":"@HodGreeley","twitter_misc":{"Written by":"Hod Greeley, Developer Advocate, Couchbase","Est. reading time":"6 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#article","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/"},"author":{"name":"Hod Greeley, Developer Advocate, Couchbase","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/9b62593c8a13531e53d52fcd5aabbca4"},"headline":"Couchbase Mobile Changes Explorer \u2013 Part. 2","datePublished":"2017-03-10T07:13:48+00:00","dateModified":"2025-06-14T02:58:39+00:00","mainEntityOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/"},"wordCount":1261,"commentCount":0,"publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/02\/ChangesExplorer.gif","keywords":["Business Logic","Changes Feed","document"],"articleSection":["Couchbase Mobile","Java","Sync Gateway"],"inLanguage":"pt-BR","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/","url":"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/","name":"O Couchbase Mobile muda o Explorer - Part. 2 - O Blog do Couchbase","isPartOf":{"@id":"https:\/\/www.couchbase.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#primaryimage"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#primaryimage"},"thumbnailUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/02\/ChangesExplorer.gif","datePublished":"2017-03-10T07:13:48+00:00","dateModified":"2025-06-14T02:58:39+00:00","breadcrumb":{"@id":"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#breadcrumb"},"inLanguage":"pt-BR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/"]}]},{"@type":"ImageObject","inLanguage":"pt-BR","@id":"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#primaryimage","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/02\/ChangesExplorer.gif","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/sites\/1\/2017\/02\/ChangesExplorer.gif","width":480,"height":275,"caption":"Couchbase Mobile Changes Explorer Animated Gif"},{"@type":"BreadcrumbList","@id":"https:\/\/www.couchbase.com\/blog\/couchbase-mobile-changes-explorer-part-2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.couchbase.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Couchbase Mobile Changes Explorer \u2013 Part. 2"}]},{"@type":"WebSite","@id":"https:\/\/www.couchbase.com\/blog\/#website","url":"https:\/\/www.couchbase.com\/blog\/","name":"Blog do Couchbase","description":"Couchbase, o banco de dados NoSQL","publisher":{"@id":"https:\/\/www.couchbase.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.couchbase.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"pt-BR"},{"@type":"Organization","@id":"https:\/\/www.couchbase.com\/blog\/#organization","name":"Blog do Couchbase","url":"https:\/\/www.couchbase.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"pt-BR","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","contentUrl":"https:\/\/www.couchbase.com\/blog\/wp-content\/uploads\/2023\/04\/admin-logo.png","width":218,"height":34,"caption":"The Couchbase Blog"},"image":{"@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/9b62593c8a13531e53d52fcd5aabbca4","name":"Hod Greeley, Advogado do desenvolvedor, Couchbase","image":{"@type":"ImageObject","inLanguage":"pt-BR","@id":"https:\/\/www.couchbase.com\/blog\/#\/schema\/person\/image\/21eb69cb5d4a401fb23b149e4f4e9e87","url":"https:\/\/secure.gravatar.com\/avatar\/52d0018695c0ced0d1c68cf64a6195c81dbac03dce5983f98eb209e7c84350df?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/52d0018695c0ced0d1c68cf64a6195c81dbac03dce5983f98eb209e7c84350df?s=96&d=mm&r=g","caption":"Hod Greeley, Developer Advocate, Couchbase"},"description":"Hod Greeley \u00e9 um defensor dos desenvolvedores da Couchbase e mora no Vale do Sil\u00edcio. Ele tem mais de duas d\u00e9cadas de experi\u00eancia como engenheiro de software e gerente de engenharia. Trabalhou em diversas \u00e1reas de software, incluindo f\u00edsica e qu\u00edmica computacional, seguran\u00e7a de computadores e redes, finan\u00e7as e dispositivos m\u00f3veis. Antes de ingressar na Couchbase em 2016, Hod liderou as rela\u00e7\u00f5es com desenvolvedores para dispositivos m\u00f3veis na Samsung. Hod \u00e9 Ph.D. em f\u00edsica qu\u00edmica pela Universidade de Columbia.","sameAs":["https:\/\/hod.greeley.org\/blog","https:\/\/x.com\/HodGreeley"],"url":"https:\/\/www.couchbase.com\/blog\/pt\/author\/hod-greeley\/"}]}},"authors":[{"term_id":9042,"user_id":73,"is_guest":0,"slug":"hod-greeley","display_name":"Hod Greeley, Developer Advocate, Couchbase","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/52d0018695c0ced0d1c68cf64a6195c81dbac03dce5983f98eb209e7c84350df?s=96&d=mm&r=g","author_category":"","last_name":"Greeley","first_name":"Hod","job_title":"","user_url":"https:\/\/hod.greeley.org\/blog","description":"Hod Greeley \u00e9 um defensor dos desenvolvedores da Couchbase e mora no Vale do Sil\u00edcio. Ele tem mais de duas d\u00e9cadas de experi\u00eancia como engenheiro de software e gerente de engenharia. Trabalhou em diversas \u00e1reas de software, incluindo f\u00edsica e qu\u00edmica computacional, seguran\u00e7a de computadores e redes, finan\u00e7as e dispositivos m\u00f3veis. Antes de ingressar na Couchbase em 2016, Hod liderou as rela\u00e7\u00f5es com desenvolvedores para dispositivos m\u00f3veis na Samsung. Hod \u00e9 Ph.D. em f\u00edsica qu\u00edmica pela Universidade de Columbia."}],"_links":{"self":[{"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/2929","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/users\/73"}],"replies":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/comments?post=2929"}],"version-history":[{"count":0,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/posts\/2929\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/media\/2888"}],"wp:attachment":[{"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/media?parent=2929"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/categories?post=2929"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/tags?post=2929"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.couchbase.com\/blog\/pt\/wp-json\/wp\/v2\/ppma_author?post=2929"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}