Armazenamento de dados
Azure DevOps Services | 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, assim 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 SDK do VSS. Aconselhamos os desenvolvedores de extensão a utilizar as APIs de serviço de cliente fornecidas, pois elas oferecem um encapsulamento amigável das APIs REST.
Observação
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 DevOps do Azure.
O que você 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 o escopo de:
- 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 projeto na qual a extensão está instalada
Armazenamento de configurações
Os dois principais métodos para gerenciar configurações são getValue()
e setValue()
:
getValue()
aceita uma chave de cadeia de caracteres (junto 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.
Veja 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.
Aqui está um exemplo de como definir um valor de configuração no nível da coleção do projeto:
// 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 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 a mesma ID de documento pode ser reutilizada em coleções diferentes.
As seguintes operações de documento estão disponíveis:
- Obter um documento
- Criar um documento
- Definir um documento (criar ou atualizar)
- Atualizar um documento
- Excluir 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 no 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);
});
});
Essa 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 o escopo da coleção 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);
});
});
Essa 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 recebe o escopo para o 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, 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 documento em si 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 será retornado quando a promessa for resolvida.
Se outro documento na coleção já existir com a mesma ID fornecida 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 executa uma operação "upsert" - modifica um documento existente se sua ID estiver presente e corresponder a um documento na coleção. Se a 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 exige que o documento que está sendo alterado já resida na coleção. Uma exceção será lançada se nenhuma ID for fornecida ou se a ID fornecida não corresponder a nenhum documento na 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);
});
});
});
Excluir um documento
Essa função exclui o documento com a ID fornecida da coleção fornecida. Se a coleção não existir ou o documento não existir, um 404 será retornado.
Aqui está um uso de exemplo:
// 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.");
});
});
Essa chamada recupera todos os documentos em uma coleção com escopo, com um limite de 100.000 documentos. Se a coleção não existir, ela retornará um erro 404.
Avançado
Como as configurações são armazenadas
Essa 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 fornecida 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 deste 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 manter as coisas simples, ao trabalhar com os setValue()
/getValue()
métodos, o nome da coleção é sempre o nome $settings
especial . 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 uma atualização ser salva, o serviço verifica se o __etag
documento armazenado atualmente corresponde ao __etag
do documento atualizado. Se corresponderem, o é incrementado __etag
e o documento atualizado é retornado 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, recuperando a última __etag
do documento, 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 de última vitória 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.