Eu estava pensando em criar um aplicativo de referência para usar o .NET e o Couchbase Server. Durante minha viagem a Londres para a conferência Progressive NoSQL na As habilidades são importantesComo eu estava esperando que meu quarto de hotel ficasse pronto, passei as horas escrevendo alguns códigos em um Startbucks para um aplicativo que chamo de TapMap.

O aplicativo é bastante simples; na verdade, é apenas uma imitação do Twitter com um foco específico em cervejas. A ideia básica é que alguém em um bar informe que encontrou (e provavelmente bebeu) uma determinada cerveja em um determinado local. Esse relato é chamado de "tap". O recurso "mapa" renderiza uma série de taps em um mapa Nokia, usando o GeoCouch. O valor é que os usuários podem saber rapidamente onde encontrar suas cervejas favoritas!

O código ainda é muito alfa e minhas habilidades em HTML precisam ser aprimoradas. Mas já implementei o suficiente para iniciar uma série de postagens sobre os padrões de código e as soluções expostas pelo TapMap. Esta publicação será a primeira parte e descreverá como criar um simples Repositório de T para o Couchbase usando a biblioteca de clientes .NET. Você pode obter o código em http://github.com/couchbaselabs/TapMap.

Se você não estiver familiarizado com repositórios, a ideia básica é que você tenha uma classe responsável pelo acesso aos dados de um objeto de domínio. Assim, para consultar um usuário pelo endereço de e-mail dele, você usaria o método GetByEmail(string email) do UserRepository. Os repositórios também costumam conter métodos CRUD padrão. Há um pouco mais sobre o padrão, mas vou demonstrar seu uso básico. Leia mais em http://msdn.microsoft.com/en-us/library/ff649690.aspx.

A herança é uma maneira natural de obter reutilização de código. O TapMap usa uma classe de repositório base para fornecer métodos CRUD às suas subclasses. Além disso, a classe base faz uso de genéricos para que os métodos CRUD possam retornar instâncias de T em vez de apenas objetos. A restrição de T a um tipo específico também permite alguns benefícios adicionais que discutirei em breve.

público abstrato classe RepositórioBase<T> onde T : ModelBase
{
//métodos brutos aqui
}

Como você já deve saber, o Couchbase Server 2.0 não impõe um esquema aos seus documentos. Você armazena um documento JSON com todas as propriedades que desejar incluir. Dito isso, todos os documentos são armazenados com uma propriedade reservada "_id" que deriva seu valor de sua chave.

Adicionar um documento da seguinte forma:

cliente.Loja(StoreMode.Adicionar, "beer_Three_Philosophers", “{ Nome : Três filósofos }”);

O resultado é um documento como o seguinte:

{
"_id" : "beer_Three_Philosophers",
"name" (nome) : "Três Filósofos"
}

Portanto, para garantir que todos os nossos objetos de modelo tenham essa propriedade de ID, criaremos uma classe base de modelo que a defina.

[Serializável]
público abstrato classe ModelBase
{
[JsonProperty(Nome da propriedade = "_id")]
público string Id { obter; definir; }
}

Não vou me aprofundar nas partes do JsonProperty aqui. Estou usando o Json.NET para serialização e escrevi mais sobre isso em http://www.couchbase.com/develop/net/next. O importante a observar é que temos objetos modelo com uma propriedade "Id" e, ao restringir nossos repositórios a usar subclasses de ModelBase, sabemos que sempre teremos essa propriedade.

Ao criar exibições, é útil ter uma taxonomia nos documentos para que seja fácil encontrar todos os documentos de "cerveja" ou todos os documentos de "usuário" ou até mesmo encontrar um documento de "usuário" com um determinado endereço de e-mail. Para facilitar esse requisito, imporemos uma propriedade "type" a todos os documentos do modelo. Em nosso ModelBase, adicionaremos uma propriedade Type abstrata e somente de leitura.

[JsonProperty("tipo")]
público abstrato string Tipo { obter; }

Agora que entendemos as suposições que podemos fazer em nosso modelo, vamos voltar à nossa classe RespositoryBase.

A primeira coisa que nossa classe RepositoryBase precisa fazer é definir uma instância do CouchbaseCliet.

protegida estático somente leitura CouchbaseClient _Cliente = nulo;

estático RepositórioBase()
{
_Cliente = novo Cliente Couchbase();
}

Como a configuração do cliente é cara, vamos garantir que ela ocorra apenas uma vez por AppDomain, criando uma instância estática somente de leitura que é instanciada em um construtor estático. O cliente é exposto como um campo protegido para subclasses que precisam estender os métodos CRUD básicos em nossa classe base. Observe que esse cliente depende de o Web.config ter definido a seção "couchbase". Em algum momento, adicionarei uma versão compatível com IoC do nosso RepositoryBase.

A base do repositório define os métodos Create, Update e Save. Esses métodos, por sua vez, chamam o método "store" privado com o valor StoreMode apropriado. Create e Update são simples. Save simplesmente cria um documento quando a chave não existe e o substitui quando existe. Os valores de retorno são o valor CAS da operação. Em algum momento, adicionarei o CAS como um parâmetro opcional. O método store também chama um método de extensão CasJson que serializa todos os objetos em cadeias de caracteres JSON antes de salvá-los no Couchbase.

público virtual ulong Criar(Modelo T)
{
retorno loja(StoreMode.Adicionar, modelo);
}

público virtual ulong Atualização(Modelo T)
{
retorno loja(StoreMode.Substituir, modelo);
}

público virtual ulong Salvar(Modelo T)
{
retorno loja(StoreMode.Conjunto, modelo);
}

privado ulong loja(Modo StoreMode, modelo T)
{
var result = _Cliente.CasJson(modo, BuildKey(modelo), modelo);
retorno resultado.Resultado ? resultado.Cas : 0;
}

O método de armazenamento privado também usa o método BuildKey protegido para criar chaves por uma convenção. Especificamente, as chaves são compostas a partir da propriedade Type do modelo e de sua propriedade Id, com espaços substituídos por sublinhados. O TapMap não está realmente fazendo uso dessa versão do BuildKey, pois ele é substituído em subclasses que usam Create, Update ou Save. Estou pensando em adicionar uma propriedade "Name" ao ModelBase para permitir a composição de uma chave com base em um tipo e um nome. Por exemplo, uma cerveja com o nome "Old Yankee Ale" recebe a chave "beer_Old_Yankee_Ale".

protegida virtual string BuildKey(Modelo T)
{
retorno string.Concat(modelo.Tipo, “_”, modelo.Id.Substituir(” “, “_”));
}

O método Get usa o _id do documento como argumento quando é chamado. Esse valor é então usado como a chave para chamar o método Get genérico do cliente. Outro método de extensão - GetJson(string id) - também está sendo usado pelo método Get para converter o documento JSON em uma instância de T.

público virtual T Obter(string id)
{
var doc = _Cliente.GetJson<T>(id);
se (doc != nulo) doc.Id = id; //servidor não retorna o _id no JSON
retorno doc;
}

Remove é muito simples. Ele simplesmente pega a chave do documento (novamente, também o _id) e remove o item do Couchbase.

público virtual vazio Remover(string id)
{
_Cliente.Remover(id);
}

O fornecimento de suporte básico para acesso à visualização é feito por convenção no método View. Esse método pressupõe que o nome do documento de design é o nome do tipo de modelo no plural. Portanto, o BeerRepository terá automaticamente suas solicitações de visualização mapeadas para um documento de design chamado "beers". O método View retorna uma instância IView. Como a chamada HTTP não é realmente feita até que um chamador faça uma iteração sobre o IView, as subclasses podem chamar a visualização usando os métodos de consulta fluentes. Em breve, terei um exemplo de uma chamada de visualização específica.

protegida Visualização<IViewRow> Ver(string viewName)
{
retorno _Cliente.Obter visualização(InflectorNet.Pluralizar(tipo de(T).Nome.Para abaixar()), viewName);
}

Atualmente, há também um método SpatialView, que permite consultas geoespaciais usando o GeoCouch. O método GetSpatialView do cliente é atualmente experimental e não foi adicionado à ramificação principal 1.2 do cliente. No entanto, ele estará disponível em breve. Se você clonar o repositório TapMap, obterá um binário de cliente com suporte para esse método.

protegida Visualização<ISpatialViewRow> SpatialView(string viewName)
{
retorno _Cliente.GetSpatialView(InflectorNet.Pluralizar(tipo de(T).Nome.Para abaixar()), viewName);
}

Isso abrange o RepositoryBase. Agora que já vimos seus métodos e sua finalidade, vamos nos aprofundar em uma subclasse. Primeiro, a classe de modelo User define propriedades para Nome de usuário, E-mail e Senha. Ela também fornece mapeamentos de propriedades JSON para versões em letras minúsculas das propriedades da classe. Observe também que User estende ModelBase e implementa a propriedade Type retornando a string "user".

[Serializável]
público classe Usuário : ModelBase
{
[Necessário]
[JsonProperty("nome de usuário")]
público string Nome de usuário { obter; definir; }

[Necessário]
[JsonProperty("email")]
público string E-mail { obter; definir; }

[Necessário]
[JsonProperty("senha")]
público string Senha { obter; definir; }

público anular string Tipo
{
obter { retorno "usuário"; }
}
}

A classe UserRepository estende RepositoryBase com T sendo definido como tipo User.

público classe Repositório de usuários : RepositórioBase<Usuário>
{
//métodos
}

Com a definição simples acima, o UserRepository tem bastante poder. Ele pode executar toda a funcionalidade CRUD básica definida em sua classe base. No entanto, há alguns cenários especiais para trabalhar com documentos do usuário no aplicativo, e o UserRepository adiciona métodos para dar suporte a esses cenários.

Quando um usuário faz login com um nome de usuário e uma senha, uma opção seria criar uma exibição que emite uma chave composta de nome de usuário e senha. No entanto, uma alternativa melhor é usar o nome de usuário e a senha para compor uma chave para o documento. Essa chave nos permitirá recuperar um documento de usuário facilmente quando o usuário fizer login. Essa abordagem economiza espaço por não precisar criar o índice de exibição secundária e permite uma chave mais rápida usando a pesquisa de hashtable.

No método substituído Create, o nome de usuário (Id) e a senha (hash) são usados para criar uma chave do formulário user_Username_HashedPassword.

público anular ulong Criar(Modelo de usuário)
{
modelo.Senha = HashHelper.ToHashedString(modelo.Senha);
modelo.Id = BuildKey(modelo);
var result = _Cliente.CasJson(StoreMode.Adicionar, BuildKey(modelo), modelo);
retorno resultado.Resultado ? resultado.Cas : 0;
}

BuildKey também é substituído para permitir o novo formato de chave. O método privado buildKey é necessário para o método Get sobrecarregado que veremos em breve.

protegida anular string BuildKey(Usuário usuário)
{
retorno buildKey(usuário.Nome de usuário, usuário.Senha);
}

privado string buildKey(string nome de usuário, string senha)
{
retorno string.Concat("user_", nome de usuário, “_”, senha);
}

O método Get, conforme definido em RepositoryBase, pressupõe uma pesquisa por chave. Em vez de expor o formato especial da chave do usuário à classe UserController que o chama, Get é sobrecarregado para aceitar um nome de usuário e uma senha a partir dos quais a nova chave é composta.

público Obtenção do usuário(string nome de usuário, string senha)
{
retorno Obter(buildKey(nome de usuário, HashHelper.ToHashedString(senha)));
}

A classe UserRepository também é responsável por ajudar a responder às perguntas sobre se um nome de usuário ou e-mail é exclusivo. Para isso, são usadas duas exibições. A primeira simplesmente emite o e-mail como chave para todos os documentos do usuário; a segunda emite o nome de usuário como chave. Há maneiras mais inteligentes de criar esses índices, mas, para simplificar, há um mapeamento de um para um entre a propriedade a ser verificada e sua exibição.

função (doc) {
se (doc.tipo == "usuário") {
emitir(doc.e-mail, nulo);
}
}

função (doc) {
se (doc.tipo == "usuário") {
emitir(doc.nome de usuário, nulo);
}
}

O UserRepository define os métodos GetByEmail e GetByUsername, que usam o método View básico para consultar suas respectivas exibições. Em ambos os métodos, o limite é definido como 1 e há um retorno rígido na primeira iteração, pois só nos interessa que exista um único item com esse valor de propriedade. Exibições desatualizadas não são permitidas, pois estamos tentando impor a exclusividade dos dados e precisamos ter certeza de que temos o índice de exibição mais atualizado possível.

público Usuário GetByEmail(string e-mail)
{
antes de (var item em Ver("by_email").Limite(1).Chave(e-mail).Vencido(StaleMode.Falso))
{
retorno Obter(item.ItemId);
}
retorno nulo;
}

público Usuário GetByUsername(string nome de usuário)
{
antes de (var item em Ver("by_username").Limite(1).Chave(nome de usuário).Vencido(StaleMode.Falso))
{
retorno Obter(item.ItemId);
}
retorno nulo;
}

Explorarei o UserController em outro momento para mostrar como tudo isso se encaixa. Meu objetivo com esta postagem foi apresentar o RepositoryBase e como ele é estendido.

Autor

Postado por John Zablocki, NET. SDK, Couchbase

John Zablocki é um desenvolvedor NET. SDK Developer na Couchbase. John também é o organizador do Beantown ALT.NET e ex-adjunto da Fairfield University. Você também pode conferir o livro na Amazon chamado "Couchbase Essentials", que explica como instalar e configurar o Couchbase Server.

Deixar uma resposta