Arquitectura de Software y matices en el mundo dev
La Arquitectura de Software es una disciplina que involucra la organización y el diseño estructural de un sistema de software. Abarca la definición de componentes, interfaces, estándares de comunicación y decisiones de alto nivel que afectan la funcionalidad, el rendimiento, la escalabilidad, la mantenibilidad y la calidad general del sistema.
Las mejores prácticas en la arquitectura de software incluyen el uso de patrones de diseño, principios de diseño como la separación de preocupaciones, la modularidad y la cohesión, así como la consideración de atributos de calidad como la seguridad, la facilidad de uso y el rendimiento.
La Arquitectura de Software juega un papel clave en el desarrollo de software del día a día, proporcionando una guía estructural para los desarrolladores y ayudando a garantizar la construcción de sistemas robustos y sostenibles.
Referencias científicas relevantes incluyen al libro Software Architecture in Practice de Len Bass, Paul Clements y Rick Kazman, así como el artículo A Survey of Architectural Approaches for Managing Uncertainty in Software-Intensive Systems de Paris Avgeriou y Uwe Zdun.
Clean Architecture
La Arquitectura Limpia (Clean Architecture) es un concepto de desarrollo de software que se centra en la creación de código modular y mantenible. El marco permite al programador crear una base de código que es fácil de entender, probar y ampliar. Destaca la segregación de la esencia primaria del negocio de la aplicación de detalles de infraestructura.
La arquitectura limpia es esencial para los profesionales que ya trabajan o pretenden ingresar al mercado, como arquitectos de software, analistas de sistemas, diseñadores de sistemas, administradores de software y programadores.
Debido a la naturaleza creciente de los sistemas, es crucial adoptar ciertos patrones para evitar que se vuelvan caóticos. Con esta preocupación en mente, los ingenieros de software sénior compilaron un conjunto integral de pautas necesarias para desarrollar un código que sea sólido y resistente al cambio. Este conjunto de principios es ampliamente conocido en la comunidad de desarrolladores por el acrónimo SOLID.
El patrón para este marco de proyecto busca adherirse a los principios de SOLID: S (Principio de la Responsabilidad Única), I (Segregación de Interfaces) y D (Inversión de Dependencia, que no debe confundirse con Injección de Dependencia), separando la capa de persistencia de datos del resto de la aplicación. Esto da como resultado un desacoplamiento efectivo, lo que permite una mayor flexibilidad y mantenibilidad en el código.
En la figura a continuación, puedes ver la idea de que cada capa de una arquitectura de software tiene la responsabilidad de orquestar su función adecuada. La capa más profunda, la entidad, es un conjunto de reglas y atributos relacionados, y un atributo es básicamente un valor de campo. Hay atributos específicos de la fuente de datos y el significado de los atributos existentes puede ser diferente según cada conjunto de datos.
En este artículo, usaremos TypeORM para Node.js como un mapeador relacional de objetos de base de datos, que se puede usar con TypeScript y JavaScript (ES5, ES6, ES7, ES8), para proporcionar funciones que usan bases de datos.
La capa de entidad de la aplicación
En la capa más interna, es decir, la más abstracta, crearemos una entidad llamada User para representar las reglas de negocio de nuestra entidad, con sus propiedades y atributos. Para ver un ejemplo sobre las reglas relacionadas con la entidad, definamos un método (línea 26) que devuelva la URL del avatar del usuario, verificando si la imagen se guardó en AWS S3 o en el disco (carpeta) en el servidor.
Otra regla de negocio en esta entidad está relacionada con el atributo de contraseña del usuario, el cual no puede ser expuesto (línea 41) en ningún tipo de 'select' de base de datos.
Como esta clase solo tiene la responsabilidad de cuidar al Usuario, respeta el primer principio de SOLID (S), que dice que un módulo debe ser responsable de un solo actor.
src/modules/users/infrastructure/typeorm/entities/User.ts
La capa de dominio de la aplicación
Más allá tenemos la capa de dominio, donde almacenamos la lógica de la aplicación, es decir, las reglas de negocio de la solución o de la empresa, también llamados casos de uso por algunos. Los objetos de esta capa contienen la información y la lógica para manipular estos datos, que son específicos del propio dominio y son independientes de los procesos de negocio que desencadenan esta lógica, además de ser completamente ajenos a la capa de aplicación.
Los cambios no deberían afectar a las entidades y objetos que orquestan el flujo de datos de entrada y salida de estas entidades, dirigiéndolos a usar sus conjuntos de reglas comerciales para lograr los roles que desempeñaba ese módulo.
Primero, creemos una interfaz que se asigne al objeto de entidad User.
src/modules/users/interfaces/objects/IUserObject.ts
Entonces, vamos a crear una interfaz que mapee el objeto de nuestra capa de dominio, es decir, de nuestro caso de uso, donde están nuestras reglas relacionadas con el negocio de la aplicación.
src/modules/users/interfaces/classes/IUserRepository.ts
Finalmente, creamos nuestro caso de uso donde almacenaremos nuestras reglas relacionadas con el negocio de la aplicación, es decir, la capa de dominio. No esperamos que los cambios en esta capa afecten a las entidades. Tampoco esperamos que esta capa se vea afectada por cambios en las externalidades, como la base de datos, la interfaz de usuario o cualquiera de los marcos comunes.
Esta capa está aislada de tales preocupaciones, como se indicó anteriormente. Sin embargo, esperamos que los cambios en la orquestación de aplicaciones afecten los casos de uso. Si los detalles de un caso de uso cambian, se verá afectada una parte del código de esta capa.
src/modules/users/infrastructure/typeorm/repositories/UsersRepository.ts
Ten en cuenta que todos los métodos implementados en este caso de uso, es decir, en esta capa de dominio, relacionados con las reglas de negocio de la aplicación, son una extensión de la interfaz creada en el archivo anterior.
El siguiente paso es implementar un servicio que llame a los métodos que se comunicarán con la base de datos y manipularán efectivamente los datos. Aquí ejecutamos el Principio de Inversión de Dependencia SOLID. La dependencia se inyecta en la clase que la usa, es decir, debe ser externa a esa clase. Considera la siguiente figura:
Inversión de Dependência
La siguiente clase se inyectará respetando el principio de inversión de dependencia para que pueda ser llamada por la capa más externa, es decir, la capa de adaptadores de interfaz.
src/modules/users/services/CreateUserService.ts
Es en esta implementación donde podemos percibir la inversión de dependencia, siendo esta clase la encargada de acceder a las operaciones de la capa de dominio, la que tiene las operaciones con la base de datos. Observa que antes de crear un usuario (línea 28), verificamos si el usuario ya existe (línea 23).
Estos métodos se implementan en su propia capa de dominio, observando el Principio Abierto/Cerrado de SOLID, que dice que las clases, métodos, módulos, entre otros, deben estar abiertos para la extensión, pero cerrados para la modificación.
La capa de presentación de la lógica
El siguiente paso para que nuestro modelo de arquitectura limpia o de arquitectura enriquecida esté casi completo en concepto es implementar la capa de adaptadores de interfaz, o la capa de presentación lógica. La aplicación en esta capa es un conjunto de adaptadores que transforman los datos de un formato más adecuado para casos de uso y entidades a un formato más adecuado para una entidad externa, como la base de datos o la Web. Por ejemplo, es en esta capa donde se contiene toda la estructura de la arquitectura MVC de una interfaz gráfica.
A partir de ahora, podemos usar nuestro controlador para llamar a servicios relacionados con la capa de dominio, donde se encuentran nuestras reglas de negocio, es decir, nuestros casos de uso.
src/modules/users/infrastructure/http/controllers/UserController.ts
Aquí es donde llamamos al método dependiente (heredado) desde la capa de dominio (línea 24) y luego lo ejecutamos (línea 25), respetando el principio de Inversión de Dependencia.
La capa de presentación de la interfaz
La capa de infraestructura de la interfaz lógica de la aplicación es donde se encuentra nuestra interfaz de usuario y se demostrará en el código a continuación, con la llamada de la ruta de ‘store’ (línea 24), responsable de crear/almacenar un nuevo usuario en la base de datos.
src/modules/users/infrastructure/http/routes/users.routes.ts
Esta capa, al ser la más externa, generalmente está compuesta por estructuras y tecnologías como la base de datos y la estructura de la Web. La web, como la base de datos, es "detalle", por lo que mantenemos estos elementos en el lado externo, donde se pueden ocurrir menos daños.
A continuación, puedes comprobar cómo se organizó mi estructura de directorios para esta arquitectura.
Conclusión
Seguir estas pautas básicas no es complicado y te ahorrará muchos problemas en el futuro. Al dividir el software en diferentes capas y adherirse a la regla de dependencia, creará un sistema que es naturalmente comprobable, con todas las ventajas que esto conlleva. Si alguna de las partes externas del sistema se vuelve obsoleta, como la base de datos o el framework web, puede reemplazar estos componentes obsoletos con un mínimo esfuerzo.
Tanto Clean Architecture como los principios de diseño de clases orientado a objetos (SOLID), te proporcionarán un marco de reglas y prácticas seguidas por los mejores ingenieros de software para crear una estructura de sistema consistente y fácil de escalar.
Si deseas ver el código completo de esta aplicación, ingresa a mi repositorio.
¡Éxito!
Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.