El proyecto Re-ofertas trata de un portal de anuncios, está pensado para la publicación de anuncios sobre objetos de todo tipo, principalmente de segunda mano, aunque también se podrán publicar anuncios de objetos nuevos o servicios. El portal Re-ofertas tiene como objetivo, que el usuario que acceda a la página web pueda visualizar los anuncios allí existentes, así, como publicar sus propios anuncios. Intentando que el usuario encuentre los productos que busca y pueda publicar los que desea vender.
Los objetivos que tiene esta aplicación son: que el usuario pueda publicar y conseguir vender con la mayor facilidad posible, así, como buscar entre los distintos anuncios y encontrar lo que desea con la máxima facilidad, comodidad y seguridad posible. Todas estas acciones se pueden realizar a través de la página web, destinado a usuarios de ordenador. Pero para usuarios de móviles con sistema operativo Android, hay una pequeña aplicación como complemento, para poder visualizar estos anuncios y por tanto poder contactar con el vendedor, así como guardarse una lista de sus favoritos para poder seleccionar o tener los anuncios que desee en su selección.
ChatGPT en el Desarrollo de Software
En esta ocasión voy a plantear el uso de ChatGPT, la principal herramienta de inteligencia artificial de uso común en el 2023-2024. Esta herramienta está cambiando la forma en que trabajamos y quiero compartir con cómo pueden beneficiarse de ella, para mejorar la productividad y la eficiencia de los desarrolladores.
Chat GPT: ¿Qué es?
Es un modelo de lenguaje basado en inteligencia artificial, desarrollador por OpenAI, basado en arquitectura GPT (Generative Pretrained Transformer). Su objetivo principal es generar texto coherente y relevante en respuesta a las entradas de los usuarios, simulando una conversación humana.
Características de ChatGPT:
- Entrenamiento Previo: El modelo se entrena utilizando grandes volúmenes de texto disponibles en Internet. Durante este entrenamiento, el modelo aprende a predecir la próxima palabra en una secuencia dada, lo que le permite construir oraciones y párrafos completos.
- Amplia Base de Conocimiento: ChatGPT ha sido entrenado con una vasta cantidad de información que abarca numerosos temas. Aunque anteriormente solo utilizaba este conocimiento para proporcionar sus respuestas, ahora dispone de acceso a internet. Esto brinda a los usuarios la posibilidad de acceder a datos frescos y enlaces directos a las fuentes, lo que antes resultaba inalcanzable.
- Atención y contexto: Utiliza una arquitectura de modelo llamada Transformer, diseñada para entender el contexto de un texto de manera profunda. Esto le permite generar respuestas coherentes y relevantes a la conversación. La arquitectura de Transformer se basa en mecanismos de atención que permiten al modelo ponderar la importancia de diferentes palabras en el texto de entrada. Esto ayuda a entender el contexto y generar respuestas más precisas.
Aplicaciones de ChatGPT en el Desarrollo de Software
Generación de Código: mediante el lenguaje natural podemos transformar este en fragmentos de código funcionales. Por ejemplo, un desarrollador puede describir una función cómo si estuviera describiendo su funcionamiento y ChatGPT generará el código correspondiente en Python, JavaScript, u otro lenguaje de programación. Esto no solo ahorra tiempo, sino que también ayuda a los desarrolladores a superar bloqueos creativos y a enfocarse en tareas más complejas.
Depuración de Código: Detectar, Identificar y corregir errores en el código aveces se puede convertir en una tarea ardua. ChatGPT puede analizar el código y sugerir correcciones, ahorrando tiempo y reduciendo el margen de error humano. Por ejemplo, al presentar un error específico, ChatGPT puede ofrecer una solución detallada, explicando los cambios necesarios para resolver el problema.
Documentación: Escribir documentación técnica y comentarios en el código puede ser tedioso. ChatGPT puede generar documentación detallada basada en el código y sus funciones, asegurando que la documentación esté siempre actualizada y sea precisa. Esto es especialmente útil en proyectos grandes, donde mantener una documentación coherente puede ser un desafío.
Ventajas y Desventajas
Ventajas:
- Aumento de la productividad: ChatGPT acelera el proceso de desarrollo, permitiendo a los desarrolladores centrarse en tareas más complejas y creativas.
- Reducción de errores: ChatGPT puede identificar errores y sugerir correcciones, mejorando la calidad del código.
- Aceleración del desarrollo: La generación automática de código y documentación agiliza significativamente el desarrollo de software.
Desventajas:
- Dependencia excesiva: Confiar demasiado en la IA puede llevar a una pérdida de habilidades técnicas fundamentales.
- Errores en el código sugerido: Aunque ChatGPT es potente, no es infalible y puede sugerir código incorrecto.
- Consideraciones éticas: El uso de IA plantea cuestiones sobre la propiedad del código y la privacidad de los datos.
Conclusión
La llegada de ChatGPT está transformando industrias, haciendo que está sean cada vez más productivas y eficaces, en el desarrollo del software como es normal esto se ve reflejado todavía más, por la naturaleza de la actividad proporcionando beneficios significativos en términos de productividad y calidad del código. Por esta razón y lo descrito en el artículo, animamos a los desarrolladores a probar esta herramienta y integrarla en su conjunto de herramientas de uso corriente.
Novedades sobre C# 13 & .NET 9
C# 13
La nueva versión de C# 13 salida recientemente, que estará disponible en .NET 9, disponible en la versión más actualizada de Visual studio 2022. Esta versión incluirá una serie de características que estarán disponibles en la página de novedades de #13 (Novedades de C#), por el momento están en versión preliminar y no disponibles oficialmente.
Novedades importantes publicadas actualmente.
Atributo InlineArray en estructuras record no estará disponible.
El atributo InlineArray en C# es una característica utilizada para optimizar el almacenamiento de matrices (arrays) pequeñas en la pila en lugar de en el heap, mejorando así el rendimiento. Este atributo permite definir un campo como un array in-line, es decir, que se almacena directamente dentro de la estructura en lugar de ser una referencia a una ubicación separada en el heap. La combinación de InlineArray con los registros de estructuras nos permite obtener los beneficios de ambas en conjunto.
En esta versión pasará a retirarse esta estructura:

Mostrará la siguiente alerta:
Error CS9259: Attribute ‘System.Runtime.CompilerServices.InlineArray’ cannot be applied to a record struct.
Los iteradores introducen un contexto seguro en C# 13 y versiones posteriores. Lo que significa que el comportamiento predeterminado será como si estuviera en un contexto seguro, permitiéndonos en caso de no desearlo indicarlo explícitamente que no deseamos este comportamiento.

Otras características anunciadas
Nueva secuencia de escape: puede usar \e como una secuencia de escape literal de caracteres para el carácter de ESCAPE (U+001B en unicode). Anteriormente, se usaba \u001b o \x1b.
Tipos naturales de grupo de métodos: Esta característica realiza pequeñas optimizaciones para solucionar sobrecargas con grupos de métodos. Lo que proporciona:
- Mejor Eficiencia: Al reducir el conjunto de métodos candidatos a considerar, mejora el rendimiento del compilador y reduce el tiempo de compilación.
- Claridad y Mantenimiento: Proceso de resolución más claro y fácil de entender, facilitando la depuración y el mantenimiento del código.
- Predictibilidad y Consistencia: Resultados de la resolución de sobrecargas más consistentes y predecibles, aumentando la confiabilidad del compilador.
Acceso a índices implícitos:
En C# 13, se introduce la capacidad de usar el operador de índice implícito “from the end” (^) en inicializadores de objetos. Esto permite acceder a los elementos de una colección o matriz desde el final en lugar de desde el principio, lo cual no era posible en versiones anteriores.

Esta característica es especialmente útil cuando se trabaja con colecciones donde solo se necesitan modificar los últimos elementos, proporcionando una sintaxis más directa y menos propensa a errores.

Características que podríamos esperar(No todas anunciadas oficialmente):
Escape character
- Mejora en el manejo de caracteres de escape.
Method group natural type improvements
- Mejora en la tipificación natural de grupos de métodos.
Lock object
- Mejoras en el manejo de objetos de bloqueo (lock).
Implicit indexer access in object initializers
- Acceso implícito a indexadores en inicializadores de objetos.
Params-collections
- Soporte para colecciones en parámetros (params).
Ref/unsafe in iterators/async
- Permite código no seguro (unsafe) y referencias (ref) en iteradores y métodos asincrónicos.
Allows ref struct constraint
- Permite restricciones de ref struct en interfaces.
Overload Resolution Priority
- Mejoras en la prioridad de resolución de sobrecargas.
Partial properties
- Soporte para propiedades parciales.
.NET 9
La nueva versión de .NET 9, sucede a la versión .NET8, con un enfoque basado en el rendimiento y las aplicaciones nativas en la nube.
Podemos ver que para la versión preview 2, se ven algunos cambios el las bibliotecas principales .NET.
Estos serán los siguientes puntos mas relevantes que se verán actualizados::
- Serialización
- LINQ
- Colecciones
- Criptografía
- Reflexión
- Rendimiento
- Pruebas unitarias
Serialización
La biblioteca System.Text.Json incorpora nuevas opciones y un nuevo singleton que facilita la serialización.
Opciones de sangría
JsonSerializerOptions permiten personalizar el carácter y el tamaño de sangría del json.

Opciones web predeterminadas
El nuevo singleton JsonSerializerOptions.Web permite serializar de la forma que lo hacer ASP.NET Core para aplicaciones web.
LINQ
Se han incluido dos nuevos métodos:
- CountBy: que permite calcular la frecuencia de cada clave.

- AggregateBy: permite agrupar datos basados en una clave específica y aplicar una operación de agregación (como una suma) sobre los valores asociados a esa clave.
Ejemplo:

Colecciones
En las colecciones PriorityQueue<TElement,TPriority>, incluye un nuevo método Remove(TElement, TElement, TPriority, IEqualityComparer<TElement>) , que puede usarse para modificar la prioridad de un elemento de la cola.
// Scan the heap for entries matching the current element.
queue.Remove(element, out _, out _);
// Re-insert the entry with the new priority.
queue.Enqueue(element, priority);
Eliminación del Elemento:
- queue.Remove(element, out _, out _) busca y elimina el elemento especificado de la cola de prioridad. Los parámetros out permiten capturar el elemento eliminado y su prioridad anterior, pero en este caso no los usamos (out _, out _).
Reinserción del Elemento con Nueva Prioridad:
- queue.Enqueue(element, priority) inserta el elemento nuevamente en la cola de prioridad, pero con la nueva prioridad especificada.
Criptografía
- CryptographicOperations.HashData(): la API CryptographicOperations.HashData. introducido en .NET 9, permite generar un hash o HMAC a través de una entrada como una captura en la que un algoritmo utilizado viene determinado por HashAlgorithmName. en lugar de tener que llamar a la clase SHA256.HashData o HMACSHA256.HashData dependiendo del tipo de algoritmo que queremos.
- Algoritmo KMAC: .NET 9 proporciona el algoritmo KMAC especificado por NIST SP-800-185. El código de autenticación de mensajes (KMAC) de KECCAK es una función pseudoaleatoria y una función hash con clave basada en KECCAK.KMAC está disponible en Linux con OpenSSL 3.0 o posterior, y en la compilación 26016 o posterior de Windows 11. Puede usar la propiedad estática IsSupported para determinar si la plataforma admite el algoritmo deseado.
Reflexión
En .NET 9, se introduce la capacidad de guardar ensamblados dinámicamente creados utilizando la nueva API AssemblyBuilder.DefinePersistedAssembly. Esto soluciona una limitación en versiones anteriores de .NET Core y .NET 5-8, donde los ensamblados creados dinámicamente no se podían guardar.
Características Clave:
1. Creación de Ensamblado Persistente:
- API Nueva: AssemblyBuilder.DefinePersistedAssembly.
- Independiente del Entorno de Ejecución: Funciona en cualquier plataforma soportada por .NET 9.
- Sólo Guardar: La implementación actual solo admite la funcionalidad de guardar, no ejecutar.
2.Compatibilidad con Código Existente:
- Los pasos para definir módulos, tipos, métodos, escribir lenguaje intermedio (IL) y otros usos de System.Reflection.Emit permanecen sin cambios.
- Esto significa que el código existente que utiliza System.Reflection.Emit puede seguir siendo utilizado para crear y guardar ensamblados.
3.Uso del Ensamblado Guardado:
- Una vez guardado, el ensamblado puede ser cargado y utilizado como cualquier otro ensamblado .NET.

Rendimiento
La nueva versión de :NET, .NET 9 incluye mejoras en el compilador JIT de 64 bits. Estas mejoras del compilador incluyen:
- Mejor generación de código para bucles.
- Más métodos de inserción para AOT nativo.
- Comprobaciones de tipos más rápidas.
Pruebas unitarias
Ejecución de pruebas en paralelo
En .NET 9, dotnet test está totalmente integrado con MSBuild. Dado que MSBuild admite la compilación en paralelo, puede ejecutar pruebas para el mismo proyecto en diferentes marcos de destino en paralelo. De forma predeterminada, MSBuild limita el número de procesos paralelos al número de procesadores del equipo. También puede establecer su propio límite mediante el modificador -maxcpucount. Si desea no participar en el paralelismo, establezca la propiedad TestTfmsInParallel de MSBuild en false.
Pantalla de prueba del registrador de terminal
Los informes de resultados de pruebas para dotnet test ahora se admiten directamente en el registrador de terminales de MSBuild. Obtendrá informes de pruebas más completos tanto mientras se ejecutan pruebas (muestra el nombre de la prueba en ejecución) y después de que se completen las pruebas (los errores de prueba se representan de una manera mejor).
Conclusión
C# 13 y .NET 9 traen consigo una serie de mejoras y nuevas características que prometen mejorar la eficiencia, la claridad y la funcionalidad de los desarrolladores. Con la introducción de innovaciones como la capacidad de usar el operador de índice implícito “from the end” en inicializadores de objetos, la optimización de la resolución de sobrecargas y la incorporación de nuevas secuencias de escape, C# 13 facilita un desarrollo más intuitivo y menos propenso a errores.
Por otro lado, .NET 9 no se queda atrás, ofreciendo mejoras significativas en áreas críticas como la serialización, LINQ, colecciones, criptografía, reflexión y rendimiento. La nueva API `CryptographicOperations.HashData` simplifica la generación de hashes y HMACs, mientras que las mejoras en la biblioteca `System.Text.Json` y en el compilador JIT de 64 bits demuestran un claro enfoque en el rendimiento y la eficiencia.
Además, la integración completa de `dotnet test` con MSBuild para ejecutar pruebas en paralelo, junto con un mejor soporte para los informes de resultados de pruebas en el registrador de terminales, subraya el compromiso de .NET 9 con la mejora continua de las herramientas de desarrollo y pruebas.
En resumen, estas actualizaciones no solo mejoran la experiencia de desarrollo, sino que también preparan a los desarrolladores para enfrentar los desafíos modernos con herramientas más robustas y flexibles. Con C# 13 y .NET 9, Microsoft sigue demostrando su dedicación a la innovación y la excelencia en el desarrollo de software.
Referencias
1. Novedades de .NET 9, https://learn.microsoft.com/es-es/dotnet/core/whats-new/dotnet-9/overview, accedido el 30 de julio de 2024.
2. Novedades de C# 13, https://learn.microsoft.com/es-es/dotnet/csharp/whats-new/csharp-13, 20 julio 2024.
3. .NET 9 Preview 2 – Release Notes, https://github.com/dotnet/core/tree/main/release-notes/9.0/preview/preview2, 20 julio 2024.
4. This document lists known breaking changes in Roslyn after .NET 8 all the way to .NET 9, https://learn.microsoft.com/es-es/dotnet/csharp/whats-new/breaking-changes/compiler%20breaking%20changes%20-%20dotnet%209, 20 julio 2024.
Principios SOLID
Principios SOLID
SOLID(Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion). Establece unos consejos que son usados como base para desarrollar y que generan en lo que ello respecta un código más duradero y robusto, en lo que desarrollo de software respecta en base a patrones y arquitectura que conforma un buen código. En definitiva, desarrollar un software de calidad.
Los 5 principios SOLID de diseño de aplicaciones de software son:
- S – Single Responsibility Principle (SRP)
- O – Open/Closed Principle (OCP)
- L – Liskov Substitution Principle (LSP)
- I – Interface Segregation Principle (ISP)
- D – Dependency Inversion Principle (DIP)
En este sentido la aplicación de los principios SOLID está muy relacionada con la comprensión y el uso de patrones de diseño, que nos permitirán mantener una alta cohesión y, por tanto, un bajo acoplamiento de software.
¿Qué son la cohesión y el acoplamiento?
Son dos conceptos muy relevantes a la hora de diseñar y desarrollar software. Veamos en qué consisten.
Acoplamiento
El acoplamiento se refiere al grado de interdependencia que tienen dos unidades de software entre sí, entendiendo por unidades de software: clases, subtipos, métodos, módulos, funciones, bibliotecas, etc.
Si dos unidades de software son completamente independientes la una de la otra, decimos que están desacopladas.
Cohesión
La cohesión de software es el grado en que elementos diferentes de un sistema permanecen unidos para alcanzar un mejor resultado que si trabajaran por separado. Se refiere a la forma en que podemos agrupar diversas unidades de software para crear una unidad mayor.
1. Principio de Responsabilidad Única(Single Responsibility Principle (SRP))
Según este principio una clase tendría que tener una y solo una razón para cambiar. A lo que Robert C.Martin identifica como responsabilidad.
2. Principio de Abierto/Cerrado(Open/Closed Principle (OCP))
Debería ser capaz de extender el comportamiento de la clase sin modificarla. Las clases deben ser abiertas para extenderse y cerradas para modificarse.
3. Principio de Sustitución de Liskov(Liskov Substitution Principle (LSP))
Las clases derivadas deben poder sustituirse, por sus clases base. Deberíamos poder usar cualquiera de sus subclases sin interferir en la funcionalidad del programa.
4. Principio de Segregación de la Interfaz(Interface Segregation Principle (ISP))
En el cuarto principio de SOLID, el tío Bob sugiere: “Haz interfaces que sean específicas para un tipo de cliente”, es decir, para una finalidad concreta.
En este sentido, según el Interface Segregation Principle (ISP), es preferible contar con muchas interfaces que definan pocos métodos que tener una interface forzada a implementar muchos métodos a los que no dará uso.
5. Principio de Inversión de Dependencias (Dependency Inversion Principle (DIP))
Llegamos al último principio: “Depende de abstracciones, no de clases concretas”.
Así, Robert C. Martin recomienda:
- Los módulos de alto nivel no deberían depender de módulos de bajo nivel. Ambos deberían depender de abstracciones.
- Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las abstracciones.
Implementación de RabbitMQ
Implementación de RabbitMQ
(Con Docker y .NET)
Selección de la imagen
Utilizaremos la imagen de docker rabbitmq:3-management
El tag 3-management en la imagen rabbitmq:3-management de Docker Hub indica que se trata de la versión 3 de RabbitMQ que incluye el plugin de administración habilitado.
Realizaremos la inclusión de la imagen en docker-compose.

Estableciendo el puerto 5672 donde nos conectaremos y el puerto 15672 por el que accederemos al panel de administración.
Así como estableciendo el resto de configuraciones.
Implementación en código
En este ejemplo llevaremos a cabo una implementación sencilla que nos de como resultado una versión ejecutable sencilla y funcional, que demuestre su funcionamiento.
Llevaremos a cabo la implementación de la clase RabbitMQService que contendrá los métodos:
- Publish: Se encargará de publicar en la cola de RabbitMq
- StartConsumer: Se encargará de leer de la cola los mensajes que se encolen.
Para llevar a cabo la configuración le pasaremos los parámetros de conexión necesarios que serán los que establecimos en el archivo docker-compose, estos serán: HostName, UserName, Password. En nuestro caso estarán el el appsettings.json

Indicando los parámetros mencionados para la conexión y especificando el nombre de la cola no conectaremos a ella, en caso de no existir creando.
Métodos
Publish
Método que publicará nuestro mensaje de texto en la cola.

Aquí podemos indicar el exchange, que en caso de no introducirlo será el default y por lo tanto routingkey será el nombre de la cola a la que nos dirigimos para encolar.
Aquí enviamos nuestro mensaje como array de bytes.
StartConsumer
Este método ejecutará la escucha de una cola

En este método de la cola que indiquemos, desencolará los mensajes que lleguen a esta y convertirá de un arreglo de bytes a texto, finalmente ejecutará el método que le indiquemos como parámetro el cual será un método que recibirá un string y realizará las acciones que considere.
Aplicación
Ejemplo realizado
Simularemos que un endpoint suspende un usuario y este debe registrarlo en un log, este log es manejado por otro microservicio y por lo tanto debe enviar el mensaje a este mediante rabbitmq para que cuando pueda lo reciba y lo procese.
Encolar mensaje
Suponiendo que tenemos un endpoint que desea comunicar la suspensión de un usuario enviando un registro log el cual otro microservicio registra en base al objeto CreateLogDto.

Crearemos el objeto que vamos a enviar como string en formato json. y lo enviaremos utilizando el método anteriormente descrito.
Tratamiento de mensajes

En esta parte tendremos una clase que se ejecutará en segundo plano y al inicio de la aplicación y ejecuta este método ExecuteAsync, que recuperará de la inyección de dependencias de los servicios necesarios y utilizando el método StartConsumer anteriormente descrito, creará la expresión lambda encargada de tratar el mensaje recibido y se lo pasará StartConsumer.
Panel de administración
Finalmente mostraremos el panel de administración donde se podrá ver información sobre los procesos que se están realizando en nuestro servicio de rabbitmq.
En este panel podremos ver información detallada de nuestro servicio de mensajería por colas
Accederemos mediante la siguiente dirección: http://localhost:15672
indicando usuario y contraseña.

En la imagen anterior podemos apreciar que disponemos de una cola, si hacemos click iremos al apartado de queues and Streams.
En la siguiente imagen podremos ver información sobre esta; podemos apreciar que la cola se llama notify y tiene información sobre esta. Esta es la cola que hemos generado por código, también podríamos crearla, así como hacer gestión de esta de forma manual desde este panel de administración.

¿Qué es Test Driven Development (TDD)?
Test Driven Development (TDD), o Desarrollo Guiado por Pruebas, es una metodología o estrategía de trabajo en el campo del desarrollo software, que pone la importancia de escribir pruebas antes de desarrollar el código funcional. La metodología TDD puede considerarse no solo como una técnica de programación, sino como una filosofía de trabajo que guía el proceso de desarrollo con el objetivo de mejorar la calidad del código y proporcionar claridad en los objetivos funcionales del software.
Pilares del TDD
1. Test First: Prueba Primero
En el núcleo de TDD está el concepto de “Test First”. Este enfoque establece que antes de iniciar el desarrollo de una nueva funcionalidad, se deben escribir pruebas automatizadas que expresen los requisitos de esa funcionalidad. Estas pruebas fallarán inicialmente, ya que el código correspondiente aún no existe. Este proceso obliga al desarrollador a pensar en la funcionalidad desde el punto de vista del usuario final, asegurando que el software cumpla con las expectativas y requerimientos especificados antes de que se escriba la lógica que lo hará funcionar.
2. Red, Green, Refactor (RGR): Rojo, Verde, Refactorizar
El ciclo de Red, Green, Refactor es una práctica cíclica que consta de tres etapas principales:
- Rojo: El desarrollo comienza escribiendo una prueba que define una mejora o una nueva funcionalidad. Dado que el código correspondiente aún no existe, la prueba fallará (indicado por el color rojo
en la interfaz de pruebas).
- Verde: Luego, el desarrollador escribe el código mínimo necesario para que la prueba pase a verde, es decir, que la prueba sea exitosa. Esto ayuda a evitar la sobreingeniería y asegura que el sistema cumpla estrictamente con los requisitos establecidos por la prueba.
- Refactorizar: Una vez que la prueba pasa y el código funciona (indicado por el color verde), el siguiente paso es refactorizar. En esta etapa, se mejora el código escrito sin alterar su comportamiento. El objetivo es optimizar la estructura, la legibilidad y, en algunos casos, el rendimiento, mientras se asegura que las pruebas sigan pasando.
Este ciclo se repite a lo largo del proceso de desarrollo. Con cada iteración, el código se vuelve más robusto y limpio, garantizando que se mantenga alineado con los requisitos y que cualquier cambio futuro no rompa las funcionalidades existentes.
Ventajas de TDD
- Mejora la Calidad del Código: Las pruebas continuas descubren errores en etapas tempranas, lo que facilita su corrección y reduce el costo asociado al proceso de depuración.
- Documentación Viva: Las pruebas actúan como documentación del código, explicando qué se espera de él y cómo se debe comportar en diferentes escenarios.
- Diseño Modular y Flexible: Al escribir pruebas primero, los desarrolladores se ven forzados a diseñar módulos desacoplados y con interfaces claras, lo que facilita un
sistema más modular y flexible.
- Confianza en el Código: Al tener un conjunto de pruebas que pasan, los desarrolladores pueden hacer cambios con la confianza de que no introducirán errores sin darse cuenta, ya que las pruebas existentes actuarán como una red de seguridad.
- Desarrollo Orientado a Funcionalidades: TDD obliga a los desarrolladores a concentrarse en las funcionalidades requeridas, evitando la distracción de agregar funcionalidades adicionales que no son esenciales para el proyecto.
- Integración Continua: TDD se complementa bien con la integración continua, ya que las pruebas automatizadas se ejecutan con frecuencia, asegurando que los cambios se integren y validen de manera constante.
Desafíos y Consideraciones
- Curva de Aprendizaje: Implementar TDD requiere un cambio de mentalidad y puede llevar tiempo acostumbrarse a escribir pruebas primero.
- Tiempo de Inicio: Escribir pruebas antes de escribir el código puede parecer que ralentiza el proceso de desarrollo al principio, pero los beneficios a largo plazo en la calidad y mantenibilidad del código generalmente superan este costo inicial.
- Complejidad en Manejo y Mantenimiento: Puede ser desafiante escribir y mantener pruebas para sistemas complejos.
Implementando el patrón Unit of Work en .NET
Introducción
El patrón Unit of work(unidad de trabajo), además de un patrón en desarrollo de software es una estrategia que nos permite gestionar y garantizar que transacciones y coherencia de datos sean de un nivel superior. Este patrón se puede combinar con una arquitectura limpia(clean architecture), dando lugar una aplicación con mayor escalabilidad y mantenibilidad a nivel de aplicaciones.
En este artículo vamos a sumergirnos un poco y explicar con .NET Core y bajo el paraguas de la arquitectura limpia, como podemos llevar a cabo una empleabilidad útil de este patrón de trabajo.
Más allá de la mera implementación, reflexionaremos sobre las múltiples ventajas que se obtienen al implementar la Unidad de Trabajo con la Arquitectura Limpia. Una de las joyas de esta fusión es el desacoplamiento exquisito entre las distintas capas de nuestra aplicación, una característica que resulta ser un salvavidas en la evolución y el mantenimiento del código a lo largo del tiempo.
Comprendiendo el patrón Unit of Work
El principal objetivo del patrón es gestionar las operaciones con la base de datos de forma que dispongamos de un mejor control de las transacciones y encapsular las múltiples operaciones como una simple unidad de trabajo. De la siguiente manera dispondremos de las operaciones relacionadas con el tratado de la base de datos juntas, tratados su ejecución y manejando los errores de una manera compacta.
A continuación ilustraremos cómo se vería de una forma más visual, partiremos de una aplicación básica sin intermediarios a nivel de base de datos, esta forma comprenderemos como y donde entra nuestro patrón.
Aplicación sin repositorio
En este ejemplo, el sistema se divide en dos capas principales.
- Capa de Presentación (Controladores): Esta es la interfaz de usuario o capa de API donde se manejan las solicitudes del usuario.
- Capa de Lógica de Negocio (Servicios): Aquí es donde se definen las reglas y operaciones comerciales.
- Estas dos capas interactúan directamente entre sí.
- La Base de Datos se accede directamente desde la Capa de Lógica de Negocio. Es decir, no hay una capa intermedia que gestione el acceso a la base de datos.
Aplicación con repositorio
En este ejemplo, el sistema se divide en tres capas.
- Capa de Presentación (Controladores): Similar al patrón anterior, es la interfaz de usuario o capa de API.
- Capa de Lógica de Negocio (Servicios): Define las reglas y operaciones comerciales.
- Capa de Acceso a Datos (Repositorios): Esta capa actúa como intermediario entre la lógica de negocio y la base de datos, abstrayendo las operaciones directas en la base de datos.
- La Capa de Presentación interactúa con la Capa de Lógica de Negocio, que a su vez interactúa con la Capa de Acceso a Datos para realizar operaciones en la Base de Datos.
Con Repositorio y Unidad de Trabajo:
Este patrón es similar al “Con Repositorio”, pero introduce un concepto adicional, la Unidad de Trabajo.
- La Unidad de Trabajo administra transacciones y agrupa múltiples operaciones de repositorio en una sola unidad lógica.
- El Repositorio en este patrón opera dentro del contexto de la Unidad de Trabajo.
- La Capa de Lógica de Negocio interactúa con la Unidad de Trabajo, que administra uno o varios Repositorios que acceden a la Base de Datos.
Implementación en aplicación .NET
Integración del Patrón de Unidad de Trabajo en Arquitectura Limpia
Para adaptar el patrón de Unidad de Trabajo en una aplicación .NET Core en el marco de la Arquitectura Limpia, sigue las siguientes recomendaciones:
1. Establecimiento de Interfaces Clave
– Diseña una interfaz denominada IUnitOfWork. Esta será responsable de establecer el acuerdo para la Unidad de Trabajo, incorporando funciones esenciales como Commit() y Rollback().
2. Desarrollo de la Unidad de Trabajo
– Elabora una versión específica de la interfaz IUnitOfWork. Esta versión se encargará de definir los bordes transaccionales y de coordinar las acciones con la base de datos. Es esencial que esta implementación ofrezca métodos para iniciar una transacción, validar cambios y, si es necesario, revertir una transacción.
Sincronización con Repositorios
– Adapta tus repositorios para que operen en conjunto con la Unidad de Trabajo. Asegúrate de que cada función del repositorio opere dentro de la transacción establecida por la Unidad de Trabajo, garantizando que las modificaciones hechas se validen o anulen de manera colectiva.
4. Integración en la Capa de Aplicación
– En el nivel de aplicación, incorpora la interfaz de Unidad de Trabajo en los controladores de comandos o consultas que demanden operaciones transaccionales. En estos controladores, lleva a cabo las acciones requeridas utilizando los repositorios que operan bajo la Unidad de Trabajo.
5. Manejo de la Unidad de Trabajo
– En situaciones donde un comando o consulta demande operaciones transaccionales, accede a la Unidad de Trabajo mediante inyección de dependencias. Inicia la transacción con la función correspondiente y ejecuta las acciones de base de datos con los repositorios bajo el control de la Unidad de Trabajo. Concluye utilizando Commit() para confirmar cambios o Rollback() para revertirlos.
Ejemplo práctico
Imaginemos una sencilla aplicación de comercio electrónico para ilustrar el uso del patrón Unidad de Trabajo.
Definición de la Interfaz de Unidad de Trabajo:
Aquí se define una interfaz llamada IUnitOfWork que tiene tres métodos principales:
- Commit(): Para guardar los cambios en la base de datos.
- Rollback(): Para deshacer cambios si es necesario.
- GetRepository<TEntity>(): Para obtener un repositorio asociado a una entidad específica.
Implementación de la Unidad de Trabajo:
Dentro de esta clase, se tiene una instancia del contexto de la base de datos (DbContext) y un diccionario que almacenará los repositorios creados. Se implementan los métodos definidos en la interfaz y se agrega un método adicional Dispose() para liberar recursos.
Esta es la clase Repository, que actúa como un intermediario entre la lógica de negocio y la base de datos. Se encarga de las operaciones CRUD (crear, leer, actualizar, eliminar) para una entidad específica.
Aquí se tiene un servicio llamado ProductService que utiliza la Unidad de Trabajo para realizar operaciones relacionadas con productos. Por ejemplo, el método CreateProduct realiza lógica de negocio y operaciones de repositorio usando _productRepository y luego confirma los cambios con _unitOfWork.Commit().
En resumen, el patrón Unidad de Trabajo permite agrupar operaciones en una única transacción, garantizando que todas las operaciones se completen con éxito o que ninguna se realice en caso de algún error. Esta estructura también promueve una separación de responsabilidades, haciendo que el código sea más mantener y sea mása legible.
Conclusión
La conclusión es que el patrón Unit of Work proporciona un mecanismo sólido para gestionar transacciones y garantizar la consistencia de los datos en aplicaciones de .NET Core. Siguiendo los pasos descritos en este artículo, puedes aprovechar los beneficios del patrón Unit of Work para construir aplicaciones escalables y mantenibles.
El ejemplo práctico, demostró cómo se puede integrar el patrón Unit of Work en una aplicación de comercio electrónico para manejar operaciones en la base de datos. Con el patrón Unit of Work, los desarrolladores pueden garantizar que las operaciones relacionadas se traten como una sola unidad lógica, promoviendo la integridad de los datos y la consistencia transaccional.
Hang Fire (librería .NET)
En la era digital actual, las aplicaciones web y móviles se han vuelto inherentemente asincrónicas y orientadas a eventos, donde las tareas en segundo plano juegan un papel crucial en la mejora de la experiencia del usuario y la eficiencia operativa. En el entorno de .NET, hay muchas librerías que los desarrolladores pueden utilizar, brindando una gran cantidad de utilidades y facilidades a la hora de crear proyectos. Hangfire es solución robusta y flexible para gestionar estas tareas en aplicaciones .NET, permitiendo a los desarrolladores ejecutar procesos en segundo plano de manera sencilla y eficaz. En esta ocasión, se va a hablar de Hangfire, una de las librerías más famosas a la hora de trabajar con procesos en segundo plano o tareas programables.
Herramientas relevantes que incluye Hangfire
- Tareas persistentes: Hangfire almacena en base de datos la gestión de las tareas, de forma que se pueda tener una clara idea de cómo ha ido su proceso de ejecución manteniéndose almacenado de forma segura.
- Automatización de reintentos: Hangfire permite que tareas que hayan fallado puedan reinventarse de forma automática.
- Panel de control: Hangfire dispone de un panel de control donde ver y gestionar las distintas tareas.
- Admite expresiones CRON
Características de Hangfire
Escalabilidad
Hangfire no solo facilita la ejecución de tareas en segundo plano sino que también escala con tu aplicación. Puede gestionar la carga entre diferentes servidores, asegurando que las tareas se distribuyan y ejecuten de manera eficiente en entornos distribuidos.
Seguridad
La seguridad es primordial, y Hangfire permite a los desarrolladores asegurar el panel de administración y gestionar las tareas de manera segura, garantizando que los datos y procesos estén protegidos.
Flexibilidad
Adaptándose a diversas necesidades y entornos de desarrollo, Hangfire ofrece una solución que puede ser implementada en una variedad de aplicaciones, desde pequeños proyectos hasta grandes empresas.
Casos de Uso posibles con Hangfire
Procesamiento de Imágenes
Imagina una aplicación de comercio electrónico que requiere procesar y optimizar miles de imágenes diariamente. Hangfire puede gestionar estas tareas, asegurando que las imágenes se procesen en segundo plano, mejorando la eficiencia y la experiencia del usuario.
Envío de Correos Electrónicos
En el contexto de una aplicación de CRM, Hangfire puede gestionar el envío de correos electrónicos masivos a los clientes, asegurando que los mensajes se envíen de manera oportuna sin afectar el rendimiento de la aplicación
Implementación Avanzada
Uso de Filtros
Los filtros en Hangfire permiten a los desarrolladores ejecutar lógica adicional antes o después de la ejecución de las tareas, proporcionando una capa adicional de flexibilidad y control.
Manejo de Errores
Hangfire gestiona automáticamente los errores y permite a los desarrolladores personalizar cómo se manejan los reintentos y las fallas, asegurando que las tareas se ejecuten de manera confiable.
Ejemplo: Integración Hanfire en Aplicación .Net
Crearemos una aplicación web de tipo Api:
Instalamos los paquetes necesarios para hangfire:
Añadimos los servicios de hangfire a nuestro proyecto:
Creamos un servidor que gestione nuestras tareas y ponemos la cadena de conexión con nuestra base de datos. Se deberá crear una base de datos para esta aplicación y poner la cadena de conexión correspondiente en este caso. Adicionalmente a SQL Server dispone de más métodos para almacenar la información de las tareas gestionadas.
También habilitaremos el panel de control:
Ejecutamos la aplicación
En nuestro proyecto si el la url añadimos “/hangfire”, deberá llevarnos al panel de control de la librería Hanfire implementada en nuestro proyecto:

Ejemplo de ejecución de proceso en segundo plano:
Este código nos permite ejecutar la impresión “¡Bienvenido al mundo de HangFire!”, por consola de forma asíncrona en segundo plano. Para este trozo de código HangFire se encarga de monitorizar y llevar un seguimiento de cómo finaliza.

En la imagen anterior se puede ver su panel de control. En este caso se puede ver como la anterior tarea ha finalizado correctamente, el código ejecutado y el tiempo de duración de esta.
Conclusión
Hangfire ha demostrado ser una herramienta impresionante y esencial en el ecosistema .NET, no solo simplificando la ejecución de tareas en segundo plano, sino también mejorando significativamente la forma en que los desarrolladores gestionan y monitorizan estas tareas. Como hemos observado, es fácil de instalar, amigable para el usuario gracias a su panel de control, y puede ser desplegado de forma rápida y sencilla. Usar Hangfire