Reducir y Reutilizar código con paquetes Nuget

Hola a todos! Después de un periodo extraño con todo lo que está sucediendo a causa del Covid-19, os traigo de nuevo (algo tarde pero sin ausencia 🙂 ) un artículo en el que aprenderemos a manejarnos con el uso de paquetes NuGet. Que a modo de introducción os puedo ir adelantando que se trata de un administrador de paquetes desarrollado por Microsoft.

Aprenderemos a manejarnos con el susodicho administrador de paquetes a través del IDE Visual Studio 2019, y también descubriremos la forma de publicar estos paquetes en un repositorio específico para dicho fin. Nada más que añadir tan solo espero que os guste y que encontréis aquello que habéis venido a aprender 😉 . Comenzamos!!

¿Qué es un paquete NuGet?

Es muy posible que si llevas algo de experiencia programando en las costillas habrás apreciado que en la gran mayoría de proyectos en los que hayas estado existen ciertas líneas de código o funcionalidades que se repiten, una y otra, y otra, y otra vez… y más aún si has trabajado con sistemas de microservicios, donde para un mismo equipo/proyecto pudieras tener varias aplicaciones donde seguramente en cada una de ellas cierta funcionalidad es exactamente la misma, por ejemplo, configuración del arranque del servicio, operaciones matemáticas complejas de un ámbito específico, conexión con el message broker configurado…etc.

Pues bien, para todo éste código la tendencia es normalmente a generarlo como un proyecto compartido entre todos éstos servicios y que al compilar el proyecto o mejor dicho, al compilar desde alguno de los proyectos desde los que se comparten dicha librería éste proyecto va incluido en cada uno, generando en el proceso de compilación la DLL que requiere para funcionar e incluyéndola en la compilación.

Este proceso presenta algunos inconvenientes, ya que, que todos los proyectos tengan una referencia directa implica que desde todos ellos usando el Visual Studio podrías modificarlo, y ésta modificación afectará a todos los proyectos por igual. Ahora imaginemos que realizamos una modificación que para uno de los servicios funciona a la perfección pero para alguno de los demás no funciona del todo bien (Por el motivo que sea, versión del proyecto, caso de uso distinto etc…). Sin darnos cuenta la herramienta compartida que nos facilita la vida en realidad nos ha puesto unos grilletes entre todos nuestros proyectos.

En base al problema anterior nos serviremos de paquetes NuGet para darle solución. Para entender lo que es un paquete de código, podríamos pensar en un archivo comprimido en el que hemos incluido las DLLs del código compartido compilado que necesitamos y le establecemos el nombre apropiado al archivo junto con su número de versión. Si ya partimos de ésta idea ¿Que es lo que nos falta para que funcione? Tan solo bastaría con subir dicho archivo a algún sitio con acceso a Internet y mediante un procedimiento o herramienta que se encargue de descargarse dicho paquete e incluirlo en el proyecto a la hora de compilarlo, así de sencillo es cómo funciona el administrador de paquetes NuGet incorporado con Visual Studio.

Este sería un ejemplo de publicación y consumo de un paquete NuGet, desde el cual resulta casi transparente al programador gracias al uso de Visual Studio, y de su integración con el administrador de paquetes propio. Y digo casi porque lo único que tendríamos que hacer es indicar obviamente que referencias de paquetes queremos importar en nuestro código, y el origen de datos (repositorio público o privado) desde donde descargarlo. Y ya el propio sistema de dependencias integrado se encarga de elaborar la magia por nosotros. Si a alguien le pica más aún el gusanillo de cómo funciona el sistema de dependencias del administrador de paquetes os dejo un enlace a la documentación oficial de Microsoft desde donde he sacado incluso algunas referencias.

Gracias a ésta herramienta podemos tener código compartido entre proyectos sin que estos se vean acoplados directamente, y además utilizando el propio sistema de versionado que es capaz de gestionar el administrador de paquetes NuGet, podemos tener versiones distintas de la misma librería de código en diversos proyectos.

Repositorios de paquetes

Antes comentaba que para que todo éste sistema funcione es necesario el uso de repositorios donde poder publicar nuestros paquetes Nuget, y desde donde obviamente puedan ser descargados. Existen dos tipos de repositorios los públicos y los privados.

  • Repositorio público: Existen ciertos paquetes que la propia comunidad o empresas se encargan de elaborar y de distribuir en un sitio público para que sean libremente consumidos por otros desarrolladores. Para ello se encuentra el repositorio nuget.org.
  • Repositorio privado: Este tipo de repositorio está mas enfocado a su uso en organizaciones que como bien indica su nombre únicamente los miembros de dicha organización tienen acceso. Se puede utilizar tanto un servidor privado Nuget, o también repositorios de terceros como por ejemplo Nexus, Artifactory, myGet… etc

A modo de ejemplo rápido de el uso del repositorio nuget.org mediante Visual Studio 2019 os muestro un ejemplo en donde vamos a implementar un serializador de objetos en formato Json, y para ello nos serviremos del paquete Nuget Newtonsoft.Json el cual nos ya nos proporciona los métodos apropiados para la serialización y des-serialización de nuestros objetos, nosotros tan solo nos serviremos de su ayuda de la siguiente forma:

En la imagen anterior vemos como he creado una clase denominada CustomSerializer.cs, creándole los métodos de serialización y donde tan solo escribiendo el inicio de la cadena de la clase estática JsonConvert (del paquete ya mencionado Netwtonsoft.Json) podemos usar la ayuda de las sugerencias de código de el propio Visual Studio para descargar e instalar de forma automática dicho paquete.

Nota: La instalación de paquetes desde las sugerencias de código solo se encuentra en las últimas versiones de Visual Studio 2019. Para versiones anteriores esta opción no se encuentra disponible.

Otra forma de instalar el paquete es desde el Administrador de paquetes Nuget del proyecto, para ello nos vamos al proyecto donde queramos instalar el paquete en el ‘Explorador de Soluciones’ y pulsamos click derecho sobre él.

Aparecerá el desplegable de la imagen anterior, y en él tendremos que seleccionar la opción marcada ‘Manage Nuget Packages….‘ y nos aparecerá una ventana como la siguiente:

Desde esta vista se encuentran remarcadas las secciones más relevantes, donde en la parte izquierda se encuentra el filtro de paquetes a buscar, ya sea un filtro por nombre junto con tres secciones ‘Browse’, ‘Installed’ y ‘Updates’ donde podremos buscar paquetes nuevos, ver los ya instalados y por último ver las actualizaciones pendientes de los paquetes instalados. Por otro lado en la parte de la derecha tenemos la sección para indicar la versión a instalar de los paquetes seleccionados en el listado central, con lo que, una vez indicado nuestro paquete Newtonsoft.Json pulsamos en el botón de la derecha ‘Install’ para descargar e instalar el paquete.

Nota: Aunque no haya mostrado mucho detenimiento en ésta parte, es importante comprobar antes de instalarnos cualquier paquete que la versión de nuestra aplicación es compatible con la versión del paquete que estamos instalando. De esta forma nos evitaremos muchos sustos en el futuro 😉

Para rematar este ejemplo tan sencillo os muestro el resultado final de la clase CustomSerializer.cs:

Una vez que el paquete ya esta descargado e instalado tan solo hace falta hacer referencia a él desde nuestro código mediante el uso de:

using Newtonsoft.Json;

De esta forma tan sencilla podemos utilizar paquetes de terceros para enriquecer nuestro código con funcionalidad que de buenas a primeras tendríamos que implementar desde cero. Pasamos ahora a crear nuestra primera aportación a la comunidad 🙂 !

Nuestro primer paquete NuGet

Antes de comenzar con la creación del paquete os voy a ilustrar un poco el típico ejemplo real de programación y una posible solución propuesta por mi parte. El problema en cuestión sería el de ejecutar tareas asíncronas o Task (En c#), en las que por motivos de rendimiento o por que dicha tarea realizaría un bloqueo demasiado extenso en base de datos, queremos lanzar las tareas y que éstas se procesen en lotes configurables.

Para simular la ejecución de una tarea de larga duración, he implementado el siguiente método:

        private static Task<ResultObject> NextSumValue(int first, int second)
        {
            return Task.Run(async () =>
            {
                var rnd = new Random();
                await Task.Delay(1000 * rnd.Next(0, 5));

                return new ResultObject(first + second);
            });
        }

        private class ResultObject
        {
            public ResultObject(int value)
            {
                Value = value;
            }

            public int Value { get; set; }
        }

El método NextSumValue(int,int) será el que utilizaremos como prueba para la creación de un paquete Nuget de ejecución de tareas en lotes. Éste método como se puede apreciar sera el encargado de, en base a dos enteros de entrada, retornar un objeto como resultado ResultObject que contiene una propiedad Value que sería el resultado de la suma de dichos números. Además para emular que la misma tarea es de larga duración será lanzada en un hilo de forma asíncrona retrasado un tiempo aleatorio entre 0 y 5 segundos gracias al uso de la función Task.Delay y la clase Random.

Empezaremos por la base y primeramente implementaremos una versión de prueba que ejecutara varias llamadas a la función de larga duración ya mencionada, y finalmente esperará a que terminen todas las ejecuciones para mostrar el resultado en pantalla a modo de ejemplo:

class Program
    {
        static async Task Main(string[] args)
        {
            System.Console.WriteLine("Start TaskBatch Test!");
            System.Console.WriteLine("Input Example:");

            var array = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            foreach (var i in array)
                System.Console.Write($"{i}:");

            System.Console.WriteLine("");

            List<Task<ResultObject>> tasks = new List<Task<ResultObject>>();

            // Run 10 times the long time method
            for (int index = 0; index < array.Length; index++)
                tasks.Add(NextSumValue(index, 10));

            await Task.WhenAll(tasks); // Wait to complete all pending Tasks

            var result = tasks.Select(t => t.Result);

            System.Console.WriteLine("End TaskBatch process");
            System.Console.WriteLine("Example Result:");
            foreach (var r in result)
                System.Console.Write($"{r.Value}:");

            System.Console.ReadLine();
        }
}

Como vemos en el ejemplo mostrado la prueba consistirá en la ejecución de 10 veces de la llamada al método NextSumValue(int,int) donde se le pasará por parámetro un índice de 0 a 10 en la iteración con la suma de la constante 10, con lo que para la entrada propuesta el resultado será el recorrido de los índices sumando cada uno por 10, es decir, 10:11:12…:19. En este primer ejemplo todas las tareas serán lanzadas en paralelo y se esperará al resultado de todas en el punto en el que se ejecuta la llamada await Task.WhenAll(tasks), con lo que el resultado si ejecutamos la aplicación será el siguiente:

Como solución al problema ya comentado de un excesivo bloqueo en el lanzamiento en paralelo de todas estas tareas, una posible solución propuesta por mi parte sería la creación de la siguiente clase TaskBatch.cs:

    public class TaskBatch<T>
    {
        public int MaxNumBatch { get; }

        private readonly List<Task<T>> _tasks;
        private readonly List<T> _results;
        private SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);

        public TaskBatch(int maxNumBatch = 20)
        {
            MaxNumBatch = maxNumBatch;
            _tasks = new List<Task<T>>();
            _results = new List<T>();
        }

        public async Task Add(Task<T> task)
        {
            if (_tasks.Count >= MaxNumBatch)
                await ProcessTasks(task);
            else
                _tasks.Add(task);
        }

        public async Task<List<T>> GetResults()
        {
            if (_tasks.Count > 0)
            {
                await ProcessTasks();
            }

            var listTasksCloned = new List<T>(_results);
            _results.Clear();

            return listTasksCloned;
        }

        private async Task ProcessTasks(Task<T> next = null)
        {
            await _semaphore.WaitAsync();

            try
            {
                var results = await Task.WhenAll(_tasks);

                //Only for code example
                Console.WriteLine($"Process {results.Count()} Tasks in batch!");

                _results.AddRange(results);
                _tasks.Clear();
                if (next != null)
                    _tasks.Add(next);

            }
            finally
            {
                _semaphore.Release();
            }


        }

    }

Este sería un ejemplo de implementación para resolver el problema de ejecución de tareas en lotes. La idea reside en utilizar la clase TaskBatch como una colección de tareas que se pueden ir añadiendo y resolviendo internamente hasta que llega un tope configurable, en éste punto, se esperará hasta que todas las tareas ya incluidas sean procesadas para poder permitir añadir más tareas. También cabe destacar el uso de otra colección con los resultados de las tareas completadas, y además el uso de un semáforo (SemaphoreSlim) para evitar los conocidos problemillas de condiciones de carrera en la espera de las tareas asíncronas.

Tampoco quiero detenerme demasiado en la explicación de cómo funciona la solución propuesta ya que no es el objetivo de éste artículo, sino en cómo empaquetaremos a continuación este código para poder ser reutilizado por cualquiera, incluso por nosotros mismos en cualquier proyecto que deseemos. Aun así al final de todo terminaremos con la publicación de éste paquete de forma pública en nuget.org, con lo que apreciaré cualquier sugerencia o mejora, además de que podréis utilizarlo con total libertad 😉

Pero antes de nada vamos a adaptar el ejemplo anterior para que utilice la clase TaskBach y ver que realmente funciona con la misma prueba:

        static async Task Main(string[] args)
        {
            System.Console.WriteLine("Start TaskBatch Test!");
            System.Console.WriteLine("Input Example:");

            var array = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            foreach (var i in array)
                System.Console.Write($"{i}:");

            System.Console.WriteLine("");

            TaskBatch<ResultObject> taskBatch = new TaskBatch<ResultObject>(4);

            for (int index = 0; index < array.Length; index++)
                await taskBatch.Add(NextSumValue(index, 10));

            var result = await taskBatch.GetResults();

            System.Console.WriteLine("End TaskBatch process");
            System.Console.WriteLine("Example Result:");
            foreach (var r in result)
                System.Console.Write($"{r.Value}:");

            System.Console.ReadLine();
        }

Como se puede apreciar es bastante sencillo de utilizar, simplemente añadimos las tareas a procesar usando la clase TaskBatch, y finalmente esperamos al resultado de todos los lotes usando para ello el método GetResults(). Tened en cuenta que conforme se van añadiendo las tareas inmediatamente pasan a procesarse en paralelo, excepto cuando llegue al tope del lote configurado en donde se espera a que las actualmente añadidas terminen para poder permitir incluir más tareas, es por esto por lo que se debe usar await en el método Add() en la iteración.

Si ahora ejecutamos este nuevo ejemplo obtendremos el siguiente resultado:

He incluido dentro de la propia clase que imprima un mensaje por consola cuando un lote es procesado indicando el número de tareas que ha procesado, es por esto por lo que aparecen esos mensajes indicando que ha procesado 4, 4 y 2 tareas en éste orden, con lo que concuerda con el número de lote configurable al instanciar la clase TaskBatch. Si bien es cierto que para éste tamaño de prueba el rendimiento no es que no sea apreciable, si no que al contrario perdemos tiempo de procesamiento al no lanzar todas las tareas en paralelo, pero como ya he mencionado antes existen ciertas situaciones donde un procesamiento por lotes si que tiene una mayor cabida.

Visto un poco el funcionamiento del ejemplo propuesto pasemos a imaginarnos el caso donde no queremos repetir estas mismas líneas de código en cada proyecto donde surja la necesidad, por lo que volvemos al motivo principal de éste articulo, crear un paquete Nuget.

Antes de nada en ésta demostración como bien dije antes vamos ha paquetizarla y a subirla de forma pública usando para ello nuget.org, por esto es por lo que deberíamos de comenzar con registrarnos en éste sitio web para poder tener acceso y crear nuestras credenciales. Para registrarnos podremos utilizar una cuenta Microsoft si ya disponemos de una.

Una vez registrados en el sitio volvemos a Visual Studio para generar el paquete Nuget de nuestro proyecto TaskBatchProject.csproj, para ello nos vamos al mismo en el “Explorador de soluciones” y hacemos click derecho en él, se nos abrirá un desplegable donde tendremos que ir ahora a la sección de “Propiedades“:

Se abrirán varias pestañas de configuración del proyecto y entre ellas a nosotros nos interesará la de “Paquete” o “Package“:

En ésta sección deberíamos de marcar el checkbox indicado en la imagen, habilitando así la creación del paquete Nuget después de una compilación del proyecto. Aparecen además otros campos a completar donde cabe destacar que los realmente importantes para que el proceso funcione sería el “Package Id“, ya que será el identificador único con el que se subirá al repositorio y por el cual los desarrolladores podrán saber que es el paquete correcto, y el campo “Package version” que será el que establezca la versión actual del paquete publicado y que también servirá para futuras actualizaciones.

Todos los campos adicionales que aparecen de información también son relevantes en el sentido de generar confianza en los desarrolladores a la hora de decidir si utilizar el paquete o no, campos como “Repository URL“, con la dirección del repositorio de código del propio paquete, “Licensing“, licencia que utiliza el código del paquete que en mi caso utilizaré una licencia de código abierto como lo es la MIT License, y de más campos que no detallaré por su obviedad.

Una vez establecida toda la configuración que nos interese, pasamos a cambiar el perfil de compilación del proyecto a “Release” y compilamos. El motivo de cambiar de perfil es porque éste es el recomendado cuando queremos ir a producción, ya que estaríamos generando una librerías de código (DLLs) optimizadas y no las habituales que son las usadas para depuración (Debug). Al terminar el proceso de compilación en la ruta “bin/Release” a partir del proyecto se nos debe de generar el paquete Nuget resultante:

Volvemos ahora otra vez a nuget.org y como ya tenemos un usuario registrado entramos con el mismo, veremos que nos aparecen varias pestañas en la parte superior, de las cuales a nosotros ahora mismo nos interesa la de “Upload“:

Para ahora poder subir nuestro paquete es muy sencillo y la propia web te lo está indicando, seleccionamos el botón “Browse” y buscamos nuestro paquete previamente generado para ser subido al repositorio público de nuget.org. Después de este paso te aparecerá toda la información contenida en el paquete, la misma que indicaste en Visual Studio, para que verifiques que está todo correcto, al final del todo tendrás el botón de “Submit para definitivamente subir el paquete. Una vez hecho esto deberá aparecernos la siguiente vista si todo ha ido correctamente:

Como vemos se nos informa de que nuestro paquete se encuentra en un proceso de validación e indexado, y hasta que ambos no terminen el paquete no aparecerá listado dentro del repositorio de búsqueda de Nuget. Ahora solo falta armarnos de paciencia y esperar a que pase algún tiempo…. (Suele ser bastante menos de una hora). Y finalmente volvemos al administrador de paquetes de Nuget y buscamos nuestro paquete, como ya hicimos en la sección anterior con el paquete Newtonson.Json.

et voilá! Ya tenemos a nuestra disposición el paquete MindBodyNCode.TaskBatch para el uso y disfrute de todos en el repositorio público de nuget.org. Con el que podremos importar la clase TaskBatch y utilizarla allá donde queramos.

Conclusión

Con estos sencillos pasos hemos aprendido a generar un paquete Nuget y a publicarlo en un repositorio específico para este caso, como bien se ha recalcado a lo largo del artículo utilizando esta técnica podremos reducir notoriamente las líneas de código y proyectos compartidos en nuestro día a día, pero tampoco nos volvamos locos, que también podríamos pecar de paquetizarlo todo, y donde debería de haber una fluidez y consistencia en nuestro código en su lugar nos encontramos un amalgama de paquetes a cual más complejo que el anterior.

Dicho esto, dejo en el tintero varias funcionalidades y ventajas que nos ofrece esta herramienta, como por ejemplo el uso de versiones preliminares (Alpha), muy útil cuando trabajamos en equipo o queremos llevar un control de las versiones publicadas, o la automatización de toda la parte de generar el paquete y publicarlo, que como hemos podido ver resulta bastante manual aún… todo esto lo dejaremos para próximos posts.

Hasta pronto developers!