Clean Architecture es un estilo de organizar el código en círculos concéntricos donde el centro es lo mas importante.
La regla clave:
Una capa mas interna no conoce ni depende de una más externa. Las dependencias solo apuntan hacia adentro.
Esto significa que el dominio:
MessageRepository) según lo que necesita.| Capa | Qué contiene |
|---|---|
domain/ |
Entidades con comportamiento, reglas de negocio e interfaces que el dominio necesita. |
client/ |
Servidores (HTTP, gRPC), persistencia (memoria, disco, SQLite), inyección de dependencias y cualquier detalle de infraestructura. |
back/
├── main.go # Punto de entrada
├── go.mod # Definición del módulo Go
│
├── domain/ # CAPA INTERNA — lógica de negocio
│ ├── message/
│ │ ├── message.go # Entidad Message + errores + factory
│ │ └── repository.go # Interfaz MessageRepository
│ └── service.go # Casos de uso (orquesta entidad + repo)
│
└── client/ # CAPA EXTERNA — infraestructura
├── persistence/
│ ├── memory/
│ │ └── repository.go # Implementación en memoria (slice)
│ ├── disk/
│ │ └── repository.go # Implementación en archivo JSON
│ └── sqlite/
│ └── repository.go # Implementación en SQLite
└── server/
├── rest/
│ └── handler.go # Servidor HTTP REST
├── grpc/
│ └── server.go # Servidor gRPC (esqueleto)
└── dual/
└── dual.go # Levanta REST + gRPC (esqueleto)
POST /messages {"author": "Ana", "text": "Hola"}
│
▼
┌─────────────────────┐
│ rest.Handler │ Deserializa JSON, llama al servicio
│ handleSend() │
└──────────┬───────────┘
│
▼
┌─────────────────────┐
│ domain.Service │ Llama a message.NewMessage() → valida
│ SendMessage() │ Llama a repo.Save()
└──────────┬───────────┘
│
▼
┌─────────────────────┐
│ memory/disk/sqlite │ Persiste según la implementación
│ Save() │ inyectada
└──────────┬───────────┘
│
▼
┌─────────────────────┐
│ rest.Handler │ Responde HTTP 201 con el Message
│ respondJSON() │ serializado a JSON
└─────────────────────┘
Message
Tiene cuatro campos: ID, Author, Text, Timestamp.
| Campo | Regla |
|---|---|
| ID | Tiene que ser unico |
| Author | No puede estar vacío |
| Text | Tiene cierta longitud |
| Timestamp | Se genera con la hora actual |
type MessageRepository interface {
Save(msg Message) error
GetAll() ([]Message, error)
Delete(id string) error
}
Está definida en el dominio porque es lo que el dominio necesita para funcionar: que alguien le permita guardar, listar y borrar mensajes. No le importa quién ni cómo lo haga.
Coordina los pasos sin meter lógica pesada. SendMessage crea el mensaje con el factory (ahí se valida) y lo guarda. ListMessages y DeleteMessage solo delegan al repo, pero si mañana necesito permisos o paginación, el service es el lugar para agregarlo.
Recibe la interfaz MessageRepository, no una implementación concreta. Eso es lo que permite cambiar de persistor sin tocar una línea del dominio.
Los tres persistors (memory, disk, SQLite) implementan la misma interfaz. Cambiar de uno a otro es cambiar un case en main.go. El dominio no se entera. Las tres implementaciones importan al dominio; el dominio no importa a ninguna de ellas.
El handler REST traduce HTTP ↔ dominio. No tiene lógica de negocio: no valida campos (eso lo hace la entidad), no decide cómo guardar (eso lo hace el repo). Solo mapea errores de dominio a códigos HTTP (400, 404, 500).
chmod +x run.sh
./run.sh