Excluir dados com uma função chamável do Cloud Functions

Confira nesta página como usar uma função chamável do Cloud para excluir dados. Depois de implantar essa função, é possível chamá-la diretamente do seu app para dispositivos móveis ou site caso você queira excluir os documentos e coleções de maneira recorrente. Por exemplo, você pode usar essa solução para permitir que usuários selecionados excluam coleções inteiras.

Para outras formas de excluir coleções, consulte Excluir dados.

Solução: excluir dados com uma função chamável do Cloud

Pode ser difícil implementar a exclusão de coleções inteiras de um app para dispositivos móveis com limitação de recursos devido aos seguintes motivos:

  • Nenhuma operação exclui atomicamente uma coleção.
  • A exclusão de um documento não remove os que estão nas subcoleções.
  • Se os seus documentos têm subcoleções dinâmicas, pode ser difícil saber quais dados devem ser excluídos de um determinado caminho.
  • A exclusão de uma coleção com mais de 500 documentos exige várias operações de gravação em lote ou centenas de exclusões únicas.
  • Em muitos apps, não é apropriado conceder permissão para que os usuários finais excluam coleções inteiras.

Você pode escrever uma função chamável do Cloud Functions para executar exclusões seguras e eficientes de coleções inteiras ou de árvores de coleção. A função do Cloud abaixo implementa uma função chamável, o que significa que ela pode ser chamada diretamente do seu app para dispositivos móveis ou site, como você faria para uma função local.

Para implantar a função e testar uma demonstração, consulte o exemplo de código.

Função do Cloud

A função do Cloud Functions abaixo exclui uma coleção e todos os descendentes dela.

Em vez de implementar sua própria lógica de exclusão recursiva para a função do Cloud, você pode usar o comando firestore:delete na interface de linha de comando (CLI) do Firebase. Você pode importar qualquer função da CLI do Firebase para seu aplicativo em Node.js usando o pacote firebase-tools.

A CLI do Firebase usa a API REST do Cloud Firestore para localizar todos os documentos no caminho especificado e excluí-los individualmente. Essa implementação não exige conhecimento da hierarquia de dados específica do seu app. Ela consegue até mesmo encontrar e excluir documentos "órfãos" que não têm mais um pai.

Node.js

/**
 * Initiate a recursive delete of documents at a given path.
 * 
 * The calling user must be authenticated and have the custom "admin" attribute
 * set to true on the auth token.
 * 
 * This delete is NOT an atomic operation and it's possible
 * that it may fail after only deleting some documents.
 * 
 * @param {string} data.path the document or collection path to delete.
 */
exports.recursiveDelete = functions
  .runWith({
    timeoutSeconds: 540,
    memory: '2GB'
  })
  .https.onCall(async (data, context) => {
    // Only allow admin users to execute this function.
    if (!(context.auth && context.auth.token && context.auth.token.admin)) {
      throw new functions.https.HttpsError(
        'permission-denied',
        'Must be an administrative user to initiate delete.'
      );
    }

    const path = data.path;
    console.log(
      `User ${context.auth.uid} has requested to delete path ${path}`
    );

    // Run a recursive delete on the given document or collection path.
    // The 'token' must be set in the functions config, and can be generated
    // at the command line by running 'firebase login:ci'.
    await firebase_tools.firestore
      .delete(path, {
        project: process.env.GCLOUD_PROJECT,
        recursive: true,
        force: true,
        token: functions.config().fb.token
      });

    return {
      path: path 
    };
  });

Invocação do cliente

Para chamar a função, consiga uma referência à função do SDK do Firebase e transmita os parâmetros necessários:

Web
/**
 * Call the 'recursiveDelete' callable function with a path to initiate
 * a server-side delete.
 */
function deleteAtPath(path) {
    var deleteFn = firebase.functions().httpsCallable('recursiveDelete');
    deleteFn({ path: path })
        .then(function(result) {
            logMessage('Delete success: ' + JSON.stringify(result));
        })
        .catch(function(err) {
            logMessage('Delete failed, see console,');
            console.warn(err);
        });
}
Swift
Observação: esse produto não está disponível para destinos watchOS e de clipes de apps.
    // Snippet not yet written
    
Objective-C
Observação: este produto não está disponível para destinos watchOS e de clipes de apps.
    // Snippet not yet written
    

Kotlin+KTX

/**
 * Call the 'recursiveDelete' callable function with a path to initiate
 * a server-side delete.
 */
fun deleteAtPath(path: String) {
    val deleteFn = Firebase.functions.getHttpsCallable("recursiveDelete")
    deleteFn.call(hashMapOf("path" to path))
        .addOnSuccessListener {
            // Delete Success
            // ...
        }
        .addOnFailureListener {
            // Delete Failed
            // ...
        }
}

Java

/**
 * Call the 'recursiveDelete' callable function with a path to initiate
 * a server-side delete.
 */
public void deleteAtPath(String path) {
    Map<String, Object> data = new HashMap<>();
    data.put("path", path);

    HttpsCallableReference deleteFn =
            FirebaseFunctions.getInstance().getHttpsCallable("recursiveDelete");
    deleteFn.call(data)
            .addOnSuccessListener(new OnSuccessListener<HttpsCallableResult>() {
                @Override
                public void onSuccess(HttpsCallableResult httpsCallableResult) {
                    // Delete Success
                    // ...
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    // Delete failed
                    // ...
                }
            });
}

Ao usar o SDK do cliente para funções chamáveis do Cloud Functions, o estado de autenticação dos usuários e o parâmetro path são transmitidos sem interrupções para a função remota. Quando a função for concluída, o cliente receberá um retorno de chamada com o resultado ou uma exceção. Para saber mais sobre como chamar uma função do Cloud no Android, Apple ou em outra plataforma, leia a documentação.

Limitações

A solução mostrada acima demonstra a exclusão de coleções de uma função chamável, mas fique atento às seguintes limitações:

  • Consistência: o código acima exclui um documento de cada vez. Se você fizer a consulta enquanto houver uma operação de exclusão em andamento, seus resultados poderão refletir um estado parcialmente completo, em que apenas alguns documentos de destino serão excluídos. Também não há garantia de que as operações de exclusão serão bem-sucedidas ou falharão de maneira uniforme. Portanto, prepare-se para lidar com casos de exclusão parcial.
  • Tempos limite: a função acima está configurada para ser executada por um máximo de 540 segundos antes do tempo limite. O código de exclusão pode remover 4.000 documentos por segundo no melhor dos casos. Se você precisar excluir mais de 2 milhões de documentos, execute a operação no seu próprio servidor para que ela não expire. Para ver um exemplo sobre como excluir uma coleção do seu próprio servidor, consulte Excluir coleções.
  • Excluir um grande número de documentos pode fazer o visualizador de dados no console do Google Cloud carregar lentamente ou retornar um erro de tempo limite.