Como implementar a multilocação usando namespaces

Com a API Namespaces, é possível ativar facilmente a multilocação no aplicativo. Basta usar o pacote web.xml para selecionar uma string de namespace para cada locatário de NamespaceManager.

Como configurar o namespace atual

Você pode receber, configurar e validar os namespaces usando NamespaceManager. Com o gerenciador de namespaces, é possível configurar um namespace atual inicial nas APIs ativadas para namespace. Para isso, use web.xml e esse namespace será utilizado automaticamente pelo armazenamento de dados e pelo Memcache.

A maioria dos desenvolvedores do App Engine usa o próprio domínio do G Suite como o namespace atual. Com o G Suite, é possível implantar o aplicativo em qualquer domínio próprio. Portanto, esse mecanismo pode ser usado para configurar facilmente namespaces diferentes para domínios distintos. Em seguida, use esses namespaces separados para segregar dados entre os domínios. Para mais informações sobre como configurar vários domínios no painel do G Suite, acesse Como implantar seu aplicativo no URL do G Suite.

A amostra de código a seguir mostra como configurar o namespace atual no domínio do G Suite que foi usado para mapear o URL. Essa string será a mesma para todos os URLs mapeados por meio do mesmo domínio do G Suite.

É possível definir namespaces em Java usando a interface de Filtro do servlet antes de chamar os métodos do servlet. No exemplo a seguir, demonstramos como usar o domínio do G Suite como o namespace atual:

Java 8

// Filter to set the Google Apps domain as the namespace.
public class NamespaceFilter implements Filter {

  @Override
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
      throws IOException, ServletException {
    // Make sure set() is only called if the current namespace is not already set.
    if (NamespaceManager.get() == null) {
      // If your app is hosted on appspot, this will be empty. Otherwise it will be the domain
      // the app is hosted on.
      NamespaceManager.set(NamespaceManager.getGoogleAppsNamespace());
    }
    chain.doFilter(req, res); // Pass request back down the filter chain
  }

Java 7

// Filter to set the Google Apps domain as the namespace.
public class NamespaceFilter implements Filter {
  @Override
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
      throws IOException, ServletException {
    // Make sure set() is only called if the current namespace is not already set.
    if (NamespaceManager.get() == null) {
      // If your app is hosted on appspot, this will be empty. Otherwise it will be the domain
      // the app is hosted on.
      NamespaceManager.set(NamespaceManager.getGoogleAppsNamespace());
    }
    chain.doFilter(req, res); // Pass request back down the filter chain
  }

Configure o filtro do namespace no arquivo web.xml. Observe que, se houver várias entradas de filtro, o primeiro namespace a ser definido será o usado.

Na amostra de código a seguir, demonstramos como configurar o filtro de namespace em web.xml:

Java 8

<!-- Configure the namespace filter. -->
<filter>
    <filter-name>NamespaceFilter</filter-name>
    <filter-class>com.example.appengine.NamespaceFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>NamespaceFilter</filter-name>
    <url-pattern>/sign</url-pattern>
</filter-mapping>

Java 7

<!-- Configure the namespace filter. -->
<filter>
    <filter-name>NamespaceFilter</filter-name>
    <filter-class>com.example.appengine.NamespaceFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>NamespaceFilter</filter-name>
    <url-pattern>/sign</url-pattern>
</filter-mapping>

Para informações mais gerais sobre o arquivo web.xml e caminhos de URL de mapeamento para servlets, consulte Descritor de implantação: web.xml.

Também é possível definir um namespace novo para uma operação temporária. Basta usar o padrão try/finally abaixo ao redefinir o namespace original quando a operação for concluída.

Java 8

// Set the namepace temporarily to "abc"
String oldNamespace = NamespaceManager.get();
NamespaceManager.set("abc");
try {
  //      ... perform operation using current namespace ...
} finally {
  NamespaceManager.set(oldNamespace);
}

Java 7

// Set the namepace temporarily to "abc"
String oldNamespace = NamespaceManager.get();
NamespaceManager.set("abc");
try {
//    ... perform operation using current namespace ...
} finally {
  NamespaceManager.set(oldNamespace);
}

Se você não especificar um valor para o namespace, o namespace será configurado como uma string vazia. A string namespace é arbitrária, mas também limitada a um máximo de 100 caracteres alfanuméricos, pontos, sublinhados e hifens. Mais explicitamente, as strings de namespace precisam corresponder à expressão regular [0-9A-Za-z._-]{0,100}.

Por convenção, todos os namespaces que começam com "_" (sublinhado) são reservados para uso do sistema. Essa regra de namespaces do sistema não é obrigatória, mas é fácil encontrar consequências negativas indefinidas se não for seguida.

Como evitar vazamentos de dados

Um dos riscos comumente associados a aplicativos multilocatários é o perigo de que dados vazem entre namespaces. Os vazamentos de dados inesperados podem surgir de muitas fontes, como estas:

  • Usar namespaces com APIs do App Engine que ainda não são compatíveis com namespaces. Por exemplo, o Blobstore não é compatível com namespaces. Se você usar Namespaces com o Blobstore, precisará evitar o uso de consultas Blobstore para solicitações de usuários finais ou chaves Blobstore de fontes não confiáveis.
  • Usar um meio de armazenamento externo (em vez de Memcache e armazenamento de dados), via URL Fetch ou algum outro mecanismo, sem fornecer um esquema de compartimentalização para namespaces.
  • Definir um namespace com base no domínio do e-mail de um usuário. Na maioria dos casos, não é desejável que todos os endereços de e-mail de um domínio tenham acesso a um namespace. Usar o domínio do e-mail também impede que o aplicativo use um namespace antes que o usuário faça login.

Como implantar namespaces

Veja nas seções a seguir como implantar namespaces com outras APIs e ferramentas do App Engine.

Como criar namespaces por usuário

Alguns aplicativos precisam criar namespaces por usuário. Se você quiser distribuir dados no nível do usuário para usuários conectados, use User.getUserId(), que retorna um código permanente e exclusivo para o usuário. A amostra de código a seguir demonstra como usar a API de usuários para esse fim:

Java 8

if (com.google.appengine.api.NamespaceManager.get() == null) {
  // Assuming there is a logged in user.
  namespace = UserServiceFactory.getUserService().getCurrentUser().getUserId();
  NamespaceManager.set(namespace);
}

Java 7

if (com.google.appengine.api.NamespaceManager.get() == null) {
  // Assuming there is a logged in user.
  namespace = UserServiceFactory.getUserService().getCurrentUser().getUserId();
  NamespaceManager.set(namespace);
}

Geralmente, nos aplicativos em que os namespaces são criados por usuário, páginas de destino específicas também são fornecidas para diferentes usuários. Nesses casos, o aplicativo deve fornecer um esquema de URL que dite qual página de destino exibir para um usuário.

Como usar namespaces com o Datastore

Por padrão, solicitações de armazenamento de dados no Datastore são feitas usando a configuração do namespace atual no gerenciador de namespaces. A API aplica esse namespace atual aos objetos Key ou Query quando eles são criados. Portanto, tome cuidado se um aplicativo armazenar objetos Key ou Query em formulários serializados, porque o namespace é preservado nessas serializações.

Se você estiver usando objetos Key e Query sem serialização, verifique se eles se comportam conforme o esperado. A maioria dos aplicativos simples que usa o armazenamento de dados (put/query/get) sem outros mecanismos de armazenamento funcionará conforme o esperado, configurando o namespace atual antes de chamar qualquer API de armazenamento de dados.

Nos objetos Query e Key, é possível ver os comportamentos únicos a seguir, em relação aos namespaces:

  • Objetos Query e Key herdam o namespace atual quando construídos, a menos que você defina um namespace explícito.
  • Quando um aplicativo cria uma nova Key de um ancestral, a nova Key herda o namespace dele.
  • Não há API do Java para definir explicitamente o namespace de uma Key ou Query.
No exemplo de código a seguir, mostramos o gerenciador de solicitação do SomeRequest para incrementar a contagem para o namespace atual e o namespace -global-, nomeado arbitrariamente em uma entidade de armazenamento de dados Counter.

Java 8

public class UpdateCountsServlet extends HttpServlet {

  private static final int NUM_RETRIES = 10;

  @Entity
  public class CounterPojo {

    @Id
    public Long id;
    @Index
    public String name;
    public Long count;

    public CounterPojo() {
      this.count = 0L;
    }

    public CounterPojo(String name) {
      this.name = name;
      this.count = 0L;
    }

    public void increment() {
      count++;
    }
  }

  /**
   * Increment the count in a Counter datastore entity.
   **/
  public long updateCount(String countName) {

    CounterPojo cp = ofy().load().type(CounterPojo.class).filter("name", countName).first().now();
    if (cp == null) {
      cp = new CounterPojo(countName);
    }
    cp.increment();
    ofy().save().entity(cp).now();

    return cp.count;
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws java.io.IOException {

    // Update the count for the current namespace.
    updateCount("request");

    // Update the count for the "-global-" namespace.
    String namespace = NamespaceManager.get();
    try {
      // "-global-" is namespace reserved by the application.
      NamespaceManager.set("-global-");
      updateCount("request");
    } finally {
      NamespaceManager.set(namespace);
    }
    resp.setContentType("text/plain");
    resp.getWriter().println("Counts are now updated.");
  }

Java 7

public class UpdateCountsServlet extends HttpServlet {
  private static final int NUM_RETRIES = 10;

  @Entity public class CounterPojo {
    @Id public Long id;
    @Index public String name;
    public Long count;

    public CounterPojo() {
      this.count = 0L;
    }

    public CounterPojo(String name) {
      this.name = name;
      this.count = 0L;
    }

    public void increment() {
      count++;
    }
  }

  // Increment the count in a Counter datastore entity.
  public long updateCount(String countName) {

    CounterPojo cp = ofy().load().type(CounterPojo.class).filter("name", countName).first().now();
    if (cp == null) {
      cp = new CounterPojo(countName);
    }
    cp.increment();
    ofy().save().entity(cp).now();

    return cp.count;
  }

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws java.io.IOException {

    // Update the count for the current namespace.
    updateCount("request");

    // Update the count for the "-global-" namespace.
    String namespace = NamespaceManager.get();
    try {
      // "-global-" is namespace reserved by the application.
      NamespaceManager.set("-global-");
      updateCount("request");
    } finally {
      NamespaceManager.set(namespace);
    }
    resp.setContentType("text/plain");
    resp.getWriter().println("Counts are now updated.");
  }

Como usar namespaces com o Memcache

Por padrão, o memcache usa o namespace atual do gerenciador de namespaces para solicitações do memcache. Na maioria dos casos, você não precisa definir um namespace explicitamente no memcache. Fazer isso pode causar bugs inesperados.

Entretanto, há algumas ocasiões específicas em que é adequado definir um namespace explicitamente no memcache. Por exemplo, o aplicativo pode ter dados em comum compartilhados entre todos os namespaces (como uma tabela contendo códigos de países).

Veja no snippet de código abaixo como definir o namespace explicitamente no memcache:

Por padrão, a API do memcache para Java consulta o administrador de namespace em busca do namespace atual do MemcacheService. Você também pode indicar explicitamente um namespace quando constrói o memcache usando getMemcacheService(Namespace). Para a maioria dos aplicativos, não é necessário especificar explicitamente um namespace.

Na amostra de código a seguir, demonstramos como criar um memcache que usa o namespace atual no administrador do namespace.

Java 8

// Create a MemcacheService that uses the current namespace by
// calling NamespaceManager.get() for every access.
MemcacheService current = MemcacheServiceFactory.getMemcacheService();

// stores value in namespace "abc"
oldNamespace = NamespaceManager.get();
NamespaceManager.set("abc");
try {
  current.put("key", value); // stores value in namespace “abc”
} finally {
  NamespaceManager.set(oldNamespace);
}

Java 7

// Create a MemcacheService that uses the current namespace by
// calling NamespaceManager.get() for every access.
MemcacheService current = MemcacheServiceFactory.getMemcacheService();

// stores value in namespace "abc"
oldNamespace = NamespaceManager.get();
NamespaceManager.set("abc");
try {
  current.put("key", value);  // stores value in namespace “abc”
} finally {
  NamespaceManager.set(oldNamespace);
}

Esta amostra de código especifica explicitamente um namespace ao criar um serviço de memcache:

Java 8

// Create a MemcacheService that uses the namespace "abc".
MemcacheService explicit = MemcacheServiceFactory.getMemcacheService("abc");
explicit.put("key", value); // stores value in namespace "abc"

Java 7

// Create a MemcacheService that uses the namespace "abc".
MemcacheService explicit = MemcacheServiceFactory.getMemcacheService("abc");
explicit.put("key", value);  // stores value in namespace "abc"

Como usar namespaces com a fila de tarefas

Por padrão, as filas push usam o namespace atual configurado no administrador de namespaces no momento em que a tarefa foi criada. Na maioria dos casos, você não precisa definir um namespace explicitamente na fila de tarefas, e fazer isso pode gerar bugs inesperados.

Os nomes de tarefas são compartilhados com todos os namespaces. Não é possível criar duas tarefas com o mesmo nome, mesmo se usarem namespaces diferentes. Se quiser usar o mesmo nome de tarefa para vários namespaces, é possível simplesmente anexar cada namespace ao nome da tarefa.

Quando o método add() da fila de tarefas é chamado por uma nova tarefa, são copiados o namespace atual e, se aplicável, o domínio do G Suite do gerenciador de namespaces. Quando a tarefa é executada, o namespace atual e o namespace do G Suite são restaurados.

Se o namespace atual não estiver configurado na solicitação de origem (em outras palavras, se get() retornar null), a fila da tarefa definirá o namespace como "" nas tarefas executadas.

Há algumas ocasiões específicas em que é adequado definir um namespace explicitamente para uma tarefa que funciona em vários namespaces. Por exemplo, é possível criar uma tarefa que agrega estatísticas de uso para todos os namespaces. Depois, você define o namespace da tarefa explicitamente. Veja na amostra de código a seguir como definir namespaces explicitamente com a fila de tarefas.

Primeiro, crie um gerenciador de fila de tarefas que incremente a contagem em uma entidade de armazenamento de Counter:

Java 8

public class UpdateCountsServlet extends HttpServlet {

  private static final int NUM_RETRIES = 10;

  @Entity
  public class CounterPojo {

    @Id
    public Long id;
    @Index
    public String name;
    public Long count;

    public CounterPojo() {
      this.count = 0L;
    }

    public CounterPojo(String name) {
      this.name = name;
      this.count = 0L;
    }

    public void increment() {
      count++;
    }
  }

  /**
   * Increment the count in a Counter datastore entity.
   **/
  public long updateCount(String countName) {

    CounterPojo cp = ofy().load().type(CounterPojo.class).filter("name", countName).first().now();
    if (cp == null) {
      cp = new CounterPojo(countName);
    }
    cp.increment();
    ofy().save().entity(cp).now();

    return cp.count;
  }

Java 7

public class UpdateCountsServlet extends HttpServlet {
  private static final int NUM_RETRIES = 10;

  @Entity public class CounterPojo {
    @Id public Long id;
    @Index public String name;
    public Long count;

    public CounterPojo() {
      this.count = 0L;
    }

    public CounterPojo(String name) {
      this.name = name;
      this.count = 0L;
    }

    public void increment() {
      count++;
    }
  }

  // Increment the count in a Counter datastore entity.
  public long updateCount(String countName) {

    CounterPojo cp = ofy().load().type(CounterPojo.class).filter("name", countName).first().now();
    if (cp == null) {
      cp = new CounterPojo(countName);
    }
    cp.increment();
    ofy().save().entity(cp).now();

    return cp.count;
  }

e

Java 8

// called from Task Queue
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
  String[] countName = req.getParameterValues("countName");
  if (countName.length != 1) {
    resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
    return;
  }
  updateCount(countName[0]);
}

Java 7

// called from Task Queue
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
  String[] countName = req.getParameterValues("countName");
  if (countName.length != 1) {
    resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
    return;
  }
  updateCount(countName[0]);
}

Em seguida, crie tarefas com um servlet:

Java 8

public class SomeRequestServlet extends HttpServlet {

  // Handler for URL get requests.
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {

    // Increment the count for the current namespace asynchronously.
    QueueFactory.getDefaultQueue()
        .add(TaskOptions.Builder.withUrl("/_ah/update_count").param("countName", "SomeRequest"));
    // Increment the global count and set the
    // namespace locally.  The namespace is
    // transferred to the invoked request and
    // executed asynchronously.
    String namespace = NamespaceManager.get();
    try {
      NamespaceManager.set("-global-");
      QueueFactory.getDefaultQueue()
          .add(TaskOptions.Builder.withUrl("/_ah/update_count").param("countName", "SomeRequest"));
    } finally {
      NamespaceManager.set(namespace);
    }
    resp.setContentType("text/plain");
    resp.getWriter().println("Counts are being updated.");
  }
}

Java 7

public class SomeRequestServlet extends HttpServlet {
  // Handler for URL get requests.
  @Override
  protected void doGet(HttpServletRequest req,
                       HttpServletResponse resp)
      throws IOException {

    // Increment the count for the current namespace asynchronously.
    QueueFactory.getDefaultQueue().add(
        TaskOptions.Builder.withUrl("/_ah/update_count")
            .param("countName", "SomeRequest"));
    // Increment the global count and set the
    // namespace locally.  The namespace is
    // transferred to the invoked request and
    // executed asynchronously.
    String namespace = NamespaceManager.get();
    try {
      NamespaceManager.set("-global-");
      QueueFactory.getDefaultQueue().add(
          TaskOptions.Builder.withUrl("/_ah/update_count")
              .param("countName", "SomeRequest"));
    } finally {
      NamespaceManager.set(namespace);
    }
    resp.setContentType("text/plain");
    resp.getWriter().println("Counts are being updated.");
  }
}

Como usar namespaces com o Blobstore

O Blobstore não é segmentado por namespace. Para preservar um namespace no Blobstore, é necessário acessar o Blobstore por um meio de armazenamento que considere namespaces (atualmente, apenas o memcache, o armazenamento de dados e a fila de tarefas). Por exemplo, se a Key de um blob for armazenada em uma entidade do armazenamento de dados, é possível acessá-la com uma Key ou Query do armazenamento de dados que considere o namespace.

Se o aplicativo está acessando o Blobstore por meio de chaves armazenadas em armazenamento que considera namespaces, o próprio Blobstore não precisa ser segmentado por namespace. Os aplicativos precisam evitar vazamentos de blobs entre namespace com as seguintes ações:

  • Não usar com.google.appengine.api.blobstore.BlobInfoFactory para solicitações de usuários finais. Você pode usar consultas BlobInfo para solicitações administrativas (como gerar relatórios sobre todos os blobs de aplicativos), mas usá-las para solicitações de usuários finais pode resultar em vazamentos de dados, porque os registros de BlobInfo não são compartimentalizados por namespace.
  • Não usar chaves do Blobstore de fontes não confiáveis.

Como definir namespaces para consultas ao Datastore

No Console do Google Cloud Platform, é possível definir o namespace para consultas ao Datastore.

Se você não quiser usar o padrão, selecione o namespace desejado na lista suspensa.

Como usar namespaces com o carregador em massa

O carregador em massa é compatível com uma sinalização --namespace=NAMESPACE que permite especificar o namespace a ser usado. Cada namespace é manipulado separadamente. Se quiser acessar todos os namespaces, itere-os.

Uma nova instância do Index herda o namespace do SearchService usado para criá-lo. Depois de criar uma referência a um índice, o namespace não pode ser alterado. Há duas maneiras de configurar o namespace para um SearchService antes de usá-lo para criar um índice:

  • Por padrão, um novo SearchService leva o namespace atual. Você pode definir o namespace atual antes de criar o serviço:

Java 8

// Set the current namespace to "aSpace"
NamespaceManager.set("aSpace");
// Create a SearchService with the namespace "aSpace"
SearchService searchService = SearchServiceFactory.getSearchService();
// Create an IndexSpec
IndexSpec indexSpec = IndexSpec.newBuilder().setName("myIndex").build();
// Create an Index with the namespace "aSpace"
Index index = searchService.getIndex(indexSpec);

Java 7

// Set the current namespace to "aSpace"
NamespaceManager.set("aSpace");
// Create a SearchService with the namespace "aSpace"
SearchService searchService = SearchServiceFactory.getSearchService();
// Create an IndexSpec
IndexSpec indexSpec = IndexSpec.newBuilder().setName("myIndex").build();
// Create an Index with the namespace "aSpace"
Index index = searchService.getIndex(indexSpec);

Você pode especificar um namespace no SearchServiceConfig ao criar um serviço:

Java 8

// Create a SearchServiceConfig, specifying the namespace "anotherSpace"
SearchServiceConfig config =
    SearchServiceConfig.newBuilder().setNamespace("anotherSpace").build();
// Create a SearchService with the namespace "anotherSpace"
searchService = SearchServiceFactory.getSearchService(config);
// Create an IndexSpec
indexSpec = IndexSpec.newBuilder().setName("myindex").build();
// Create an Index with the namespace "anotherSpace"
index = searchService.getIndex(indexSpec);

Java 7

// Create a SearchServiceConfig, specifying the namespace "anotherSpace"
SearchServiceConfig config =  SearchServiceConfig.newBuilder()
    .setNamespace("anotherSpace").build();
// Create a SearchService with the namespace "anotherSpace"
searchService = SearchServiceFactory.getSearchService(config);
// Create an IndexSpec
indexSpec = IndexSpec.newBuilder().setName("myindex").build();
// Create an Index with the namespace "anotherSpace"
index = searchService.getIndex(indexSpec);
Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Ambiente padrão do App Engine para Java 8