Sin categoría

TapMap Parte I: Trabajando con un Repositorio de T con Couchbase Server 2.0 y la Librería Cliente .NET

He estado pensando en crear una aplicación de referencia para usar .NET y Couchbase Server. Mientras viajaba a Londres para la conferencia Progressive NoSQL en Las competencias importanPasé las horas de espera a que mi habitación de hotel estuviera lista escribiendo algo de código en un Startbucks para una aplicación que llamo TapMap.

La aplicación es bastante sencilla: se trata de una imitación de Twitter centrada en las cervezas. La idea básica es que alguien en un bar informe de que ha encontrado (y probablemente bebido) una cerveza concreta en un lugar determinado. Ese informe se llama "grifo". La función "mapa" muestra una serie de grifos en un mapa de Nokia, utilizando GeoCouch. El valor es que los usuarios pueden aprender rápidamente dónde encontrar sus cervezas favoritas.

El código es todavía muy alfa y mis habilidades HTML necesitan ser mejoradas. Pero he implementado lo suficiente como para empezar una serie de posts sobre los patrones de código y las soluciones expuestas por TapMap. Este post será la primera parte y describirá como crear un simple Repositorio de T para Couchbase usando la librería .NET Client. Puedes obtener el código en http://github.com/couchbaselabs/TapMap.

Si no estás familiarizado con los repositorios, la idea básica es que tienes una clase que es responsable del acceso a los datos de un objeto de dominio. Así, para buscar un usuario por su dirección de correo electrónico, utilizarías el método GetByEmail(string email) del UserRepository. Los repositorios también suelen contener métodos CRUD estándar. Hay un poco más en el patrón, pero voy a demostrar su uso básico. Más información en http://msdn.microsoft.com/en-us/library/ff649690.aspx.

La herencia es una forma natural de conseguir la reutilización del código. TapMap utiliza una clase base de repositorio para proporcionar métodos CRUD a sus subclases. Además, la clase base hace uso de genéricos para que los métodos CRUD puedan devolver instancias de T en lugar de sólo objetos. Restringir T a un tipo particular también permite algunos beneficios adicionales que discutiré en breve.

público abstracto clase RepositoryBase<T> donde T : BaseModelo
{
//métodos basura aquí
}

Como ya sabrás, Couchbase Server 2.0 no impone un esquema a sus documentos. Almacena un documento JSON con las propiedades que desees incluir. Dicho esto, todos los documentos se almacenan con una propiedad reservada "_id" que deriva su valor de su clave.

Añadir un documento de la siguiente manera:

cliente.Tienda(StoreMode.Añadir, "cerveza_tres_filósofos", “{ Nombre : Tres filósofos }”);

El resultado es un documento como el siguiente:

{
"_id" : "cerveza_tres_filósofos",
"nombre" : "Tres Filósofos"
}

Así que para asegurarnos de que todos nuestros objetos modelo tienen esta propiedad ID crearemos una clase base modelo que defina una.

[Serializable]
público abstracto clase BaseModelo
{
[JsonProperty(NombrePropiedad = "_id")]
público cadena Id { consiga; configure; }
}

No voy a cavar en él JsonProperty piezas aquí. Estoy usando Json.NET para la serialización y escribí más sobre ello en http://www.couchbase.com/develop/net/next. Lo importante a tener en cuenta es que tenemos objetos modelo con una propiedad "Id" y al restringir nuestros repositorios para utilizar subclases de ModelBase, sabemos que siempre tendremos esta propiedad.

A la hora de crear vistas, es útil disponer de una taxonomía en los documentos para que sea fácil encontrar todos los documentos "cerveza" o todos los documentos "usuario" o incluso encontrar un documento "usuario" con una dirección de correo electrónico determinada. Para facilitar este requisito, impondremos una propiedad "tipo" a todos los documentos del modelo. A nuestra ModelBase, añadiremos una propiedad Type abstracta y de sólo lectura.

[JsonProperty("tipo")]
público abstracto cadena Tipo { consiga; }

Ahora que entendemos las suposiciones que podemos hacer de nuestro modelo, volvamos a nuestra clase RespositoryBase.

Lo primero que nuestra clase RepositoryBase necesita hacer es definir una instancia de CouchbaseCliet.

protegido estático sólo lectura CouchbaseClient _Cliente = null;

estático RepositoryBase()
{
Cliente = nuevo CouchbaseClient();
}

Dado que la configuración del cliente es costosa, vamos a asegurarnos de que sólo ocurra una vez por AppDomain creando una instancia estática de sólo lectura que se instancie en un constructor estático. El cliente es expuesto como un campo protegido a subclases que necesitan extender los métodos CRUD básicos en nuestra clase base. Nótese que este cliente depende de que el Web.config haya definido la sección "couchbase". En algún momento, añadiré una versión IoC amigable de nuestro RepositoryBase.

La base del repositorio define los métodos Crear, Actualizar y Guardar. Estos métodos a su vez llaman al método privado "store" con el valor apropiado de StoreMode. Crear y Actualizar son sencillos. Guardar simplemente crea un documento cuando la clave no existe y lo reemplaza cuando existe. Los valores de retorno son el valor CAS de la operación. En algún momento, añadiré CAS como parámetro opcional. El método store también llama a un método de extensión CasJson que serializa todos los objetos a cadenas JSON antes de guardarlos en Couchbase.

público virtual ulong Cree(Modelo T)
{
devolver tienda(StoreMode.Añadir, modelo);
}

público virtual ulong Actualización(Modelo T)
{
devolver tienda(StoreMode.Sustituir, modelo);
}

público virtual ulong Guardar(Modelo T)
{
devolver tienda(StoreMode.Establecer, modelo);
}

privado ulong tienda(Modo StoreMode, modelo T)
{
var resultado = Cliente.CasJson(modo, BuildKey(modelo), modelo);
devolver resultado.Resultado ? resultado.Cas : 0;
}

El método privado store también hace uso del método protegido BuildKey para crear claves mediante una convención. Específicamente, las claves se componen a partir de la propiedad Type del modelo y su propiedad Id, con espacios reemplazados por guiones bajos. TapMap en realidad no hace uso de esta versión de BuildKey ya que se sobrescribe en las subclases que utilizan Create, Update o Save. Estoy considerando añadir una propiedad "Name" al ModelBase para permitir componer una clave basada en un tipo y un nombre. Por ejemplo, una cerveza con nombre "Old Yankee Ale" obtiene una clave "beer_Old_Yankee_Ale".

protegido virtual cadena BuildKey(Modelo T)
{
devolver cadena.Concat(modelo.Tipo, “_”, modelo.Id.Sustituir(” “, “_”));
}

El método Get toma el _id del documento como argumento cuando es llamado. Ese valor se utiliza como clave para llamar al método Get genérico del cliente. Otro método de extensión - GetJson(string id) - también está siendo utilizado por el método Get para convertir el documento JSON en una instancia de T.

público virtual T Obtener(cadena id)
{
var doc = Cliente.GetJson<T>(id);
si (doc != null) doc.Id = id; //servidor no devuelve el _id en el JSON
devolver doc;
}

Remove es muy sencillo. Simplemente toma la clave del documento (de nuevo, también el _id) y elimina el elemento de Couchbase.

público virtual void Eliminar(cadena id)
{
Cliente.Eliminar(id);
}

Proporcionar soporte base para el acceso a las vistas se realiza por convención en el método View. Este método asume que el nombre del documento de diseño es el nombre del tipo de modelo pluralizado. Así BeerRepository tendrá automáticamente sus peticiones de vista mapeadas a un documento de diseño llamado "cervezas". El método View devuelve una instancia IView. Dado que la llamada HTTP no se realiza hasta que un llamador itera sobre el IView, las subclases pueden llamar a la vista utilizando los métodos de consulta fluidos. Tendré un ejemplo de una llamada específica a la vista en breve.

protegido Ver<IViewRow> Ver(cadena viewName)
{
devolver Cliente.GetView(InflectorNet.Pluralizar(tipode(T).Nombre.ToLower()), viewName);
}

También existe actualmente un método SpatialView, que permite realizar consultas geoespaciales utilizando GeoCouch. El método GetSpatialView del cliente es actualmente experimental y no ha sido añadido a la rama maestra 1.2 del cliente. Sin embargo, se añadirá próximamente. Si clona el repositorio TapMap, obtendrá un binario del cliente con soporte para este método.

protegido Ver<ISpatialViewRow> SpatialView(cadena viewName)
{
devolver Cliente.GetSpatialView(InflectorNet.Pluralizar(tipode(T).Nombre.ToLower()), viewName);
}

Eso cubre RepositoryBase. Así que ahora que hemos visto sus métodos y su propósito, vamos a profundizar en una subclase. Primero, la clase modelo User define propiedades para Username, Email y Password. También proporciona mapeos de propiedades JSON a versiones en minúsculas de las propiedades de la clase. Observa también que User extiende ModelBase e implementa la propiedad Type devolviendo la cadena "user".

[Serializable]
público clase Usuario : BaseModelo
{
[Requerido]
[JsonProperty("nombre de usuario")]
público cadena Nombre de usuario { consiga; configure; }

[Requerido]
[JsonProperty("email")]
público cadena Correo electrónico { consiga; configure; }

[Requerido]
[JsonProperty("contraseña")]
público cadena Contraseña { consiga; configure; }

público anular cadena Tipo
{
consiga { devolver "usuario"; }
}
}

La clase UserRepository extiende RepositoryBase con T de tipo User.

público clase UsuarioRepositorio : RepositoryBase<Usuario>
{
//métodos
}

Con la simple definición anterior, el UserRepository tiene bastante poder. Puede realizar toda la funcionalidad CRUD básica definida en su clase base. Sin embargo, hay algunos escenarios especiales para trabajar con documentos de Usuario en la aplicación y el UserRepository añade métodos para soportar estos escenarios.

Cuando un usuario se registra con un nombre de usuario y una contraseña, una opción sería crear una vista que emita una clave compuesta de nombre de usuario y contraseña. Sin embargo, una alternativa mejor es utilizar el nombre de usuario y la contraseña para componer una clave para el documento. Dicha clave nos permitirá recuperar fácilmente un documento de usuario cuando éste se conecte. Este enfoque ahorra espacio al no tener que crear el índice secundario de la vista y permite una clave más rápida al utilizar la búsqueda hashtable.

En el método anulado Create, el nombre de usuario (Id) y la contraseña (hash) se utilizan para crear una clave de la forma user_Username_HashedPassword.

público anular ulong Cree(Modelo de usuario)
{
modelo.Contraseña = HashHelper.ToHashedString(modelo.Contraseña);
modelo.Id = BuildKey(modelo);
var resultado = Cliente.CasJson(StoreMode.AñadirBuildKey(modelo), modelo);
devolver resultado.Resultado ? resultado.Cas : 0;
}

BuildKey también está sobrecargado para permitir el nuevo formato de clave. El método privado buildKey es necesario para el método Get sobrecargado que veremos en breve.

protegido anular cadena BuildKey(Usuario)
{
devolver buildKey(usuario.Nombre de usuariousuario.Contraseña);
}

privado cadena buildKey(cadena nombre de usuario, cadena contraseña)
{
devolver cadena.Concat("usuario_"nombre de usuario, “_”Contraseña);
}

El método Get definido en RepositoryBase asume una búsqueda por clave. En lugar de exponer el formato especial de la clave de usuario a la clase UserController que llama, Get está sobrecargado para aceptar un nombre de usuario y una contraseña a partir de los cuales se compone la nueva clave.

público Obtener usuario(cadena nombre de usuario, cadena contraseña)
{
devolver Visite(buildKey(nombre de usuario, HashHelper.ToHashedString(contraseña)));
}

La clase UserRepository también es responsable de ayudar a responder a las preguntas de si un nombre de usuario o un correo electrónico son únicos. Para ello, se utilizan dos vistas. La primera simplemente emite el email como clave para todos los documentos de usuario; la segunda emite el nombre de usuario como clave. Hay formas más ingeniosas de crear estos índices, pero para simplificar las cosas, hay un mapeo uno a uno entre la propiedad a comprobar y su vista.

función (doc) {
si (doc.tipo == "usuario") {
emite(doc.correo electrónico, null);
}
}

función (doc) {
si (doc.tipo == "usuario") {
emite(doc.usrename, null);
}
}

El UserRepository define los métodos GetByEmail y GetByUsername, que utilizan el método base View para consultar sus respectivas vistas. En ambos métodos, el límite se establece en 1 y hay un retorno duro en la primera iteración, ya que sólo nos importa que exista un único elemento con este valor de propiedad. No se permiten vistas obsoletas, ya que estamos tratando de hacer cumplir la unicidad de los datos y tenemos que asegurarnos de que tenemos el índice de vista más actualizado posible.

público Usuario GetByEmail(cadena correo electrónico)
{
foreach (var artículo en Ver("by_email").Límite(1).Clave(correo electrónico).Rancio(StaleMode.Falso))
{
devolver Visite(artículo.ItemId);
}
devolver null;
}

público Usuario GetByUsername(cadena nombre de usuario)
{
foreach (var artículo en Ver("por_nombre_de_usuario").Límite(1).Clave(nombre de usuario).Rancio(StaleMode.Falso))
{
devolver Visite(artículo.ItemId);
}
devolver null;
}

Exploraré el UserController en otro momento para mostrar cómo encaja todo. Mi objetivo con este post era introducir el RepositoryBase y cómo se extiende.

Comparte este artículo
Recibe actualizaciones del blog de Couchbase en tu bandeja de entrada
Este campo es obligatorio.

Autor

Publicado por John Zablocki, Desarrollador NET. Desarrollador SDK, Couchbase

John Zablocki es desarrollador de NET. SDK en Couchbase. John es también el organizador de Beantown ALT.NET y antiguo profesor adjunto en la Universidad de Fairfield. También puedes consultar el libro en Amazon llamado "Couchbase Essentials" que explica cómo instalar y configurar Couchbase Server.

Deja un comentario

¿Listo para empezar con Couchbase Capella?

Empezar a construir

Consulte nuestro portal para desarrolladores para explorar NoSQL, buscar recursos y empezar con tutoriales.

Utilizar Capella gratis

Ponte manos a la obra con Couchbase en unos pocos clics. Capella DBaaS es la forma más fácil y rápida de empezar.

Póngase en contacto

¿Quieres saber más sobre las ofertas de Couchbase? Permítanos ayudarle.