Partilhar via


Armazenamento de dados

Serviços de DevOps do Azure | Azure DevOps Server 2022 - Azure DevOps Server 2019

As extensões de DevOps do Azure podem armazenar preferências do usuário e estruturas de dados complexas diretamente na infraestrutura fornecida pela Microsoft, o que garante que os dados do usuário estejam seguros e com backup como outros dados da organização e do projeto. Isso também significa que, para necessidades simples de armazenamento de dados, você, como provedor de extensão, não é obrigado a configurar, gerenciar ou pagar por serviços de armazenamento de dados de terceiros.

Há dois métodos para interagir com o serviço de armazenamento de dados: por meio de APIs REST ou por meio de um serviço de cliente fornecido pela Microsoft, que faz parte do VSS SDK. Aconselhamos os desenvolvedores de extensões a utilizar as APIs de serviço de cliente fornecidas, pois elas oferecem um encapsulamento amigável das APIs REST.

Nota

Procurando APIs REST do Azure DevOps? Consulte a referência mais recente da API REST do Azure DevOps.

Para obter informações sobre bibliotecas de cliente .NET, consulte Bibliotecas de cliente .NET para Azure DevOps.

O que pode armazenar

O serviço foi projetado para permitir que você armazene e gerencie dois tipos diferentes de dados:

  • Configurações: configurações simples de chave-valor (como preferências do usuário)
  • Documentos: coleções de objetos complexos semelhantes (documentos)

Uma coleção é como um contêiner indexado para documentos. Um documento é um blob JSON que pertence a uma coleção. Além de alguns nomes de propriedade reservados, você controla e gerencia o esquema desses documentos.

Como você pode definir o escopo dos dados

As configurações e as coleções de documentos podem ter como escopo para:

  • Coleção de projetos: compartilhada por todos os usuários da coleção de projetos na qual a extensão está instalada
  • Usuário: um único usuário de uma coleção de projetos na qual a extensão está instalada

Armazenamento de configurações

Os dois principais métodos para gerenciar configurações sãogetValue():setValue()

  • getValue() aceita uma chave string (juntamente com outras opções como escopo) e retorna um IPromise. O valor resolvido dessa promessa é o valor associado à chave fornecida.
  • setValue() aceita uma chave de cadeia de caracteres, um valor e outras opções, como escopo, e retorna um IPromise. O valor resolvido dessa promessa é o valor atualizado da configuração.

Aqui está um exemplo de como definir um valor:

        private async initializeState(): Promise<void> {
        await SDK.ready();
        const accessToken = await SDK.getAccessToken();
        const extDataService = await SDK.getService<IExtensionDataService>(CommonServiceIds.ExtensionDataService);
        this._dataManager = await extDataService.getExtensionDataManager(SDK.getExtensionContext().id, accessToken);

        this._dataManager.getValue<string>("test-id").then((data) => {
            this.setState({
                dataText: data,
                persistedText: data,
                ready: true
            });
        }, () => {
            this.setState({
                dataText: "",
                ready: true
            });
        });
    }

Veja um exemplo de como recuperar um valor de configuração:

    // Get data service
    SDK.getService(SDK.getContributionId()).then(function(dataService) {
        // Get value in user scope
        dataService.getValue("userScopedKey", {scopeType: "User"}).then(function(value) {
            console.log("User scoped key value is " + value);
        });
    });

Se scopeType não for especificado, as configurações serão armazenadas no nível da coleção de projetos e poderão ser acessadas por todos os usuários dessa coleção de projetos usando a extensão. Veja um exemplo de como definir um valor de configuração no nível da coleção de projetos:

    // Get data service
    SDK.getService(SDK.getContributionId()).then(function(dataService) {
        // Set value (default is project collection scope)
        dataService.setValue("someKey", "abcd-efgh").then(function(value) {
            console.log("Key value is " + value);
        });
    });

Armazenamento de dados (coleções de documentos)

Para lidar com dados mais complexos além dos pares chave-valor, você pode utilizar o conceito de documentos para executar operações CRUD nos dados da sua extensão. Um documento é um blob JSON, aprimorado com duas propriedades especiais: ID e __etag. Se forem cruciais para o modelo de dados de uma extensão, os IDs podem ser definidos pelo usuário ou, se não forem especificados, o sistema os gera. Esses IDs devem ser exclusivos dentro de uma coleção específica. Como uma coleção se refere a um escopo e instância específicos de uma extensão, isso implica que o mesmo ID de documento pode ser reutilizado em coleções diferentes.

Estão disponíveis as seguintes operações documentais:

  • Obter um documento
  • Criar um documento
  • Definir um documento (criar ou atualizar)
  • Atualizar um documento
  • Eliminar um documento

Há também uma única operação que pode ser realizada em uma coleção: Obter todos os documentos

Obter um documento por ID

Obter um documento de uma coleção usando seu identificador é simples, como o exemplo a seguir:

    // Acquire data service
    SDK.getService(SDK.getContributionId()).then(function(dataService) {
        // Retrieve document by id
        dataService.getDocument("MyCollection", "MyDocumentId").then(function(doc) {
            // Assuming document has a property named foo
            console.log("Doc foo: " + doc.foo);
        });
    });

Esta operação tenta buscar um documento com a ID "MyDocumentId" da coleção "MyCollection". Na ausência de um escopo fornecido, o serviço usa como padrão a coleção com escopo para toda a instância dessa extensão. Se essa coleção ou um documento com a ID especificada não existir, um erro 404 será retornado, que a extensão deve manipular. O documento retornado é um objeto JSON que inclui todas as suas propriedades, juntamente com a ID especial e __etag as propriedades utilizadas pelo serviço de armazenamento de dados.

    // Get data service
    SDK.getService(SDK.getContributionId()).then(function(dataService) {
        // Get document by id
        dataService.getDocument("MyCollection", "MyDocumentId").then(function(doc) {
            // Assuming document has a property named foo
            console.log("Doc foo: " + doc.foo);
        });
    });

Esta chamada tenta recuperar um documento com a ID "MyDocumentId" da coleção "MyCollection". Como nenhum escopo é fornecido, a coleção que o serviço usa obtém o escopo padrão de toda a instância dessa extensão. Se essa coleção não existir ou um documento com essa ID não existir, então um 404 será retornado, que a extensão deve manipular. O documento retornado é um objeto JSON que contém todas as suas próprias propriedades, além da ID especial e __etag das propriedades usadas pelo serviço de armazenamento de dados.

Criar um documento

Para criar um novo documento, execute uma chamada como o exemplo a seguir:

    // Get data service
    SDK.getService(SDK.getContributionId()).then(function(dataService) {
        // Prepare document first
        var newDoc = {
            fullScreen: false,
            screenWidth: 500
        };

        dataService.createDocument("MyCollection", newDoc).then(function(doc) {
            // Even if no ID was passed to createDocument, one gets generated
            console.log("Doc id: " + doc.id);
        });
    });

Se a coleção com o nome e o escopo fornecidos ainda não existir, ela será criada dinamicamente antes que o próprio documento seja criado.

Se o documento fornecido contiver uma id propriedade, esse valor será usado como a ID exclusiva do documento. Por favor, note que o fornecido id deve ser limitado a 50 caracteres. Se esse campo não existir, um GUID será gerado pelo serviço e incluído no documento que é retornado quando a promessa é resolvida.

Se já existir outro documento na coleção com o mesmo ID fornecido no documento, a operação falhará. Se o comportamento desejado for criar um novo documento se a ID não existir, mas modificar o documento existente se existir, o setDocument() método deverá ser usado.

Definir um documento (atualizar ou criar)

A setDocument() função realiza uma operação "upsert" - modifica um documento existente se o seu ID estiver presente e corresponder a um documento na coleção. Se o ID estiver ausente ou não corresponder a nenhum documento da coleção, um novo documento será adicionado à coleção.

    // Get data service
    SDK.getService(SDK.getContributionId()).then(function(dataService) {
        // Prepare document first
        var myDoc = {
            id: 1,
            fullScreen: false,
            screenWidth: 500
        };

        dataService.setDocument("MyCollection", myDoc).then(function(doc) {
            console.log("Doc id: " + doc.id);
        });
    });

Atualizar um documento

A updateDocument função requer que o documento que está sendo alterado já resida no acervo. Uma exceção será lançada se nenhuma ID for fornecida ou se a ID fornecida não corresponder a nenhum documento da coleção.

Aqui está um exemplo de como a atualização é usada:

    // Get data service
    SDK.getService(SDK.getContributionId()).then(function(dataService) {
        var collection = "MyCollection";
        var docId = "1234-4567-8910";
        // Get document first
        dataService.getDocument(collection, docId, { scopeType: "User" }).then(function(doc) {
            // Update the document
            doc.name = "John Doe";
            dataService.updateDocument(collection, doc, { scopeType: "User" }).then(function(d) {
                // Check the new version
                console.log("Doc version: " + d.__etag);
            });
        });
    });

Eliminar um documento

Esta função exclui o documento com o ID fornecido da coleção fornecida. Se a coleção não existir ou o documento não existir, um 404 será devolvido.

Aqui está um exemplo de uso:

    // Get data service
    SDK.getService(SDK.getContributionId()).then(function(dataService) {
        var docId = "1234-4567-8910";
        // Delete document
        dataService.deleteDocument("MyCollection", docId).then(function() {
            console.log("Doc deleted");
        });
    });

Obter todos os documentos de uma coleção

O exemplo a seguir recupera todos os documentos da coleção "MyCollection" usando o serviço de dados e, em seguida, registra o número de documentos no console:

    // Get data service
    SDK.getService(SDK.getContributionId()).then(function(dataService) {
        // Get all document under the collection
        dataService.getDocuments("MyCollection").then(function(docs) {
            console.log("There are " + docs.length + " in the collection.");
        });
    });

Esta chamada recupera todos os documentos em uma coleção com escopo, com um limite de 100.000 documentos. Se a coleção for inexistente, ela retornará um erro 404.

Avançado

Como as configurações são armazenadas

Esta chamada encapsula o setDocument método cliente, fornecendo-lhe várias partes de dados. Como dito anteriormente, as configurações são salvas internamente como documentos. Portanto, um documento básico é gerado dinamicamente, onde o ID do documento é a chave dada no setValue() método. O documento tem mais duas propriedades. A value propriedade contém o valor passado para o método e a revision propriedade é definida como -1. Embora a revision propriedade seja mais elaborada na seção "Trabalhando com documentos", no contexto das configurações, a configuração revision como -1 no documento significa que não estamos preocupados com o controle de versão desse documento de configurações.

Como as configurações são armazenadas como documentos, precisamos fornecer um nome de coleção, indicando onde armazenar o documento. Para simplificar, ao trabalhar com os setValue()/getValue() métodos, o nome da coleção é sempre o nome $settingsespecial. A chamada anterior emite uma solicitação PUT no seguinte ponto de extremidade:

GET _apis/ExtensionManagement/InstalledExtensions/{publisherName}/{extensionName}/Data/Scopes/User/Me/Collections/%24settings/Documents

A carga útil da solicitação é como o exemplo a seguir:

{
                "id": "myKey",
                "__etag": -1,
                "value": "myValue"
}

APIs REST

Supondo que esse trecho seja executado depois que o valor for definido, você verá uma mensagem de alerta contendo o texto "O valor é myValue". O método getValue é novamente um wrapper em torno das APIs REST, emitindo uma solicitação GET para o seguinte ponto de extremidade:

GET _apis/ExtensionManagement/InstalledExtensions/{publisherName}/{extensionName}/Data/Scopes/User/Me/Collections/%24settings/Documents/myKey

etags

O __etag campo é usado pelo Serviço de Armazenamento de Dados para gerenciamento de simultaneidade de documentos. Antes de salvar uma atualização, o serviço verifica se o __etag documento armazenado atualmente corresponde ao __etag documento atualizado. Se corresponderem, o é incrementado __etag e o documento atualizado é devolvido ao chamador. Se eles não corresponderem, isso indica que o documento a ser atualizado está desatualizado e uma exceção é lançada. O gravador de extensão é responsável por lidar com essa exceção normalmente, seja recuperando o documento mais recente __etag , mesclando as alterações e tentando novamente a atualização, ou notificando o usuário.

Para alguns tipos de documentos, o nível de simultaneidade fornecido pode não ser necessário, e um modelo last-in-wins pode ser mais adequado. Nesses casos, ao editar o documento, insira -1 como o __etag valor para significar essa funcionalidade. O serviço de configurações mencionado anteriormente emprega esse modelo para armazenar configurações e preferências.