Compartilhar via


Este artigo foi traduzido por máquina.

O programador

Mudando para o NoSQL com o MongoDB, Parte 2

Ted Neward

Baixe o código de exemplo

Ted NewardNo meu artigo anterior , Noções básicas do MongoDB levou a frente e no centro: obtenção de instalá-lo, executando e inserindo e localizar dados. No entanto, abordei apenas as noções básicas sobre — os objetos de dados usados foram pares nome/valor simples. Que fazia sentido, porque “ nicho do MongoDB ” inclui as estruturas de dados não-estruturados e relativamente simples. Mas certamente esse banco de dados pode armazenar pares nome/valor mais do que simplesmente simples.

Neste artigo, vamos usar um método um pouco diferentes para investigar MongoDB (ou qualquer tecnologia). O procedimento, chamado de um teste de exploração, o ajudarão a encontrar um possível bug no servidor e, ao longo do caminho, realce os problemas comuns de desenvolvedores orientado a objeto serão executados ao usar MongoDB.

No nosso último episódio …

Primeiro certifique-se de que estamos todos na mesma página, e também abordaremos alguns Terra Nova um pouco. Olhe Let’s MongoDB um pouco mais estruturada de forma que fizemos no artigo anterior (de msdn.microsoft.com/magazine/ee310029). Em vez disso, que acabou de criar um aplicativo simples e invadir nele, let’s kill dois pássaros com uma pedra e criar testes da exploração — segmentos de código que parecem testes de unidade, mas que explore a funcionalidade em vez de tentar verificá-lo.

Escrita de testes de exploração serve várias finalidades diferentes ao investigar uma nova tecnologia. Um, elas ajudam a descobrir se a tecnologia sob investigação é inerentemente testável (partindo do pressuposto de que, se é difícil a exploração de teste, ele será difícil de teste de unidade — um sinalizador de vermelho grande). Dois, eles servem como uma espécie de regressão quando uma nova versão da tecnologia sob investigação vem, pois eles fornecem uma direta se a funcionalidade do antiga não funciona mais. E três, desde que os testes devem ser relativamente pequena e granular, exploração testes inerentemente aprender uma tecnologia mais fácil com a criação de novas ocorrências “ hipotética ” que se baseiam em casos anteriores.

Mas, diferentemente dos testes de unidade, exploração de testes não são desenvolvidos continuamente juntamente com o aplicativo, assim, depois que você considerar que a tecnologia que aprendeu, separe os testes. Don descartá-las, no entanto, eles também podem ajudar a separar os bugs no código do aplicativo na biblioteca ou estrutura. Os testes de fazem-lo, fornecendo um ambiente leve, independente de aplicativo para experimentação sem a sobrecarga do aplicativo.

Com isso em mente, crie let’s MongoDB-explorar, um projeto de teste do Visual c#. Adicione MongoDB.Driver.dll à lista de referências de assembly e a compilação para certificar-se de que tudo o que é bom ir. (Compilação deve pegar o um TestMethod que será gerada como parte do modelo de projeto. Ele irá passar por padrão, portanto tudo o que deve ser um bom, que significa que, se o projeto apresentou falha na criação, algo é parafusado no ambiente de. Considerações de verificação é sempre bom.)

Como tentador como seria saltar para escrever o código funcione corretamente, no entanto, um problema de superfícies muito rapidamente: MongoDB precisa que o processo de servidor externo (mongod.exe) esteja em execução antes que o código do cliente pode conectar-se contra ele e fazer algo útil. Embora seja tentador simplesmente dizer “ bem, bem, let’s iniciá-lo e voltar a escrever código ”, há um problema corollary. É uma aposta-se de que quase que em algum momento, 15 semanas mais tarde quando vir a olhar novamente esse código, alguns desenvolvedores ruim (você, me ou um funcionário) tentará executar esses testes, todos eles falharem e perder dois ou três dias, tentando descobrir o que está acontecendo antes que ela acha que a aparência para ver se o servidor da execução.

Lição: Tente capturar todas as dependências nos testes de alguma forma. O problema surgirá novamente durante os testes de unidade, mesmo assim. Nesse ponto, precisaremos iniciar a partir de um servidor de limpo, fizer algumas alterações e depois desfazê-las todas. Isso é mais fácil de realizar simplesmente interrompendo e portanto, a partir do servidor, resolvendo agora economiza tempo posteriormente.

Essa idéia de executar algo antes do teste (ou posterior, ou ambos) não é um novo e Microsoft Test Lab o Gerenciador de projetos e pode ter inicializadores por teste e o conjunto por teste e métodos de limpeza. Eles estão adornados com os atributos personalizados ClassInitialize e ClassCleanup de escrituração contábil do conjunto por testes e TestInitialize e TestCleanup de escrituração contábil de por teste. (Consulte “ de Working with Unit Tests ” para obter mais detalhes). Assim, o inicializador de um conjunto por teste iniciará o processo de mongod.exe e a limpeza de pacote por teste desligará o processo, como mostrado no do Figura 1.

Figura 1 de parcial de código de limpeza e de inicializador de teste

namespace MongoDB_Explore
{
  [TestClass]
  public class UnitTest1
  {
    private static Process serverProcess;

   [ClassInitialize]
   public static void MyClassInitialize(TestContext testContext)
   {
     DirectoryInfo projectRoot = 
       new DirectoryInfo(testContext.TestDir).Parent.Parent;
     var mongodbbindir = 
       projectRoot.Parent.GetDirectories("mongodb-bin")[0];
     var mongod = 
       mongodbbindir.GetFiles("mongod.exe")[0];

     var psi = new ProcessStartInfo
     {
       FileName = mongod.FullName,
       Arguments = "--config mongo.config",
       WorkingDirectory = mongodbbindir.FullName
     };

     serverProcess = Process.Start(psi);
   }
   [ClassCleanup]
   public static void MyClassCleanup()
   {
     serverProcess.CloseMainWindow();
     serverProcess.WaitForExit(5 * 1000);
     if (!serverProcess.HasExited)
       serverProcess.Kill();
  }
...

Na primeira vez que for executada, uma caixa de diálogo será exibida informando ao usuário que o processo está sendo iniciado. Clicar em OK fará com que a caixa de diálogo desaparecer... até a próxima vez que o teste é executado. Depois que essa caixa de diálogo fica muito desagradável, localizar a caixa de opção que diz, “ nunca mostrar esta caixa de diálogo novamente ” e verifique-o para verificar a mensagem desaparece para sempre. Se estiver executando o software de firewall, como o Firewall do Windows, a caixa de diálogo provavelmente fará uma aparência aqui também, porque o servidor deseja abrir uma porta para receber conexões de cliente. Aplicar o mesmo tratamento e tudo o que deve ser executado silenciosamente. Coloque um ponto de interrupção na primeira linha do código de limpeza para verificar se que o servidor estiver sendo executado, se desejado.

Depois que o servidor está executando, testes podem iniciar o acionamento — com exceção de superfícies de outro problema: Cada teste deseja trabalhar com seu próprio banco de dados novo, mas é útil para o banco de dados possui alguns dados pré-existentes para facilitar os testes de certas coisas (consultas, por exemplo) mais fácil. Seria bom se cada teste pode ter seu próprio novo conjunto de dados já existentes. Que será a função dos métodos adornados TestInitializer e TestCleanup.

Mas antes de obter para que, let’s examinaremos esse TestMethod rápida, que tenta assegurar que o servidor pode ser encontrado uma conexão feita e um objeto inserido, encontradas e removidas, para que a exploração testa Deleite-se com o que é abordado no artigo anterior (consulte do Figura 2).

De TestMethod para Verifique se que o servidor pode ser encontrado e uma conexão feita, a Figura 2

[TestMethod]
public void ConnectInsertAndRemove()
{
  Mongo db = new Mongo();
  db.Connect();

  Document ted = new Document();
  ted["firstname"] = "Ted";
  ted["lastname"] = "Neward";
  ted["age"] = 39;
  ted["birthday"] = new DateTime(1971, 2, 7);
  db["exploretests"]["readwrites"].Insert(ted);
  Assert.IsNotNull(ted["_id"]);

  Document result =
    db["exploretests"]["readwrites"].FindOne(
    new Document().Append("lastname", "Neward"));
  Assert.AreEqual(ted["firstname"], result["firstname"]);
  Assert.AreEqual(ted["lastname"], result["lastname"]);
  Assert.AreEqual(ted["age"], result["age"]);
  Assert.AreEqual(ted["birthday"], result["birthday"]);

  db.Disconnect();
}

Se esse código é executado, ele encontra uma declaração e o teste falhar. Em particular, a última declaração ao redor “ aniversário ” é acionada. Portanto, aparentemente, enviar um DateTime no banco de dados MongoDB sem uma hora não viagem bastante corretamente. O tipo de dados fica como uma data com um tempo associado da meia-noite, mas retorna como uma data com um tempo associado de 8: 00, que interrompe a asserção AreEqual no final do teste.

Isso realça a utilidade do teste de exploração — sem ele (como é o caso, por exemplo, com o código do artigo anterior), essa característica do MongoDB pouco talvez passaram despercebida até semanas ou meses no projeto. Se esse é um bug no servidor MongoDB é uma questão de valor e não algo a ser explorada no momento. A questão é que o teste de exploração coloca a tecnologia de microscópio, ajudando a isolar esse comportamento “ interessante ”. Que permite que os desenvolvedores que desejam para usar a marca de tecnologia de suas próprias decisões como para se esta é uma alteração significativa. Informado é forearmed.

A propósito, corrigir o código para que o teste passar, requer que o DateTime fornecido volta do banco de dados a ser convertido em hora local. Eu levados isso em um fórum online e de acordo com a resposta do autor MongoDB.Driver, Sam Corder, “ todas as datas em que são convertidas para UTC, mas esquerda como UTC voltem check-out. ” Portanto, você deve tanto converter DateTime UTC antes de armazená-lo via DateTime.ToUniversalTime ou então converta qualquer DateTime recuperados do banco de dados para o fuso horário local via DateTime.ToLocalTime, usando o exemplo de código a seguir:

Assert.AreEqual(ted["birthday"], 
  ((DateTime)result["birthday"]).ToLocalTime());

Isso por si só destaca uma das grandes vantagens dos esforços de comunidade — em geral as entidades envolvidas são apenas um email imediatamente.

A adição de complexidade

Os desenvolvedores que desejam para usar MongoDB necessidade de entender que, ao contrário do que a aparência inicial, ele não é um banco de dados do objeto — ou seja, ele não pode lidar com gráficos de objeto complexas arbitrariamente sem a Ajuda. Existem algumas convenções que lidam com maneiras de fornecer a ajudá-lo, mas até o momento, fazendo assim permanece nos ombros do desenvolvedor.

Por exemplo, considere a possibilidade de Figura 3 de , uma coleção simples de objetos criados para refletir o armazenamento de um número de documentos que descrevem uma família bem conhecida. Até aqui tudo bem. Na verdade, enquanto ele estiver com ele, o teste realmente deve consultar o banco de dados para os objetos inseridos, como mostrado no do Figura 4, a fim de tornar-se de que eles são recuperáveis. E … o teste passar. A Awesome.

De de uma coleção de objetos simples, a Figura 3

[TestMethod]
public void StoreAndCountFamily()
{
  Mongo db = new Mongo();
  db.Connect();

  var peter = new Document();
  peter["firstname"] = "Peter";
  peter["lastname"] = "Griffin";

  var lois = new Document();
  lois["firstname"] = "Lois";
  lois["lastname"] = "Griffin";

  var cast = new[] {peter, lois};
  db["exploretests"]["familyguy"].Insert(cast);
  Assert.IsNotNull(peter["_id"]);
  Assert.IsNotNull(lois["_id"]);

  db.Disconnect();
}

De consultar o banco de dados para os objetos, a Figura 4

[TestMethod]
public void StoreAndCountFamily()
{
  Mongo db = new Mongo();
  db.Connect();

  var peter = new Document();
  peter["firstname"] = "Peter";
  peter["lastname"] = "Griffin";

  var lois = new Document();
  lois["firstname"] = "Lois";
  lois["lastname"] = "Griffin";

  var cast = new[] {peter, lois};
  db["exploretests"]["familyguy"].Insert(cast);
  Assert.IsNotNull(peter["_id"]);
  Assert.IsNotNull(lois["_id"]);

  ICursor griffins =
    db["exploretests"]["familyguy"].Find(
      new Document().Append("lastname", "Griffin"));
  int count = 0;
  foreach (var d in griffins.Documents) count++;
  Assert.AreEqual(2, count);

  db.Disconnect();
}

Na verdade, que pode não ser completamente verdadeiro — leitores acompanhando em casa e digitar o código podem achar que o teste não passa Afinal de contas, como afirma que a contagem esperada de objetos não correspondência de 2. Isso é porque, como bancos de dados devem fazer, ele mantém o estado entre invocações de e o código de teste não está explicitamente a remoção desses objetos, eles permanecem em testes.

Isso realça outro recurso do banco de dados orientada por documento: Duplicatas totalmente esperadas e permitidas. Que é por que cada documento, uma vez inseridos, é marcado com o atributo implicit_id e recebe um identificador exclusivo para ser armazenada dentro dele, torna-se, na verdade, de chave primária do documento.

Portanto, se forem passar os testes, o banco de dados precisa ser limpa antes de cada teste ser executado. Embora seja bastante fácil, basta excluir os arquivos no diretório onde MongoDB armazena, novamente, com isso é feito automaticamente como parte do conjunto de teste é vastamente preferível. Cada teste pode fazê-lo manualmente após a conclusão, que foi possível obter a ser um pouco maçante ao longo do tempo. Ou o código de teste pode tirar proveito dos recursos de TestInitialize e TestCleanup do teste de Microsoft e gerente de laboratório para capturar o código comum (e por que não incluem o banco de dados se conectar e desconectar-se a lógica), conforme mostrado no do Figura 5.

A Figura 5 de as vantagens das TestInitialize e TestCleanup

private Mongo db;

[TestInitialize]
public void DatabaseConnect()
{
  db = new Mongo();
  db.Connect();
}
        
[TestCleanup]
public void CleanDatabase()
{
  db["exploretests"].MetaData.DropDatabase();

  db.Disconnect();
  db = null;
}

Embora a última linha do método CleanDatabase é desnecessária, pois o próximo teste substituirá a referência de campo com um novo mongo objeto, às vezes, é melhor tornar claro que a referência não é boa. Advertência emptor. O importante é que o banco de dados impuros de teste é descartado, esvaziando os arquivos que MongoDB usa para armazenar os dados e deixando tudo renovado e cintilantes limpa para o próximo teste.

Como as coisas se, no entanto, o modelo da família de produtos está incompleto, duas pessoas referenciadas são alguns e considerando isso, eles devem ter uma referência entre si como spouses, conforme mostrado aqui:

peter["spouse"] = lois;
  lois["spouse"] = peter;

Executar esse teste, no entanto, gera uma StackOverflowException — o serializador de driver MongoDB nativamente não entende a noção de circular referencia e ingenuamente segue as referências ao redor de ad infinitum . Opa. Não é boa.

Corrigir isso requer que você escolha uma das duas opções. Com um, o campo de sua esposa pode ser preenchido com _id campo os outros documentos (depois de ter sido inserido esse documento) e atualizados, conforme mostrado no do Figura 6.

Figura 6 de superar o problema de referências a circular

[TestMethod]
public void StoreAndCountFamily()
{
  var peter = new Document();
  peter["firstname"] = "Peter";
  peter["lastname"] = "Griffin";

  var lois = new Document();
  lois["firstname"] = "Lois";
  lois["lastname"] = "Griffin";

  var cast = new[] {peter, lois};
  var fg = db["exploretests"]["familyguy"];
  fg.Insert(cast);
  Assert.IsNotNull(peter["_id"]);
  Assert.IsNotNull(lois["_id"]);

  peter["spouse"] = lois["_id"];
  fg.Update(peter);
  lois["spouse"] = peter["_id"];
  fg.Update(lois);

  Assert.AreEqual(peter["spouse"], lois["_id"]);
  TestContext.WriteLine("peter: {0}", peter.ToString());
  TestContext.WriteLine("lois: {0}", lois.ToString());
  Assert.AreEqual(
    fg.FindOne(new Document().Append("_id",
    peter["spouse"])).ToString(),
    lois.ToString());

  ICursor griffins =
    fg.Find(new Document().Append("lastname", "Griffin"));
  int count = 0;
  foreach (var d in griffins.Documents) count++;
  Assert.AreEqual(2, count);
}

No entanto, há uma desvantagem da abordagem: Ele requer que os documentos a ser inserido no banco de dados e seus valores _id (que são instâncias da identificação de objeto, na linguagem do MongoDB.Driver) ser copiados para os campos de sua esposa de cada objeto conforme apropriado. Depois de cada documento será atualizado novamente. Apesar de viagens ao banco de dados MongoDB rápidas em comparação com aqueles com uma atualização de RDBMS tradicional, este método ainda é um pouco dispendioso.

Uma segunda abordagem é gerar previamente os valores de identificação de objeto para cada documento, preencher os campos de sua esposa e, em seguida, enviar o lote inteiro para o banco de dados, como mostrado no do Figura 7.

A Figura 7 de de Um método melhor para solucionar o problema de referências a circular

[TestMethod]
public void StoreAndCountFamilyWithOid()
{
  var peter = new Document();
  peter["firstname"] = "Peter";
  peter["lastname"] = "Griffin";
  peter["_id"] = Oid.NewOid();

  var lois = new Document();
  lois["firstname"] = "Lois";
  lois["lastname"] = "Griffin";
  lois["_id"] = Oid.NewOid();

  peter["spouse"] = lois["_id"];
  lois["spouse"] = peter["_id"];

  var cast = new[] { peter, lois };
  var fg = db["exploretests"]["familyguy"];
  fg.Insert(cast);

  Assert.AreEqual(peter["spouse"], lois["_id"]);
  Assert.AreEqual(
    fg.FindOne(new Document().Append("_id",
    peter["spouse"])).ToString(),
    lois.ToString());

  Assert.AreEqual(2, 
    fg.Count(new Document().Append("lastname", "Griffin")));
}

Essa abordagem requer apenas o método Insert, porque agora os valores de identificação de objeto são conhecidos antes do tempo. Observe, a propósito, que as chamadas de ToString no teste de asserção deliberadas — dessa forma, os documentos são convertidos em seqüências de caracteres antes de serem comparadas.

O que é realmente importante a observar sobre o código do Figura 7, no entanto, é que de-referencing documento referenciado por meio da identificação de objeto pode ser relativamente difícil e tediosa porque o estilo orientado por documento assume que os documentos mais ou menos são entidades autônomas ou hierárquicas, não um gráfico de objetos. (Observe que o driver do .net fornece DBRef, que fornece uma maneira um pouco mais rica de referência/desreferência de outro documento, mas ainda não vai torná-lo em um sistema amigável de gráfico de objeto). Assim, embora seja certamente possível levar a um modelo de objeto avançado e armazená-lo em um banco de dados MongoDB, não é recomendável . Usar armazenamento de clusters estreitamente grupos de dados, usando os documentos do Word ou Excel como uma metáfora orientador. Se algo que pode ser considerado um documento grande ou planilha, em seguida, provavelmente é uma boa opção para MongoDB ou algum outro banco de orientado por documento dados.

Mais para explorar

Podemos concluir nossa investigação MongoDB, mas antes de concluirmos, há poucas coisas mais para explorar, incluindo a execução de consultas de predicado, agregados, suporte a LINQ e algumas anotações de administração de produção. Nós irá lidar com esse mês que vem. (Esse artigo vai ser uma parte muito ocupada!) Nesse ínterim, explorar o sistema MongoDB e certifique-se de soltar-me um email com sugestões para colunas futuras.

Ted Neward   é uma entidade de segurança com Neward & Associates, uma empresa independente especializado em sistemas de plataforma de .NET Framework e Java corporativo. Ele escreveu mais de 100 artigos, é um MVP de c#, palestrante da INETA e autor ou co-autor de diversos livros, incluindo o disponível em breve “ Professional F # 2. 0 ” (Wrox). Ele consulta e mentors regularmente. Entrar em ted@tedneward.com de e leia seu blog em blogs.tedneward.com de .

Graças ao especialista técnico seguir para revisar este artigo: Sam Corder