En mi último post, traté los pros y los contras generales que encontramos al pasar de .NET Framework a .NET Core.En futuros posts, discutiremos algunos de los desafíos técnicos específicos a los que nos enfrentamos, y cómo trabajamos en torno a ellos.Antes de ponernos demasiado técnicos en nuestra mirada sobre la migración a .NET Core, sin embargo, tomemos un momento para revisar las elecciones arquitectónicas que hicimos antes de intentar cambiar de framework.Una gran parte de la razón por la que pudimos migrar de .NET Framework tan rápidamente fue porque separamos nuestra lógica de aplicación de las decisiones tomadas por el framework.
Incluso si nunca has codificado en C#, y no tienes intención de cambiar eso, quédate. Los principios que discuto aquí son ampliamente aplicables, sin importar los detalles del marco que estés usando.
La promesa de los marcos
Los marcos son herramientas poderosas que prometen hacer que el proceso de construcción de software complicado sea rápido y fácil (al menos en comparación). Abordan problemas difíciles y pueden abstraer las complejidades de las tareas comunes.
En gran medida, los marcos han cumplido su promesa.al ocultar gran parte de las complejidades de tareas como «ejecutar un servidor y responder a las peticiones HTTP», los marcos nos permiten centrarnos en la complejidad que diferencia a Google.los marcos también permiten una serie de opciones arquitectónicas interesantes.si cada servicio se construyera a partir de conexiones TCP, los microservicios nunca habrían ganado la popularidad de la que gozan actualmente.
El costo de los marcos
Si bien aportan muchos beneficios, los marcos también tienen un costo de acompañamiento. No hay tal cosa como un almuerzo gratis, después de todo, y por la misma razón que no hay una arquitectura perfecta, no hay un marco perfecto e inmutable. Quiero tomar unos minutos para hablar de un par de estos costos: el costo de la abstracción y el costo del cambio.
El costo de la abstracción
Mi primera introducción a los problemas que las abstracciones pueden traer fue el post de Joel Spolsky sobre la Ley de las Abstracciones con Filtraciones. Él hace un gran trabajo explicando por qué las abstracciones, estas cosas maravillosas que ideamos para hacer nuestras vidas más fáciles, a veces terminan haciéndolas más difíciles en su lugar. Los marcos están llenos de abstracciones, ¡a menudo contienen múltiples capas de abstracciones!
La mayoría de las veces, esto es algo bueno. En lugar de tratar con toda la complejidad de la especificación HTTP, y todas las formas en que diferentes personas han abusado y malinterpretado de ella, los marcos nos permiten tratar con un código relativamente simple y lógicamente consistente. Pero tenemos que recordar que los programadores de marcos también son humanos. A veces esto se mostrará con errores o vulnerabilidades de seguridad en los marcos de trabajo de los que dependemos.otras veces puedes descubrir que la cabecera HTTP Content-Type no se añade a la colección de cabeceras de una HttpResponse, sino que se añade a la colección de cabeceras de un objeto de contenido en la HttpResponse.
El hecho de que las abstracciones se filtren no debería impedirnos usarlas. Sólo debemos recordar que hay costos ocultos por hacerlo.
El costo del cambio
El segundo coste de añadir un marco que a menudo pasamos por alto es el coste de cambiar ese marco. Como todo lo demás en el mundo del software, los marcos están en un estado constante de cambio. Ya sea porque una versión actualizada de su marco actual está disponible, o porque aparece un nuevo marco, prometiendo resolver los problemas a los que se enfrenta, es casi seguro que terminará cambiando el marco sobre el que construyó su código. De repente, el marco que le permitió hacer tantas cosas tan rápido se está interponiendo en su camino.
Si tienes suerte, es tan simple como necesitar cambiar la forma en que funcionan algunas cosas, pero en muchos casos te encontrarás con cambios en las suposiciones fundamentales que hace tu marco. Cuando eso sucede, es probable que te encuentres con pequeños errores insidiosos que terminarán tomando mucho tiempo y esfuerzo para rastrearlos.
Una cosa importante a recordar cuando estás en lo profundo de la maleza luchando contra este tipo de cambio es que este es un buen problema a tener! El hecho de que estés cambiando el marco de trabajo significa que tu código es productivo. El código que simplemente funciona, que no se ajusta del todo a tu arquitectura idealizada, que ha estado funcionando medio olvidado durante años es maravillosamente exitoso .
Viviendo con nuestros marcos
Entonces, ¿cómo obtenemos el mejor valor de los marcos, mientras minimizamos los costos que traen?
En mi experiencia, una de las mejores herramientas para este tipo de trabajo es el aislamiento .
Los frameworks generalmente tienen un trabajo que resolver, darles exactamente el espacio suficiente para resolver el problema, y luego construir muros fuertes para mantenerlos fuera del resto de su código. Esto significa, cuando se usa un framework como ASP.NET, que dejamos que el framework resuelva todos los problemas de respuesta a las solicitudes HTTP, pero ponemos toda la lógica de la aplicación en otro lugar. En muchos casos, incluso me gusta poner esa lógica en un ensamblado separado que ni siquiera hace referencia a ASP.NET en absoluto, como una capa de protección añadida.
A modo de ejemplo, veamos un ejemplo de controlador del tutorial de la API web ASP.NET Core. Siguiendo sus instrucciones, acabas con un controlador algo así:
[Ruta("api/controlador")][ApiControlador]publicclassTodoController:ControllerBase{privatereadonlyTodoContext_context;// Muchos otros puntos finales omitidos por brevedad [HttpDelete("{id}")]publicasyncTask<IActionResult>DeleteTodoItem(longid){vartodoItem=await_context. TodoItems.FindAsync(id);if(todoItem==null){returnNotFound();}_context.TodoItems.Remove(todoItem);await_context.SaveChangesAsync();returnNoContent();}}
Obsérvese que este código contiene referencias a dos marcos diferentes al mismo tiempo: ASP.NET Core y Entity Framework.Para el equivalente de una aplicación de «Hello World», esto probablemente está bien. Sin embargo, si alguna vez tenemos que cambiar cualquiera de esos marcos, toda la lógica sobre si se pueden eliminar elementos y cómo eliminarlos realmente está enredada con el código del marco.
Una simple refactorización que reduce ese acoplamiento sería quitar del controlador toda la lógica que no se ocupa de responder a las peticiones HTTP. Entonces terminamos con dos clases:
[Ruta("api/controlador")][ApiControlador]publicclassTodoController:ControllerBase{privatereadonlyTodoList_todoList;// Muchos otros puntos finales omitidos por brevedad [HttpDelete("{id}")]publicasyncTask<IActionResult>DeleteTodoItem(longid){vardeleted=await_todoList. DeleteById(id)if(!deleted){returnNotFound();}returnNoContent();}}
public classTodoList{privatereadonlyTodoContext_context;publicasyncTask<bool>DeleteById(longid){varitem=await_context. TodoItems.FindAsync(id);if(item==null){returnfalse;}_context.TodoItems.Remove(item);await_context.SaveChangesAsync();returntrue;}}
Si este fuera mi proyecto, probablemente también querría extraer cualquier referencia a Entity Framework de TodoList, de modo que sólo se trate de objetos de clase sencillos.trataría las entidades generadas para EF como DTOs, convirtiéndolas en objetos de dominio que controlo por completo antes de pasarlas de un repositorio.aunque eso se complica un poco para una entrada de blog.
Trabajar de esta manera tiene un costo. Los marcos tratan de resolver más problemas de los que yo quiero, y es tentador dejar que el marco tome decisiones arquitectónicas por mí. También hay un nivel de duplicación que se hace inevitable cuando trato de mantener el marco lejos de mis objetos de dominio. A menudo tengo clases que parecen casi idénticas, con la única diferencia de que una está anotada para un marco y la otra es estrictamente de código. Además de ese tipo de duplicación obvia, la creación de esas diferentes capas termina haciendo un trabajo secundario. Sin embargo, al final me parece que es mejor pagar una pequeña cuota de mantenimiento por adelantado, en lugar de ocuparse de las consecuencias catastróficas en un futuro desconocido.
Esta es la segunda parte de una serie en curso sobre nuestra transición al .NET Core. Se puede encontrar más información en la parte 1 y la parte 3.
Categorías: technicalTags: architecture, clean code