Modelo

Architecture diagrams as code using the C4 model - version-controlled, automatically generated, and interactive.

Model proporciona un Go DSL para describir la arquitectura del software siguiendo el modelo C4. Define tu arquitectura en código, modifícala junto con tu software y genera diagramas automáticamente.

¿Por qué diagramas como código?

Los diagramas de arquitectura tradicionales se pudren. Se crean con herramientas gráficas, se desconectan del código base y se quedan obsoletos rápidamente. Model soluciona esto haciendo que la arquitectura sea una parte de primera clase de su repositorio:

  • Versión controlada: Los cambios en la arquitectura son commits git con historial completo
  • Consistente: Estilos y componentes compartidos en todos los diagramas
  • Automatizado: Generar diagramas en CI/CD-sin capturas de pantalla manuales
  • Composible: Importación de componentes de arquitectura entre proyectos a través de paquetes Go
  • Revisable: Los cambios en la arquitectura se revisan como cualquier otro código

El modelo C4

C4 define cuatro niveles de abstracción. El modelo soporta los tres primeros:

NivelLo que muestraElementos de ejemplo
Contexto: Sistema en su entorno, sistemas de software, personas
Contenedor: Aplicaciones dentro de un sistema: Aplicaciones web, API, bases de datos, colas de mensajes
ComponenteMódulos dentro de un contenedorServicios, controladores, repositorios

El cuarto nivel (Código) se refiere a las clases y funciones reales, su IDE ya lo muestra.


Instalación

go install goa.design/model/cmd/mdl@latest
go install goa.design/model/cmd/stz@latest

Requiere Go 1.23+.


Inicio rápido

1. Crear un paquete modelo

// model/model.go
package model

import . "goa.design/model/dsl"

var _ = Design("My System", "A description of the overall design", func() {
    // Define elements
    var System = SoftwareSystem("My System", "Does useful things", func() {
        Tag("primary")
    })
    
    Person("User", "Someone who uses the system", func() {
        Uses(System, "Uses")
        Tag("external")
    })
    
    // Define views
    Views(func() {
        SystemContextView(System, "Context", "Shows the system in context", func() {
            AddAll()
            AutoLayout(RankLeftRight)
        })
        
        Styles(func() {
            ElementStyle("primary", func() {
                Background("#1168bd")
                Color("#ffffff")
            })
            ElementStyle("external", func() {
                Shape(ShapePerson)
                Background("#08427b")
                Color("#ffffff")
            })
        })
    })
})

2. Iniciar el editor

mdl serve ./model -dir gen

Abra http://localhost:8080. Arrastre los elementos para colocarlos y, a continuación, guárdelos. El SVG se escribe en el directorio gen/.

3. Generar diagramas en CI

mdl svg ./model -dir gen --all

Esto ejecuta un navegador sin cabeza para auto-diseñar y guardar todas las vistas como archivos SVG.


Conceptos básicos

Jerarquía de elementos

El modelo sigue la jerarquía de C4. Los elementos se anidan dentro de sus padres:

Design
├── Person (top-level)
├── SoftwareSystem (top-level)
│   └── Container
│       └── Component
└── DeploymentEnvironment
    └── DeploymentNode
        ├── InfrastructureNode
        ├── ContainerInstance
        └── DeploymentNode (nested)

Referencias de elementos

Muchas funciones DSL aceptan referencias a elementos. Se puede hacer referencia a elementos de dos maneras:

Por variable (cuando se tiene una referencia):

var API = Container("API", "Backend service", "Go")
var DB = Container("Database", "Stores data", "PostgreSQL")

// Reference by variable
API.Uses(DB, "Reads from", "SQL")

Por ruta (cuando el elemento está definido en otro lugar):

// Same container, reference sibling by name
Uses("Database", "Reads from", "SQL")

// Different software system, use full path
Uses("Other System/API", "Calls")

// Component in another container
Uses("My System/Other Container/Service", "Invokes")

Las rutas utilizan / como separador. Las rutas relativas funcionan dentro del ámbito (contenedores en el mismo sistema, componentes en el mismo contenedor).

Fusión de elementos

Si define el mismo elemento dos veces (mismo nombre en el mismo nivel), Model los fusiona:

var User = Person("User", "First definition", func() {
    Uses("System A", "Uses")
})

var User2 = Person("User", "Updated description", func() {
    Uses("System B", "Also uses")
})
// Result: One "User" person with "Updated description" and both relationships

Esto permite importar modelos compartidos y ampliarlos con relaciones locales.


Sistemas de software

Elemento de nivel superior que representa un sistema de software desplegable:

var ECommerce = SoftwareSystem("E-Commerce", "Online shopping platform", func() {
    // Mark as external to the enterprise
    External()
    
    // Documentation link
    URL("https://docs.example.com/ecommerce")
    
    // Custom metadata
    Prop("owner", "Platform Team")
    Prop("tier", "1")
    
    // Tags for styling
    Tag("critical", "public-facing")
})

Contenedores

Aplicaciones, servicios o almacenes de datos dentro de un sistema de software:

var _ = SoftwareSystem("E-Commerce", "Online store", func() {
    var WebApp = Container("Web Application", "Customer UI", "React", func() {
        Tag("frontend")
    })
    
    var API = Container("API", "Backend REST API", "Go/Goa", func() {
        Tag("backend")
        URL("https://api.example.com")
    })
    
    var Database = Container("Database", "Order and product data", "PostgreSQL", func() {
        Tag("database")
    })
    
    var Queue = Container("Message Queue", "Async processing", "RabbitMQ", func() {
        Tag("infrastructure")
    })
    
    // Relationships between containers
    WebApp.Uses(API, "Makes requests to", "HTTPS/JSON")
    API.Uses(Database, "Reads/writes", "SQL")
    API.Uses(Queue, "Publishes events to", "AMQP")
})

Con Goa, puede utilizar una definición de servicio directamente:

// Goa service definition
var OrdersService = Service("orders", func() { /* ... */ })

// Use it as a container
var _ = SoftwareSystem("E-Commerce", func() {
    Container(OrdersService, func() {
        // Name, description, and "Go and Goa v3" technology come from the service
        Tag("api")
    })
})

Componentes

Módulos dentro de un contenedor. Utilícelos para documentación técnica detallada:

var _ = Container("API", "Backend API", "Go", func() {
    var OrderService = Component("Order Service", "Handles order lifecycle", "Go package")
    var PaymentService = Component("Payment Service", "Processes payments", "Go package")
    var NotificationService = Component("Notification Service", "Sends emails/SMS", "Go package")
    
    OrderService.Uses(PaymentService, "Calls for payment")
    OrderService.Uses(NotificationService, "Triggers confirmations")
    PaymentService.Uses("Stripe/API", "Processes cards via", "HTTPS")
})

Personas

Usuarios, actores o roles que interactúan con los sistemas:

var Customer = Person("Customer", "A customer placing orders", func() {
    External()  // Outside the enterprise
    Tag("user")
    Uses("E-Commerce", "Places orders using", "HTTPS")
})

var Admin = Person("Administrator", "Manages the platform", func() {
    // Internal by default
    Tag("internal")
    Uses("E-Commerce/Admin Portal", "Manages products via")
})

var Support = Person("Support Agent", "Handles customer issues", func() {
    InteractsWith(Customer, "Helps")  // Person-to-person relationship
    Uses("E-Commerce/Support Tools", "Uses")
})

Relaciones

Tipos

FunciónDeACaso de Uso
UsesCualquier elementoCualquier elementoDependencia general
DeliversSistema/Contenedor/ComponentePersonaSalida a usuarios
InteractsWithPersonaInteracción humana

Sintaxis

Todas las funciones de relación aceptan los mismos argumentos opcionales:

// Minimal
Uses(Target, "description")

// With technology
Uses(Target, "description", "technology")

// With interaction style
Uses(Target, "description", Synchronous)  // or Asynchronous

// With technology and style
Uses(Target, "description", "technology", Synchronous)

// With properties
Uses(Target, "description", "technology", func() {
    Tag("async")
})

Ejemplos

// System uses system
PaymentSystem.Uses(BankAPI, "Processes payments via", "REST/JSON", Synchronous)

// Container uses external system
API.Uses("Stripe/API", "Charges cards", "HTTPS", Asynchronous)

// System delivers to person
NotificationSystem.Delivers(Customer, "Sends order updates to", "Email")

Vistas

Las vistas representan subconjuntos de su modelo con diferentes niveles de detalle.

Vista panorámica del sistema

Muestra todos los sistemas y las personas: el panorama general:

SystemLandscapeView("Landscape", "Enterprise overview", func() {
    Title("Company Systems")
    AddAll()
    AutoLayout(RankTopBottom)
    EnterpriseBoundaryVisible()  // Shows internal vs external
})

Vista de contexto del sistema

Muestra un sistema y sus dependencias directas:

SystemContextView(ECommerce, "Context", "E-Commerce in context", func() {
    AddAll()                      // Add system + all related elements
    Remove(InternalTooling)       // Exclude specific elements
    AutoLayout(RankLeftRight)
    EnterpriseBoundaryVisible()
})

Vista de contenedor

Muestra los contenedores dentro de un sistema:

ContainerView(ECommerce, "Containers", "E-Commerce containers", func() {
    AddAll()
    SystemBoundariesVisible()  // Shows external system boundaries
    AutoLayout(RankTopBottom)
})

Vista de componentes

Muestra los componentes dentro de un contenedor:

ComponentView(API, "Components", "API internals", func() {
    AddAll()
    ContainerBoundariesVisible()  // Shows external container boundaries
    AutoLayout(RankLeftRight)
})

Vista dinámica

Muestra un flujo o escenario específico con interacciones ordenadas:

DynamicView(ECommerce, "OrderFlow", "Order placement flow", func() {
    Title("Customer places an order")
    
    Link(Customer, WebApp, func() {
        Description("1. Submits order")
        Order("1")
    })
    Link(WebApp, API, func() {
        Description("2. Creates order")
        Order("2")
    })
    Link(API, PaymentService, func() {
        Description("3. Processes payment")
        Order("3")
    })
    Link(API, Database, func() {
        Description("4. Saves order")
        Order("4")
    })
    
    AutoLayout(RankLeftRight)
})

El ámbito puede ser Global (cualquier elemento), un sistema de software (sus contenedores) o un contenedor (sus componentes).

Vista de despliegue

Muestra cómo se despliegan los contenedores en la infraestructura:

DeploymentView(Global, "Production", "ProdDeployment", "Production setup", func() {
    AddAll()
    AutoLayout(RankLeftRight)
})

Vista filtrada

Crea una versión filtrada de otra vista basada en etiquetas:

// Base view
SystemContextView(System, "AllContext", "Everything", func() {
    AddAll()
})

// Filtered to show only external elements
FilteredView("AllContext", func() {
    FilterTag("external")
})

// Filtered to exclude infrastructure
FilteredView("AllContext", func() {
    FilterTag("infrastructure")
    Exclude()
})

Manipulación de vistas

Añadir Elementos

FunciónComportamiento
AddAll()Añadir todo en el ámbito
AddDefault() AddDefault() AddDefault() AddDefault() AddDefault() Añadir elementos contextualmente relevantes
Add(element)Añadir elemento específico
AddNeighbors(element) AddNeighbors(element) AddNeighbors(element) AddNeighbors(element) Añadir elemento y sus conexiones directas
AddContainers() AddContainers() AddContainers() AddContainers() AddContainers() Añadir todos los contenedores (ContainerView/ComponentView)
Añadir todos los componentes (ComponentView)
AddInfluencers() AddInfluencers() AddInfluencers() AddInfluencers() AddInfluencers() Añadir contenedores + dependencias externas (ContainerView)

AddAll vs AddDefault: AddAll añade todo lo posible en el ámbito. AddDefault añade lo que es típicamente relevante-para un SystemContextView, que es el sistema más los sistemas directamente relacionados y las personas, pero no los elementos no conectados.

Eliminar elementos

SystemContextView(System, "key", "desc", func() {
    AddAll()
    Remove(InternalTool)              // Remove specific element
    RemoveTagged("internal")          // Remove by tag
    RemoveUnrelated()                 // Remove elements with no relationships
    RemoveUnreachable(MainSystem)     // Remove elements not reachable from MainSystem
})

Gestión de relaciones

// Explicitly add a relationship
Link(Source, Destination, func() {
    Vertices(100, 200, 100, 400)  // Waypoints for line routing
    Routing(RoutingOrthogonal)    // RoutingDirect, RoutingCurved, RoutingOrthogonal
    Position(50)                  // Label position (0-100 along line)
})

// Remove a relationship
Unlink(Source, Destination)
Unlink(Source, Destination, "specific description")  // When multiple relationships exist

// Merge multiple relationships into one (Context/Landscape views only)
CoalesceRelationships(Customer, System, "Interacts with")  // Explicit merge
CoalesceAllRelationships()  // Auto-merge all duplicates

Posicionamiento de elementos

Al añadir elementos, puede especificar coordenadas exactas:

Add(Customer, func() {
    Coord(100, 200)      // X, Y position
    NoRelationship()     // Don't render relationships for this element
})

AutoLayout

AutoLayout posiciona los elementos automáticamente usando algoritmos de diseño gráfico:

AutoLayout(RankTopBottom, func() {
    Implementation(ImplementationDagre)  // or ImplementationGraphviz
    RankSeparation(300)   // Pixels between ranks (default: 300)
    NodeSeparation(600)   // Pixels between nodes in same rank (default: 600)
    EdgeSeparation(200)   // Pixels between edges (default: 200)
    RenderVertices()      // Create waypoints on edges
})

Direcciones de rango: RankTopBottom, RankBottomTop, RankLeftRight, RankRightLeft


Estilos

Los estilos se aplican mediante etiquetas. Los elementos y relaciones pueden tener múltiples etiquetas; los estilos se aplican en cascada.

Estilos de elementos

Styles(func() {
    ElementStyle("database", func() {
        Shape(ShapeCylinder)
        Background("#438DD5")
        Color("#ffffff")
        Stroke("#2E6295")
        FontSize(24)
        Border(BorderSolid)  // BorderSolid, BorderDashed, BorderDotted
        Opacity(100)         // 0-100
        Width(450)           // Pixels
        Height(300)          // Pixels
        Icon("https://example.com/db-icon.png")
        ShowMetadata()       // Show technology
        ShowDescription()    // Show description text
    })
})

Estilos disponibles: ShapeBox, ShapeRoundedBox, ShapeCircle, ShapeEllipse, ShapeHexagon, ShapeCylinder, ShapePipe, ShapePerson, ShapeRobot, ShapeFolder, ShapeWebBrowser, ShapeMobileDevicePortrait, ShapeMobileDeviceLandscape, ShapeComponent

Estilos de relación

RelationshipStyle("async", func() {
    Dashed()                    // or Solid()
    Thickness(2)                // Line thickness in pixels
    Color("#707070")
    FontSize(18)
    Width(200)                  // Label width
    Routing(RoutingOrthogonal)  // RoutingDirect, RoutingCurved, RoutingOrthogonal
    Position(50)                // Label position (0-100)
    Opacity(100)
})

Soporte de temas CSS

Los SVG exportados incluyen propiedades personalizadas CSS con valores fallback, lo que permite la compatibilidad con temas claros/oscuros sin regenerar los diagramas. Los colores definidos en ElementStyle y RelationshipStyle se convierten en variables CSS basadas en el nombre de la etiqueta.

Convención de nomenclatura de variables:

  • Fondos de elemento: --mdl-<tag>-bg
  • Colores de texto de elementos: --mdl-<tag>-color
  • Elemento trazos: --mdl-<tag>-stroke
  • Colores de relación: --mdl-rel-<tag>-color

Por ejemplo, esta definición de estilo:

Styles(func() {
    ElementStyle("database", func() {
        Background("#438DD5")
        Color("#ffffff")
    })
    RelationshipStyle("async", func() {
        Color("#707070")
    })
})

Genera elementos SVG con:

<rect fill="var(--mdl-database-bg, #438DD5)" ... />
<text fill="var(--mdl-database-color, #ffffff)" ... />
<path stroke="var(--mdl-rel-async-color, #707070)" ... />

Implementación de temas en su sitio:

/* Light theme (defaults from SVG) */
:root {
  --mdl-database-bg: #438DD5;
  --mdl-database-color: #ffffff;
  --mdl-rel-async-color: #707070;
}

/* Dark theme overrides */
[data-theme="dark"] {
  --mdl-database-bg: #3a7bc8;
  --mdl-database-color: #f0f0f0;
  --mdl-rel-async-color: #9ca3af;
}

Los SVG funcionan de forma independiente (los valores fallback se renderizan correctamente) y se adaptan automáticamente cuando se incrustan en páginas que definen estas variables CSS.

Importante: Las propiedades personalizadas de CSS sólo funcionan cuando el SVG está incrustado en el documento HTML. Los SVG cargados mediante <img src="..."> no heredan CSS de la página principal. Para sitios Hugo, utilice el código corto diagram para insertar SVG:

{{< diagram name="MyDiagram" >}}
{{< diagram name="MyDiagram" caption="System architecture" >}}

Modelado de despliegue

Modelar entornos de infraestructura y tiempo de ejecución:

var _ = Design("System", "desc", func() {
    var System = SoftwareSystem("My System", func() {
        var API = Container("API", "Backend", "Go")
        var DB = Container("Database", "Storage", "PostgreSQL")
    })
    
    DeploymentEnvironment("Production", func() {
        DeploymentNode("AWS", "Amazon Web Services", "Cloud Provider", func() {
            DeploymentNode("us-east-1", "US East Region", "AWS Region", func() {
                
                DeploymentNode("ECS Cluster", "Container orchestration", "AWS ECS", func() {
                    Instances("3")  // Can be number or range like "1..N"
                    
                    ContainerInstance("My System/API", func() {
                        InstanceID(1)
                        HealthCheck("API Health", func() {
                            URL("https://api.example.com/health")
                            Interval(30)      // Seconds
                            Timeout(5000)     // Milliseconds
                            Header("Authorization", "Bearer token")
                        })
                    })
                })
                
                DeploymentNode("RDS", "Managed database", "AWS RDS", func() {
                    ContainerInstance("My System/Database")
                })
                
                InfrastructureNode("ALB", "Load balancer", "AWS ALB", func() {
                    Tag("infrastructure")
                    URL("https://docs.aws.amazon.com/elasticloadbalancing/")
                })
            })
        })
    })
    
    Views(func() {
        DeploymentView(Global, "Production", "ProdDeploy", "Production deployment", func() {
            AddAll()
            AutoLayout(RankLeftRight)
        })
    })
})

La CLI mdl

mdl serve - Editor Interactivo

mdl serve ./model -dir gen -port 8080

Inicia un editor basado en web en http://localhost:8080. Funciones:

  • Arrastre elementos para posicionarlos
  • Múltiples algoritmos de diseño (por capas, forzado, árbol, radial) a través de ELK.js
  • Herramientas de alineación y distribución
  • Deshacer/rehacer (Ctrl+Z / Ctrl+Shift+Z)
  • Exportación SVG
  • Recarga en caliente cuando cambia DSL

Atajos de teclado (use Cmd en Mac):

Acción
Guardar: Ctrl+S
Deshacer Ctrl+Z
Rehacer Ctrl+Shift+Z
Seleccionar todo Ctrl+A
Acercar/Alejar: Ctrl++/- o Ctrl+scroll
Ajustar a la vista: Ctrl+9
Zoom 100% Ctrl+0
Autodisposición Ctrl+L
Ajustar a la cuadrícula Ctrl+G
Ajustar a la cuadrícula Ctrl+Mayús+G
Alineación horizontal Ctrl+Mayús+H
Alinear vertical Ctrl+Mayús+A
Añadir vértice Alt+Click

mdl gen - Exportación JSON

mdl gen ./model -out design.json

Exporta el modelo como JSON. Útil para la integración con otras herramientas.

mdl svg - Generación SVG sin cabeza

# Generate all views
mdl svg ./model -dir gen --all

# Generate specific views
mdl svg ./model -dir gen --view Context --view Containers

# With layout options
mdl svg ./model -dir gen --all --direction LEFT --compact --timeout 30s

Ejecuta un navegador sin cabeza para auto-diseñar y guardar vistas. Ideal para CI/CD pipelines.


La stz CLI

Subir modelos a Structurizr:

# Generate Structurizr workspace JSON
stz gen ./model

# Upload to Structurizr
stz put workspace.json -id WORKSPACE_ID -key API_KEY -secret API_SECRET

# Download from Structurizr
stz get -id WORKSPACE_ID -key API_KEY -secret API_SECRET -out workspace.json

Goa Plugin

Cuando utilice Model con Goa, añada la importación DSL a su paquete de diseño:

package design

import (
    . "goa.design/goa/v3/dsl"
    . "goa.design/model/dsl"
)

// API definition
var _ = API("orders", func() {
    Title("Orders API")
})

var _ = Service("orders", func() {
    Method("create", func() { /* ... */ })
})

// Architecture model
var _ = Design("Orders", "Order management system", func() {
    var OrdersAPI = SoftwareSystem("Orders API", "Manages orders", func() {
        Container(OrdersService)  // Uses Goa service
    })
    
    Person("Customer", "Places orders", func() {
        Uses(OrdersAPI, "Creates orders via", "HTTPS")
    })
    
    Views(func() {
        SystemContextView(OrdersAPI, "Context", "System context", func() {
            AddAll()
            AutoLayout(RankTopBottom)
        })
    })
})

Ejecutar goa gen produce tanto código API como diagramas de arquitectura (design.json y workspace.json).


Integración CI/CD

Acciones GitHub

name: Generate Architecture Diagrams
on: [push]

jobs:
  diagrams:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-go@v5
        with:
          go-version: '1.23'
      
      - name: Install mdl
        run: go install goa.design/model/cmd/mdl@latest
      
      - name: Generate SVGs
        run: mdl svg ./model -dir docs/architecture --all
      
      - name: Commit diagrams
        run: |
          git config user.name github-actions
          git config user.email [email protected]
          git add docs/architecture/
          git diff --cached --quiet || git commit -m "Update architecture diagrams"
          git push

Mejores prácticas

Organizar modelos grandes

Divida los modelos en varios archivos:

// model/systems.go
package model

import . "goa.design/model/dsl"

var ECommerce = SoftwareSystem("E-Commerce", "Online store", func() {
    // containers...
})

var Payments = SoftwareSystem("Payments", "Payment processing", func() {
    // containers...
})
// model/views.go
package model

import . "goa.design/model/dsl"

var _ = Design("Platform", func() {
    Views(func() {
        SystemLandscapeView("Landscape", "All systems", func() {
            AddAll()
        })
        // more views...
    })
})

Etiquetado coherente

Defina una convención de etiquetado y aténgase a ella:

// Element types
Tag("database")
Tag("api")
Tag("frontend")
Tag("queue")

// Visibility
Tag("external")
Tag("internal")

// Criticality
Tag("critical")
Tag("tier-1")

Importar componentes compartidos

// shared/infrastructure.go
package shared

import . "goa.design/model/dsl"

var Stripe = SoftwareSystem("Stripe", "Payment processing", func() {
    External()
    Tag("external", "payments")
})
// myservice/model.go
package model

import (
    . "goa.design/model/dsl"
    _ "mycompany/shared"  // Import shared elements
)

var _ = Design("My Service", func() {
    var Service = SoftwareSystem("My Service", func() {
        Uses("Stripe", "Processes payments via")  // Reference by name
    })
})

Referencia