[ASP.NET] Simplificando los controladores

Hace unos días Juanma publicó un tweet con un articulo que me resultó interesante. En este se describe como «mejorar» el código de las aplicaciones web escritas en lenguajes orientados a objetos y sobre frameworks MVC usando «técnicas» un poco más cercanas a un paradigma funcional, y describe, además, lo difícil de adaptar el mundo stateless de la programación web (server side) a lo que propone un diseño orientado al dominio.

Me pareció interesante, además, porque en las últimas semanas he estado replanteándo como hago yo este tipo de desarrollos… en un nuevo proyecto. Siempre que puedo trato de cambiar en algo el como escribo y organizo mi código, casi siempre influenciado por lecturas (libros, blogs, twitter), conferencias, y demás, y me han llevado por escribir aplicaciones como lo planteaba el libro negro de la Arquitectura Orientada al Dominio con Tecnologías Microsoft, si, con todo y los cien ensamblados. He escrito aplicaciones con todo el código en los controladores o, en el mejor de los casos, llamando a clases que «encapsulan» estos procedimientos, pero igual, todo muy acoplado. He escrito aplicaciones con modelos de dominio anémicos y un montón de servicios que inyecto en los controladores, con y sin repositorios, etc. Pero ahora quise probar algo que vi en el blog de Bogard, quien propone usar el patrón mediador para escribir aplicaciones que usen un modelo CQS. Incluso creó una librería para resolver los Handlers de las consultas y comandos, con una que otra cosa de más, llamada MediatR, que se integra bien con y sin contenedores de dependencias.

En resumen, lo que se busca con CQS es separar las consultas de las escrituras, para simplificarlo podríamos verlo como los GET y los POST en nuestras aplicaciones, y con la aplicación del patrón mediador es tener una única dependencia en los controladores (el mediador) y que sea este ultimo quien resuelva que hadler hace que.

Para ilustrar mejor esta definición veamos un ejemplo:

    public interface ICommand<out TResult> { }

    public interface ICommandHandler<in TCommand, TResult> where TCommand : ICommand<TResult>
    {
        Task<TResult> HandleAsync(TCommand command);
    }

    public class AddItemCommand : ICommand<AddedItemViewModel>
    {
        public AddItemCommand(Guid itemId, Guid storeId)
        {
            ItemId = itemId;
            StoreId = storeId;
        }

        public Guid ItemId { get; private set; }
        public Guid StoreId { get; private set; }
    }

    public class AddItemHandler : ICommandHandler<AddItemCommand, AddedItemViewModel>
    {
        private readonly IApplicationDbContext _context;

        public AddItemHandler(IApplicationDbContext context)
        {
            _context = context;
        }

        public async Task<AddedItemViewModel> HandleAsync(AddItemCommand command)
        {
            var itemId = command.ItemId;
            var storeId = command.StoreId;
            var store = await _context.Stores.FirstAsync(s => s.Id == strtoreId);

            //Todas las reglas del dominio al dominio.
            var item = store.ShipItem(itemId);
            //...
            
            _context.SaveChanges();
            var resp = new AddedItemViewModel
            {
                Location = item.Location,
                //...
            };
            return resp;
        }
    }

Podemos detenernos aquí y recibir en el constructor del controlador cuantos handlers deseemos y usarlos en los métodos de acción que los requieran, o, si queremos ir más allá, podemos crear un objeto que resuelva y llame al handler adecuado a partir del comando, para esto crearemos un mediador o usamos MediatR. De hecho podemos detenernos en el Command y «encapsular» allí, acoplado o no, toda la funcionalidad de dicho comando y ser felices, todo depende de nuestra aplicación, sus requerimientos, y la facilidad para testear el código que deseemos.

Con esto habré transcrito aquí lo que entendí de la serie de artículos de Bogard, así que iré un poco más allá y voy a discutir algo que vi hace un tiempo en un video del buen Greg Young.

Greg Young afirma que la magia, en el código, es mala. Expone el caso de los dynamic proxy y de cómo nos explicarían eso a los desarrolladores junior, o cómo entendemos la magia de los frameworks que usan en un proyecto cuando recibimos los fuentes. Habla de los contenedores de dependencias y su magia, nos habla de buscar una interfaz común y cambiar nuestro problema con el fin de evitar la magia. Muestra una implementación de comandos y consultas, en lago similar a la que ya vimos… en fin, es un buen video y estoy seguro que algo aprenderás como lo hice yo.

Revisando la implementación anterior

Viendo el código de nuestro command handler vemos que la interfaz común nos indica que habrá un parámetro de entrada y uno de salida con un única acción: procesar el comando. Todo lo que necesitemos para llevar a cabo esta acción lo pasaremos como parámetro al constructor, y el único estado que contendrá esta clase serán dichas dependencias. Si no hay más estado que dichas dependencias y la interfaz común solo expone una acción con una entrada y una salida… ¿no luce esto como una función?

Ya Mark Seemann había escrito sobre el tema, y en español, Juanma también nos compartió sus opiniones.

En este caso me inclino a pensar un poco más funcional, es decir, darle todo lo necesario a la función para que haga su trabajo y no tener un estado que no aporta mucho en el objeto. Así las cosas podríamos olvidarnos de las interfaces y usar solo funciones:

    public class AddItemCommandHandler
    {
        public static Func<AddItemCommand, IApplicationDbContext, IAzure, Task<AddedItemViewModel>> Handle = HandleBody;

        private static Task<AddedItemViewModel> HandleBody(AddItemCommand command, IApplicationDbContext context, IAzure azure)
        {
            throw new NotImplementedException();
        }
    }
    public class RemoveItemCommandHandler
    {
        public static Func<AddItemCommand, IApplicationDbContext, Task<RemovedItemViewModel>> Handle = HandleBody;

        private static Task<RemovedItemViewModel> HandleBody(AddItemCommand command, IApplicationDbContext context)
        {
            throw new NotImplementedException();
        }
    }

El problema de ahora pasar como parámetro todas las dependencias a nuestros métodos es que hemos roto la interfaz común que habíamos logrado. Así que, ¿cómo lo conseguimos?

Aplicación parcial de funciones

La aplicación parcial de funciones es algo muy común en la programación funcional, podemos verla como la función resultante de llamar a una función sin todos sus argumentos. La manera más fácil de entenderlo es mediante un ejemplo:

    Func<int, int, int> Multi = (a, b) => a * b;
    Func<int, int> DosPor = a => Multi(a, 2);
    DosPor(3); // 6 😛

Haciendo uso de la aplicación parcial de funciones podemos pasar los parámetros extra (repositorios, contexto, loggers, helpers) para trabajar con una función que reciba un solo parámetro, el command, y recuperar así nuestra interfaz común.

Ok, ¿y cómo se resuelven los Handlers?

Ya no tenemos interfaces para inyectar y resolver desde el mediador. Pero podemos usar un contenedor de Comandos (un diccionario) para usarlos desde el controlador. ¿No es este un acoplamiento con dicho contenedor que hará más difícil testear mis controladores? te preguntarás, sí, si deseas hacerle test unitarios al controlador lo «mejor» sería sacarle una interfaz a esta dependencia (pero no me gusta hacerle unit testing a los controladores…)

¿Cómo queda este contenedor de comandos?

    public class ApplicationCommands
    {
        private static readonly Dictionary<Type, Func<ICommand, Task<IResult>>> Handlers = new Dictionary<Type, Func<ICommand, Task<IResult>>>();

        public static void Register<T>(Func<T, Task<IResult>> handler) where T : ICommand
        {
            if (!Handlers.ContainsKey(typeof(T))) Handlers.Add(typeof(T), command => handler((T)command));
        }

        public static async Task<IResult> HandleAsync(ICommand command)
        {
            return await Handlers[command.GetType()](command);
        }
    }

La interfaz ICommand la tenemos desde la primera implementación, pero ahora no será genérica y nos servirá de marker interface. Y la interfaz IResult tendrá dos métodos que uso para eso de mostrar mensajes de error y ayuda. La definición de las interfaces es:

    public interface IResult
    {
        void AddHttpInfo(HttpViewModelInfo info);
        IList<HttpViewModelInfo> GetHttpInfo();
    }

    public interface ICommand { }

Ya podremos cargar nuestro contenedor de comandos haciendo algo como:

            ApplicationCommands.Register<AddItemCommand>(
                async command => await AddItemHandler.Handle(command, new ApplicationDbContext(), new AzureManager()));

¿Y cómo se maneja el ciclo de vida de los objetos?

Al manejar un diccionario estático con los comandos no se puede registrar una instancia con ciclo de vida por request, lo que estaría por request es el comando, pero el dispose de, por ejemplo, el DbContext queda fuera de nuestro control. Por lo que no sé si convendría más usar un contenedor por request, es decir, reconstruirlo para cada controlador o actualizar la entrada en el diccionario (agradezco sugerencias en los comentarios) :S

Para verlo más claro vamos a cargar los applications commands para el Web Api:

    public class WebApiCompRoot : IHttpControllerActivator
    {
        public IHttpController Create(
            HttpRequestMessage request,
            HttpControllerDescriptor controllerDescriptor,
            Type controllerType)
        {
            if (controllerType == typeof(ShippingController))
            {
                var context = new ApplicationDbContext();
                var azureContext = new AzureContext();
                ApplicationCommands.Register<AddItemCommand>(async command => await AddItemHandler.Handle(command, context, azureContext));
                request.RegisterForDispose(context); //Ciclo de vida por request
                return new ShippingController();
            }
            return (IHttpController) Activator.CreateInstance(controllerType);
        }
    }

Si ejecutamos así el primer request funcionará perfecto. Pero para los siguientes no se creará un nuevo context y recibiremos la excepción indicando que ya se ha hecho Dispose. Podemos entonces reescribir la entrada en el diccionario en el ApplicationCommands o crear un contenedor por request y pasarlo a cada controlador.

Notas

En esta entrada solo mostré el uso de comando y no de consultas, pero la idea es la misma.

«¿No deben los comandos ser de tipo void (Action)?» eso dicen, pero noté que siempre quiero informar algo y si no es así, siempre puedes crear un IResult Unit, como el de F#

Ese código se ve horrible, ¿por qué no usas mejor un lenguaje funcional? Si, ya me lo han dicho y espero estar hablando sobre el tema pronto.

Espero les sea de utilidad, y si tienen feedback, bienvenido!

Hasta el próximo post.

2 comentarios sobre “[ASP.NET] Simplificando los controladores

  1. Hace algún tiempo en mis inicios con CQRS también intenté hacer cosas como las que expones para resolver los comandos, algunos «en CQRS» lo llaman CommandDispatcher, implementé uno propio con algo de entusiasmo y en parte motivado porque muchos lo hacían, como suelen comenzar muchas cosas que terminan «sobrecomplejas».

    El caso es que desistí de adoptar esa estrategia de mediación, para mi caso y creo que para la mayoría no tiene mucho sentido, realmente es hacer una capa innecesaria que solo agrega trabas. Es decir, practicamente siempre tiene una relación 1-1 entre un request y una ejecución de comando y solo tienes un command handler. Uses un «bus» o un dictionary o lo que sea, en la práctica sabes que cierto verbo sobre cierto recurso siempre va a parar al mismo comando de toda la vida, entonces ¿para qué ponerlo en una lista que tendrás que recorrer?.

    Sobre el asunto del acoplamiento o digamos más bien el tener dependencias, el problema para mi no es tanto en tenerlas, si no en si se tienen sobre los asuntos que se deben tener o no, sobre asuntos ortogonales es claro que conviene desacoplar. Para mi está perfectamente bien que mi código de controladores se encargue de sus responsabilidades como cosas Http, routing, etc. y está bien que «dependa» de una implementación específica de un servicio de aplicación, que podría ser el command handler en este caso, y que este a su vez dependa de una implementación específica de un objeto de negocios, aunque claramente en sentido contrario no estaría nada bien. Ya si uso IoC container y cómo, pues debería ir subordinado a esto y hay formas.

    El asunto de la facilidad de realizar pruebas automatizadas es otro tema bastante amplio, solo diré que para mi, tiene mucho sentido realizar pruebas unitarias sobre por ejemplo reglas de negocio en ese tipo de objetos, pero ya en objetos de aplicación e interfaz (HTTP API/UI), como pareces indicar, pues tiene sentido más algo de pruebas funcionales / de aceptación. Y bueno, esto conduciría a mucho más…

Deja un comentario