Victor Gomez de Juan

Desarrollador de software y analista de datos

Clean Architecture con .NET Core

Introducción

En Segula Technologies, mi actual empresa,  estamos desarrollando una nueva Intranet/Extranet con tecnología Microsoft. Los hemos decidido así por el conocimiento previo en desarrollo de software en entorno .NET de los dos miembros del equipo, porque ya teníamos licencias de Visual Studio y porque la mayoría de sistemas y entornos utilizan tecnología de la marca de Redmond. En este post os explico cómo y por qué nos decidimos finalmente por la opción de Clean Architecture con .NET Core.

Como suele ser común en estos casos, queríamos que la nueva plataforma fuera amigable, responsive, escalable y fácil de desarrollar y mantener. Así que me puse manos a la obra en la búsqueda de una buena arquitectura de software para nuestros requisitos.

Somos un equipo de desarrollo pequeño (dos personas) y no queremos montar una megaplataforma con 500 capas y módulos diferentes. Aún así, esta Intranet va a tener de todo: interfaz web y probablemente app móvil, bastantes apartados diferentes, lógica de negocio de varias áreas diferentes de la empresa, acceso a APIs externas, acceso a bases de datos múltiples, tareas en background y oferta de servicios web a sistemas externos (y probablemente otro tipo de interfaces con otros sistemas que no soporten este tipo de interacción o casos en los que no sea la mejor opción). Si este es tu/vuestro caso, sigue leyendo 😉.

Después de buscar, indagar y descargarme unos 15 proyectos de esqueleto o demo con diferentes estructuras, me decanté por la propuesta Clean Architecture with .NET Core  de Jason Taylor. Las demás, o se quedaban cortas, o no cumplían con ciertos criterios de separación de responsabilidades entre capas o eran monstruos más bien para el escaparate académico. La propuesta de Jason no es excesivamente sencilla de entender, pero gracias a sus artículos, vídeos, los dos proyectos de ejemplo que tiene y un poco de tiempo y esfuerzo lo acabas entendiendo. Y es relativamente fácil comenzar a picar código partiendo del proyecto esqueleto.

La propuesta de arquitectura de Jason realmente consigue un equilibro bastante reseñable en cuanto a limpieza de código, agilidad de desarrollo, modularidad, escalabilidad y mantenibilidad. Si vas a realizar un proyecto para una PYME o similar, y no quieres ser un aportador más del código «churro» inmantenible que hemos heredado (y en algunos casos seguramente creado, por que no) muchos de nosotros, te recomiendo estudiar esta opción.

En qué consiste

Resumiendo mucho, Clean Architecture se basa en la idea de que la capa de dominio no dependa de ninguna capa exterior. La de aplicación sólo depende de la de dominio y el resto (normalmente presentación y acceso a datos) depende exclusivamente de la capa de aplicación (no tienen acceso a la capa de dominio directamente). Todo esto lo consigue de forma muy hábil con la implementación de interfaces de servicios que luego tendrán que implementar las capas externas y ya la popular inyección de dependencias.

esquema Clean Architecture

Ventajas que ofrece

Jason Taylor cita algunas más, pero yo me quedo con éstas:

  • Independiente del tipo de UI (Interfaz de Usuario).  Al estar toda la lógica fuera de la capa de UI, podemos utilizar la tecnología que queramos. O incluso, como probablemente sea nuestro caso, utilizar más de una (una para acceso web desde PC/portátil y otra para acceso desde smartphone).
  • Independencia de la base de datos. Esto es algo que se supone que ya nos lo proporciona Entity Framework y me gusta el hecho de que Jason tampoco se complique mucho el tema con esto por esa misma razón.
  • Independiente de todo lo externo al proyecto/aplicación. Esto siempre se dice de forma grandilocuente y luego nunca suele ser verdad, pero es cierto que la modularidad, la gestión de dependencias y el hecho de basarse en .NET Core hace que esta opción sea una de las que más se acerce a tan atrevida afirmación.

También está como ventaja reseñable la posibilidad de sistematizar el tema del testing, pero es algo que nosotros de momento no vamos a implementar y la experiencia me dice que esto suele ser lo común.

Tecnologías y frameworks utilizados

Sin duda una de las joyas de la corona de la propuesta Clean Architecture de Taylor es el uso que hace de frameworks y tecnologías sin caer en el purismo ni en el uso alocado de estas herramientas. Utiliza fundamentalmente las siguientes:

  • MediatR. Para la llamada de servicios a la capa de aplicación sin hacer uso de dependencias. Me ha parecido superútil no sólo para evitar el tema de dependencias, si no sobre todo para estructurar las llamadas de queries (consulta) y comandos (inserción/modificación/borrado) de manera fácilmente entendible, desarrollable y mantenible.
  • FluentValidation. Permite «aislar» las validaciones de los comandos para tenerlas en un único sitio y ahorrarse mogollón de código. Durante algunos momentos me asusté porque creía que no iba a poder hacer algunos tipos de validación con esta herramienta, pero con un poco de tiempo, esfuerzo y la ayuda de esa buena gente que publica soluciones a problemas complejos, de momento he conseguido hacer de todo.
  • AutoMapper. Para sistematizar la conversión de objetos de modelo en view models de una manera fácil, ahorrándose código y sabiendo donde encontrarlo cuando lo buscas.
  • Entity Framework Core. El Framework para acceso a datos estándar de .NET Core. Conocido por tod@s, seguramente.

Dependiendo de tus necesidades utilizarás alguna más. En mi caso por ejemplo de momento Razor Pages para la interfaz de usuario. Jason Taylor implementa algunas en sus proyectos de ejemplo.

¿Por dónde empezar?

Buena pregunta. Yo cuento qué orden seguí más o menos, y cada uno ya veréis si os vale o no.

  1. Presentación de Jason Taylor en 2018. Clean Architecture with ASP.NET Core 2.1
  2. Otra presentación posterior del bueno de Jason en 2019. Clean Architecture with ASP.NET Core 3.0
  3.  Descargarse los proyectos Clean Architecture (casi esqueleto sólo, pero más nuevo) y Northwind Traders del GitHub de Jason y echarles un vistazo con visto en los vídeos.
  4. El típico Getting Started.
  5. Encontré muy útil también este artículo. El Getting Started desde alguien que no sea el propio Taylor.
  6. Y por último, este artículo, si como nosotros quieres añadir validación por roles (la autenticación la hacemos via Active Directory).

¡Ánimo! Si tienes cualquier consulta puedes dejar un comentario y lo intentaré contestar con la mayor brevedad.

Información adicional

Quizás pueda ser de tu interés alguno de los artículos que he escrito posteriormente relacionados con este post:

Añadir Servicio de Windows en Clean Architecture con .NET Core

Clean Architecture con .NET Core, Blazor y Windows Authentication

Clean Architecture con Blazor

11 thoughts on “Clean Architecture con .NET Core

  • Añadir Servicio de Windows en Clean Architecture con .NET Core - Victor Gomez de Juan
    18 de agosto de 2020 at 15:18

    […] Clean Architecture con .NET Core […]

  • Crear suscripción de Reporting Services basada en datos con SQL Server Standard - Victor Gomez de Juan
    8 de septiembre de 2020 at 16:15

    […] Añadir Servicio de Windows en Clean Architecture con .NET Core – Victor Gomez de Juan en Clean Architecture con .NET Core […]

  • Clean Architecture con .NET Core, Blazor y Windows Authentication - Victor Gomez de Juan
    14 de octubre de 2020 at 17:32

    […] Crear suscripción de Reporting Services basada en datos con SQL Server Standard – Victor Gomez de Juan en Clean Architecture con .NET Core […]

  • Eduardo
    23 de diciembre de 2020 at 09:54

    Hola,
    Estoy tratando de implementar una arquitectura similar, solo que para el UI quiero utilizar MVC ya que no tengo experiencia con otro framework.
    Quisiera preguntarte si para la autenticación recomiendas utilizar aspnet identity o sería mejor implementar mi propia autenticación.

    ¿Tendrás algún link de ejemplo?

    Saludos y muchas gracias.

  • victorgomezdejuan
    23 de diciembre de 2020 at 14:57

    Hola Eduardo,

    MVC es, probablemente, la opción más utilizada en .NET, lo entiendo perfectamente. No vas a tener problema en utilizarlo para la capa de presentación. Con respecto a la autenticación, nosotros utilizamos la integración con Active Directory ya que los usuarios utilizan siempre estas credenciales para acceder a cualquier sistema de la empresa. Si no tienes este requerimiento, ASP .NET Identity puede ser una buena opción. Es la opción estándar de Microsoft y seguramente te encuentres muchas facilidades para integrarla con el resto del proyecto.

    Yo no la he utilizado con un proyecto de Clean Architecture, así que no tengo un ejemplo propio para darte, pero, según he comprobado, en los dos proyectos de GitHub de Jason utilizan esta tecnología. El link lo tienes en el artículo también, pero sería éste:

    https://github.com/jasontaylordev

    Un saludo y mucha suerte con tu proyecto!!

  • Ariel
    8 de febrero de 2021 at 11:28

    Hola:
    Se agradece mucho el post y todo los links para arrancar. Estoy intentando una solución similar pero con Blazor server y si bien en el template el consumo de las queries y comandos ocurre en los Controllers, no me queda claro donde ir´´ia esta interacción con Blazor (ya que todo pasa en los components directamente). Tendrías algún insight sobre esto o algún link que creas útil?
    Muchas gracias.

    • victorgomezdejuan
      9 de febrero de 2021 at 09:45

      Hola Ariel,

      Buena pregunta 🙂

      Nosotros tenemos también un proyecto de Blazor para el acceso via móvil (a través del navegador web). En realidad, en un proyecto Blazor la arquitectura se simplica más que en un proyecto MVC o Razor Pages. En Blazor toda la lógica de presentación va en los propios ficheros .razor (no tiene porque ser así, pero es una de sus ventajas). De manera que, al más puro estilo ASP Classic, haces las llamadas a los comandos y las queries desde el propio fichero .razor.

      Por ejemplo, nosotros en una pantalla tenemos este trozo de código (en el fichero .razor de esa pantalla).

      De manera que, cuando el usuario hace click sobre el botón, Blazor llama a la función Create(), contenida en el mismo fichero .razor, que crea un nuevo elemento en la base de datos haciendo una llamada al comando MediatR correspondiente.

      Espero haber contestado a tu duda.

      Por si es de tu interés, también escribí un post para utilizar Windows Authentication con Blazor.
      https://www.victorgomezdejuan.com/2020/10/14/clean-architecture-con-net-core-blazor-y-windows-authentication/

      ¡Ánimo con tu proyecto! 😉

  • Freddy Alvarado R
    5 de marzo de 2021 at 18:17

    Hola Victor,
    Buen artículo, hoy en día estoy lidiando mucho con todos estos temas de arquitectura limpia y tratando de montar una buena solución para .net 5. Tengo una duda, bueno son varias en realidad, pero puntualmente cómo podemos hacer referencia a objetos del dominio desde la capa de infraestructura, sin acceder directamente a esta? Por ejemplo cómo lo presenta Jason cuando realiza una configuración de DbSet para EFCore, usa los objetos entidad de la capa domino, pero no tiene una referencia al proyecto, si yo intento lo mismo me da un error.
    Aquí esta la parte a la que me refiero: https://github.com/jasontaylordev/CleanArchitecture/tree/d0f133ee026aec5cd5856c5592c307b5f20fa8e4/src/Infrastructure

    Saludos

    • victorgomezdejuan
      6 de marzo de 2021 at 12:26

      Hola Freddy,

      Muchas gracias. Creo que tuve, si no el mismo problema, sí uno parecido al principio.

      Efectivamente, el proyecto de Infraestructura sólo añade como referencia el proyecto Application. Pero este proyecto Application tiene, por un lado, la interfaz IApplicationDbContext, que contiene propiedades de tipo DbSet<> que hacen referencia a los objetos de capa de Domain, y por otro, el mismo proyecto Application tiene una referencia sobre el proyecto Domain, el cuál contiene las clases con los objetos de dominio específicándolas como «public class», y, por tanto, accesibles desde proyectos externos.

      De hecho, en este enfoque no se quiere que la capa de Infraestructura no acceda a la de Dominio. Lo que se trata es de no generar dependencias innecesarias y muchos menos circulares. Así, lo que se busca es que la capa de Dominio no dependa de ninguna otra capa. Si bien la de Infraestructura puede hacer uso de los objetos de dominio sin problema, ya que la capa de Aplicación le proporciona acceso a éstos.

      Si te descargas la solución de Jason y la abres con Visual Studio, ¿te ocurre lo mismo?

      Quizás debería ver tu proyecto concreto para poder encontrar el problema. Se me ocurre que quizás no estás incluyendo los DbSet de las clases de Dominio en la clase ApplicationDbContext de la capa de Infraestructure. Éstos deben estar tanto en la interfaz IApplicationDbContext como en la clase ApplicationDbContext. ¿Puede ser?

      Otra opción es que estés utilizando el parámetro ExcludeAssets en la referencia al proyecto de Aplicación: https://stackoverflow.com/questions/57076146/net-core-project-reference-inheritance

      Si quieres sube un .zip (o .7z) a un servidor público o el proyecto a Github, me lo descargo y le echo un vistazo. Tiene que ser una tontería, seguro. Pero las tonterías son las más difíciles de detectar ;-).

      • Freddy
        15 de marzo de 2021 at 19:07

        Hola Victor.
        Revisando tus consejos y viendo de nuevo el código de Jason, pude lograr ajustar mi código. Estos días he pasado revisando otros temas y me ha quedado una duda. Si queremos usar CQRS para la separación de los comandos y consultas, considero que necesitamos un ente superior a este como lo serían los servicios de aplicación para temas de transacciones, orquestación y demás cosas, siendo así se me ocurre que los controladores llamen a esta capa de servicios y este a su vez los comandos o consultas dependiendo la necesidad, pero temo que eso no se ajuste o quiebre algún principio entre todos estos patrones. Apreciaría cualquier comentario.

        Saludos

        • victorgomezdejuan
          16 de marzo de 2021 at 12:31

          Hola Freddy,

          Yo sigo el patrón CQRS a nivel conceptual. Es decir, dependiendo de si el IRequest de MediatR va a realizar una acción de tipo inserción/modificación/borrado o de consulta desde el punto de vista de la capa que le llama (o desde el punto de vista humano/general/de negocio, es decir, el mío :-)), lo pongo en la carpeta de comandos o de queries.

          Quiero decir, es normal que para una operación de inserción/modificación/borrado tengas que hacer alguna consulta. E incluso una o varias transacciones. Pero un comando que se llama por ejemplo ChangeEmployeeCompanyCommand() para mí va ir dentro de Commands y todas las operaciones necesarias, de consulta o de moficiación, van a ir dentro de este comando, en una o varias transacciones, según sea el caso. No voy a crear un IRequest para cada consulta o update «nuclear». Otra cosa es la reutilziación de código, donde es importante hilar fino para no duplicarlo, sobre todo en casos potencialmente peligrosos/críticos (que apliquen cierta lógica). Para evitar esto, en varios IRequests he creado métodos estáticos que son llamados por el propio IRequest y por otros. Los pongo en el IRequest más lógico para mí. También tengo una clase con métodos estáticos comunes que no suponen un acceso a la capa de datos.

          Otra manera de detectar este tipo de incongruencias que pueden surgir, es a través de tests unitarios, que te podrán indicar que has cambiado el código en un sitio pero no en otro. Pero para mí no sería la manera correcta. Al menos no la única. Primero porque no tienen por qué indicártelo, dependiendo del caso, y segundo porque mantener el código limpio y sin duplicidades debe ser una prioridad. Es una manera mucho más eficaz de prevenir errores y de hacer el proyecto mantenible.

          Si estás en una situación parecida a la nuestra, quieres tener un código estructurado y limpio pero no de museo, sino funcional y ágil. No es fácil encontrar «el punto». Pero siempre se puede encontrar una solución viable.

          Espero que te sirvan estos comentarios. ¡Ánimo!

Deja una respuesta

Your email address will not be published. Required fields are marked *.

*
*
You may use these <abbr title="HyperText Markup Language">HTML</abbr> tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>