Creando diagramas de manera efectiva con el modelo C4

En nuestro día a día empleamos múltiples niveles de abstracción para referirnos a los sistemas de información que construimos, manipulamos o mantenemos, nos referimos a sistemas, aplicaciones, servicios, APIs, llamadas, funciones, almacenamiento, memoria, ficheros, eventos, etc.

Muchas de estas abstracciones las comentamos de manera informal, las describimos a través del código que escribimos y que ejecutamos, las describimos en prosa, o empleamos la herramienta favorita de los arquitectos y la más temida por los desarrolladores, los diagramas.

Un diagrama, ya sea de componentes, de secuencia, de interacción o de clases, no es más que otra abstracción en la que describimos como es o cómo pretendemos que sea nuestro sistema. El diagrama nos puede permitir pasar por ciertas capas de descubrimiento, descubrir o fijar los límites de nuestro servicio en comparación con otros, y es una herramienta que nos permite mantener una conversación.

Esta herramienta es crítica cuando tenemos que interactuar con otros equipos, o compañeros que pueden no tener todo el contexto. Es por ello que es importante tener en cuenta el público, el objetivo, y el nivel de abstracción que queremos mantener. al crear dichos diagramas y representar contextos

Una metodología para la representación de diagramas que he estado poniendo en práctica recientemente es el modelo C4 por Simon Brown, en el cual, siguiendo ciertas prácticas e iterando sobre nuestros diagramas, podemos diseñar nuestro sistema utilizando varios niveles de abstracción:

  • Contexto
  • Contenedor
  • Componente
  • Código

Pese a que el modelo define 4 capas, en este artículo hablaremos de las tres capas superiores, ya que la cuarta se asemeja bastante a diagramas a los que podamos estar más familiarizados, y en ocasiones no es necesario ni siquiera llegar a ellas.

El ejemplo

A la hora de escribir el artículo pensé en algo del estilo “lista de tareas” dada mi obsesión por las aplicaciones, metodologías y otros, pero al final me decanté por un sistema que me permitiera escribir artículos en un blog.

Los requisitos son los siguientes:

  • Un lector puede ver artículos publicados y escribir comentarios.
  • Un autor puede escribir artículos, editarlos y borrarlos
  • Un autor, además, recibirá una notificación cuando se cree un nuevo comentario.

Vayamos manos a la obra:

Nivel 0: Contexto

tbe-blog-page-3

A este nivel queremos definir de manera clara quienes son los actores que interactúan con nuestro sistema, qué sistemas tenemos, y una primera idea de cómo se relacionan entre ellos.

A este nivel podemos tener una conversación con un potencial usuario del sistema, o con nuestros compañeros de negocio, ya que los casos de uso generales se definen aquí.

Es también una oportunidad para destacar qué queda fuera del sistema, y hasta qué punto se pueden modificar esos sistemas. Podemos utilizar esquemas de colores para destacar qué sistemas podemos y cuales no podemos modificar.

Nivel 1: Contenedores

Tbe Blog-Page-4

En este caso hacemos zoom en cada uno de los sistemas, y lo separamos en contenedores que tienen una función lógica. En el caso de nuestro blog tenemos dos subsistemas diferentes, uno encargado de la edición de artículos y otro de la visualización, así como una API entre la que interactúan ambos sistemas.

A este nivel podemos tener conversaciones a nivel técnico con diferentes equipos involucrados en el proyecto, el nivel de abstracción es menor, y podemos tomar decisiones como cuantos subsistemas construimos y cómo se reparte el trabajo de los mismos.

Nivel 2: Componentes

Tbe Blog-Page-5

Este nivel es el más profundo al que llegaremos en este artículo, aquí podemos establecer una separación más clara, en el caso de la UI podemos separar las diferentes páginas que existirán, y en el caso de la API backend los diferentes componentes que podemos emplear, en el que cada componente puede ser una librería, un paquete, un proyecto o una unidad semántica de código.

En este nivel podemos tomar decisiones de arquitectura directamente a nivel de equipo, entender las diferentes relaciones entre los diferentes componentes y modularizar más, si fuese necesario.

Evolución

Una de las características más interesantes de este estilo de modelado es que es iterativo, una vez que empiezas y vas pasando por las diferentes capas descubres que estabas empleando el modelo erróneo de abstracción, y mueves “cajitas” entre las diferentes capas.

El primer diagrama que hagamos nunca será perfecto, pero el hecho de tener tres diagramas diferentes e iterar entre ellos nos permitirá descubrir nuestra arquitectura, encontrar límites y tener una mejor idea del alcance de los productos que vamos a construir.

Desde el punto de vista práctico, recomiendo utilizar una herramienta que nos permita tener los tres tipos de diagramas visibles a la vez, ya que nos veremos en la situación de estar cambiando componentes entre los diferentes niveles de abstracción.

Retrospección

Podemos utilizar estos diagramas no solamente como una manera de crear software, sino también de familiarizarnos con software existente. A la hora de enfrentarnos a un nuevo proyecto, entender el sistema en el que nos encontramos nos permite mitigar el factor ”Pulpo en garaje” y ser más productivos desde el principio.

Conclusiones

En general los diagramas son otra herramienta que podemos utilizar tanto para descubrir, explorar o enterarnos de donde nos hemos metido, no son el fin, ni mucho menos, sino un medio para construir mejores sistemas. Metodologías como C4 nos permiten crear mejores diagramas y con ello, mejores sistemas.

Más información sobre el modelo C4 en la web de Simon Brown: The C4 model for software architecture

Imagen por Kaleidico vía Unsplash

Principios SOLID e inyección de dependencias

El pasado jueves 27 tuve la oportunidad de asistir a la reunión mensual del grupo de usuarios .Net de Madrid (Mad.nug) y la conversación estuvo enfocada a la arquitectura de software, inyección de dependencias, inversión de control y algo de principios SOLID, algo de lo que era bastante ajeno.

Este tema de conversación me pareció bastante interesante y he estado indagando, lo que me ha llevado a escribir a modo de artículo, algunas ideas que he adquirido de estos temas.

En este artículo se verá un resumen los principios SOLID, luego se hablará de inyección de dependencias, y finalmente un poco de Unity, un framework de inyección de dependencias creado por el equipo de Microsoft Patterns and Practices.

Principios SOLID

Son principios de diseño básicos para poder crear aplicaciones robustas y sostenibles, creados por Robert C.Martin, y es, como define Hadi Hariri en el vídeo que se enlaza al final del artículo, un acrónimo de acrónimos, es decir, una combinación de varios principios ya existentes.

  • S-RP: Single Responsability Principle: Especifica que una clase (o un método) solamente tiene que tener un propósito concreto, es decir, tener una única responsabilidad o un motivo por el que cambiar. Esto se reduce en clases más pequeñas y más fáciles de entender.
  • O-CP: Open Closed Principle: Se basa en delegar la responsabilidad a la clase. Si existen una serie de acciones que dependen del subtipo de una clase, es más fácil que esa funcionalidad esté implítica en la clase, y que las subclases las reimplementen (o las especifiquen) mediante polimorfismo, una de las características más comunes de los lenguajes orientados a objetos. Esto se resume en que una clase debe estar abierta a extensión y cerrada a cambios.
  • L-SP: Liskov Susbtitution Principle: Explica que todas las clases que hereden de una superclase, por decirlo así, no deben replicar funcionalidad ya implementada en esta clase, con lo cual la clase base se deberá poder sustituir por las subclases en cualquier región del código.
  • I-SP: Interface Segregation Principle: Propone dividir las interfaces en algo que tenga más sentido, en vez de dejar operaciones de la interfaz por implementar, en cierta manera sería una aplicación del SRP a las interfaces, teneniendo en cuenta que una clase puede implementar varias interfaces de manera simultánea. No se debe forzar a clientes a implementar métodos no necesarios.
  • D-IP: Dependency Inversion Principle: Permite conseguir un desacoplamiento de las clases en el código, de tal manera que si una clase emplea otras clases, la inicialización de los objetos venga dada desde fuera. Además, si se puede sustituir las clases por interfaces, se puede extender a otro objeto que implemente la interfaz, sin variar el parámetro.

Estos cinco patrones componen SOLID, pero hay más, está la ley de Demeter, los los principios DRY… varias buenas prácticas para desarrollar buen software que van bastante más allá del tema de este artículo. Otro detalle importante es que son patrones, no metodologías, se pueden seguir o no, aunque las razones para seguirlos son bastante interesantes.

Inversión de dependencias e inyección de dependencias.

La inversión de dependencias, como se ha dicho en el apartado anterior, lleva implícita la inyección de dependencias. Para explicar este concepto, se puede mostrar el siguiente ejemplo, partiendo de este código inicial.

class MiClase
{
    public MiClase()
    {
        ClaseA a = new ClaseA();
        ClaseB b = new ClaseB();

        a.UnMetodo();
        b.OtroMetodo();
    }
}

Este código tiene un problema de acoplamiento, ya que la clase depende de otras dos clases. Si cambiase el comportamiento de las clases ClaseA o ClaseB, habría que cambiar el código de esta clase, no digamos si los constructores tienen parámetros.

Una primera mejora consistiría en pasar los objetos por argumento, de tal manera que sean definidos fuera de la clase, eliminando la dependencia en esta fase, e inyectándola mediante el constructor, por lo tanto, si el código de estos cambia, no será necesario editar esta clase, pero, nos aseguraremos que el código sigue siendo válido?:

class MiClase
{
    public MiClase(ClaseA a, ClaseB b)
    {
        a.UnMetodo();
        b.OtroMetodo();
    }
}
Una segunda mejora consistiría en sustituir los objetos por interfaces, de tal manera que lo que se pase al constructor de la función no sea una clase, sino un contrato, una implementación que tendrá que cumplir con la interfaz acordada, aunque el contenido de la clase cambie, o sea otra clase completamente diferente que implemente la interfaz:
class MiClase
{
    public MiClase(IClaseA a, IClaseB b)
    {
        a.UnMetodo();
        b.OtroMetodo();
    }
}
De esta manera si se crea una clase OtherClass que implemente IClaseA, se puede pasar por el constructor y el funcionamiento de MiClase no variará, con lo cual no será necesario editar el código. La consecuencia es que esta clase está más desacoplada, lo que permite, por ejemplo, poder pasar objetos de prueba para test unitarios, reducir los fallos y, en definitiva, tener un código más sólido.

Unity, un contenedor IoC

Un detalle del apartado anterior, es que el código no es del todo desacoplado, es decir, estará acoplado a la llamada al constructor o a la función, para terminar de resolver este “pequeño problema” surge Unity, que se define como un contenedor de inyección de dependencias.

El funcionamiento del contenedor permite primeramente registrar los tipos que se usarán, es decir, los objetos se devolverán al solicitar las diferentes interfaces. Una vez se han registrado, para crear los objetos solamente será necesario llamar al contenedor, y él se encargará de resolver la ruta pedida, es decir, emparejar la interfaz que se solicita con el objeto definido en la fase de registro.

Una ventaja de este contenedor es que la configuración se puede realizar vía XML, evitando el hecho de recompilar.

Resumen

Programar no es solamente escribir código, existen estilos y patrones que si los seguimos 1. No hacen daño y 2. Permiten obtener un código legible y mejorar su mantenibilidad. Espero que la lectura te haya resultado interesante, y si quieres más información, te remito a los libros y las charlas de Hadi.

Más información: