¿Alguna vez te has preguntado por qué tu código Go compila perfectamente pero tu arquitectura se deteriora con el tiempo? El compilador de Go previene los ciclos de importación, pero no puede detectar violaciones arquitectónicas más sutiles. Aquí es donde entra GoArchTest, una biblioteca que te permite definir y hacer cumplir reglas arquitectónicas en tus proyectos Go.
En este post, te mostraré cómo usar GoArchTest para mantener tu código Go organizado y arquitectónicamente correcto, especialmente cuando trabajas con patrones como Clean Architecture y Domain-Driven Design.
Esta es la pregunta del millón. Aunque Go previene esto:
paquete_a.go// ❌ Go compiler ERROR: import cycle package A import "B" // A → B package B import "A" // B → A (ERROR: import cycle)
Go SÍ permite esto (pero viola Clean Architecture):
domain.go// ✅ Go compiler: Compila bien // ❌ Clean Architecture: VIOLACIÓN package domain import "infrastructure" // Capa interna dependiendo de capa externa package infrastructure import "domain" // Esto sí está correcto en Clean Architecture
GoArchTest llena este vacío detectando violaciones arquitectónicas que Go no puede verificar.
Primero, instala GoArchTest en tu proyecto:
~go get github.com/solrac97gr/goarchtest
Empecemos con un ejemplo sencillo donde queremos asegurar que la capa de presentación no dependa directamente de la capa de datos:
architecture_test.gopackage main_test import ( "testing" "path/filepath" "github.com/solrac97gr/goarchtest" ) func TestArchitecture(t *testing.T) { // Obtener la ruta del proyecto projectPath, _ := filepath.Abs("./") // Crear instancia de GoArchTest types := goarchtest.InPath(projectPath) // Regla: presentación no debe depender de datos result := types. That(). ResideInNamespace("presentation"). ShouldNot(). HaveDependencyOn("data"). GetResult() if !result.IsSuccessful { t.Error("❌ Violación: La capa de presentación depende de datos") for _, failingType := range result.FailingTypes { t.Logf("Violación en: %s (%s)", failingType.Name, failingType.Package) } } }
GoArchTest incluye patrones predefinidos para arquitecturas comunes. Aquí tienes cómo validar Clean Architecture:
clean_arch_test.gofunc TestCleanArchitecture(t *testing.T) { projectPath, _ := filepath.Abs("./") types := goarchtest.InPath(projectPath) // Definir el patrón de Clean Architecture cleanArchPattern := goarchtest.CleanArchitecture( "domain", // Capa de dominio "application", // Capa de aplicación "infrastructure", // Capa de infraestructura "presentation", // Capa de presentación ) // Validar todas las reglas validationResults := cleanArchPattern.Validate(types) // Verificar resultados passedRules := 0 for i, result := range validationResults { if result.IsSuccessful { passedRules++ t.Logf("✅ Regla #%d: EXITOSA", i+1) } else { t.Errorf("❌ Regla #%d: FALLA", i+1) for _, failingType := range result.FailingTypes { t.Logf(" Violación: %s (%s)", failingType.Name, failingType.Package) } } } t.Logf("Resumen: %d/%d reglas pasaron", passedRules, len(validationResults)) }
Para proyectos más complejos que usan DDD, GoArchTest soporta múltiples contextos delimitados (bounded contexts):
ddd_test.gofunc TestDDDArchitecture(t *testing.T) { projectPath, _ := filepath.Abs("./") types := goarchtest.InPath(projectPath) // Definir dominios (bounded contexts) domains := []string{"user", "products", "orders"} // Patrón DDD con Clean Architecture dddPattern := goarchtest.DDDWithCleanArchitecture( domains, // Lista de dominios "internal/shared", // Kernel compartido "pkg", // Utilidades ) validationResults := dddPattern.Validate(types) // Verificar aislamiento entre dominios for i, result := range validationResults { if !result.IsSuccessful { t.Errorf("❌ Regla DDD #%d falla", i+1) for _, failingType := range result.FailingTypes { t.Logf("Violación: %s (%s)", failingType.Name, failingType.Package) } } } }
Estructura de proyecto DDD típica:
Puedes crear reglas específicas para tu proyecto usando predicados personalizados:
custom_rules_test.gofunc TestCustomRules(t *testing.T) { projectPath, _ := filepath.Abs("./") types := goarchtest.InPath(projectPath) // Regla personalizada: Servicios deben terminar en "Service" isServiceImplementation := func(typeInfo *goarchtest.TypeInfo) bool { return typeInfo.IsStruct && len(typeInfo.Name) > 7 && typeInfo.Name[len(typeInfo.Name)-7:] == "Service" } result := types. That(). WithCustomPredicate("IsServiceImplementation", isServiceImplementation). Should(). ResideInNamespace("application"). GetResult() if !result.IsSuccessful { t.Error("❌ Los servicios deben estar en la capa de aplicación") } }
GoArchTest puede generar gráficos de dependencias para visualizar tu arquitectura:
graph_generation.gofunc GenerateDependencyGraph() { projectPath, _ := filepath.Abs("./") types := goarchtest.InPath(projectPath) // Crear reportero reporter := goarchtest.NewErrorReporter(os.Stderr) // Obtener todos los tipos allTypes := types.That().GetAllTypes() // Generar gráfico DOT err := reporter.SaveDependencyGraph(allTypes, "dependency_graph.dot") if err != nil { log.Printf("Error generando gráfico: %v", err) return } log.Println("Gráfico guardado en: dependency_graph.dot") log.Println("Para generar PNG: dot -Tpng dependency_graph.dot -o dependency_graph.png") }
Para mantener tu arquitectura limpia automáticamente, agrega los tests a tu pipeline:
.github/workflows/architecture.ymlname: Architecture Tests on: [push, pull_request] jobs: architecture: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Go uses: actions/setup-go@v3 with: go-version: 1.21 - name: Run Architecture Tests run: | go test -v ./tests/architecture/ - name: Generate Dependency Graph run: | go run cmd/graph/main.go - name: Upload Architecture Report uses: actions/upload-artifact@v3 with: name: architecture-report path: dependency_graph.png
GoArchTest es una herramienta esencial para cualquier proyecto Go que quiera mantener una arquitectura sólida y escalable. No reemplaza las protecciones del compilador, sino que las complementa con validaciones arquitectónicas que van más allá de lo que Go puede verificar.
Al implementar GoArchTest en tu proyecto, lograrás:
¿Ya usas alguna herramienta para validar arquitectura en tus proyectos Go? ¡Cuéntame en los comentarios cómo mantienes tu código organizado!
💡 Tip extra: Combina GoArchTest con herramientas de análisis estático como golangci-lint
para tener una validación completa de tu código Go.
🔗 Enlaces útiles: