En este post vamos a ver de forma muy directa que es la regla de la dependencia y muy por encima qué estructura tiene la clean architecture de Robert C. Martin.
La regla de la dependencia es la piedra angular del clean architecture, y en mi opinión debería de serlo para cualquier proyecto de larga duración. Incluso para proyectos pequeños o prototipos, los beneficios que aporta son muchos para el coste que tiene que prácticamente es ninguno, solo seguir unas normas.
Y sí, estoy diciendo que un prototipo debe seguir unas normas mínimas. Al ser un proyecto sobre el que vamos a iterar mucho y muy rápido seguir la regla de la dependencia nos aportará que sabremos en todo momento quien puede hablar con quien, donde encontraremos la lógica X que desarrollamos en la primera iteración, y nos evitará mucho código spaghetti.
El código de un prototipo no tiene que ser perfecto pero nos tiene que permitir correr, y un código caótico nunca ayudará a este fin. Otro motivo para seguir unas normas es que el día de mañana se puede incorporar alguien al prototipo y no queremos que le de una depresión al ver nuestro código y tengamos que invertir horas en explicarle la NO estructura que hemos seguido, dejemos que el código hable por si mismo.
Clean Architecture
Vamos a ver una imagen de lo que sería el Clean Architecture y como sigue la regla de la dependencia.

Básicamente tenemos una estructura por capas de forma que las capas de fuera conocen a las de dentro pero las de dentro no conocen a las de fuera, intentamos proteger a las capas internas de cambios externos.
Frameworks y Drivers
En la capa más externa tenemos la capa de Frameworks y Drivers. Lo que sería los plugins que utilizamos, el framework para hacer peticiones web, la librería para escribir en playerPrefs, incluso el propio Unity y los MonoBehavior estarían en esta capa.
¿Quiere esto decir que no podemos utilizar Unity o estas librerías en las capas internas?
No, para nada. Lo que quiere decir es que para utilizar estas cosas en las capas interiores nos tenemos que proteger del cambio y utilizar abstracciones o interfaces.
¡SOBRE INGENIERÍA! Gritarán algunos cuando escuchen arquitectura por capas e interfaces. Quien crea esto seguramente sea porque no ha tenido la suerte de trabajar en proyecto que cumpla al pie de la letra estas normas, y tenga una arquitectura limpia. La mayoría de proyectos que he visto que utilizan interfaces tienden a abusar y ponerle interfaces a todo sin ton ni son, no hace falta ponerle interfaz a todo, hay que aplicar una lógica.
Esto es el paraíso de la programación, donde puedes estimar y saber exactamente que te va a costar porque eres capaz de pensar cada pieza que vas a tener que crear y sabes lo que cuesta cada componente.
¡Esto es una arquitectura de tiempo constante en desarrollo! Lo que quiere decir que te va a costar lo mismo desarrollar una feature al principio del proyecto que al final, es maravilloso y os animo a probarlo 🙂.
Pero no me creáis a mi, ir al libro de Vaughn Vernon sobre DDD donde explica que con una arquitectura hexagonal (el padre de la clean architecture) y una tabla de tiempos como la de abajo, experimentó con un equipo aplicando los tiempos de la tabla para estimar y con otro equipo estimando de forma «normal» (puntos de scrum, basadas en creencias, etc.). Los resultados fueron que la estimación del primer equipo fue un 20% más acertada, UN VEINTE PORCIENTO, es una barbaridad.
Tipo de componente | Fácil (Horas) | Moderado (Horas) | Complejo (Horas) |
Entidad | 1 | 2 | 4 |
Caso de uso | 0.5 | 1 | 2 |
Presenter | 0.2 | 0.3 | 0.5 |
Pero bueno, no me desvío más del tema, volvamos a la imagen del clean architecture.
Interface Adapters
La segunda capa es la de los interface adapters, es donde creamos los adapters para comunicarnos con la vista, o con el acceso a datos.
En este caso los controllers reciben el input del usuario, esto es cuando un usuario hace click en un botón y es el controller el que reacciona, ¡ojo! Es el que reacciona, no el que se suscribe al botón, eso es cosa de la vista.
Los presenters son los que formatean los datos para presentarlos al usuario. Formatean los datos pero no los asignan en el GameObject, eso nuevamente es la vista quien o hace.
Los Gateways o Repositories son nuestros adapters para acceder a los datos o enviar peticiones web, o cualquier otra conexión con los Frameworks que necesitemos. Recordemos la importancia de estos adapters que ya hablamos en este post.
Dominio
Las otras dos capas más internas son lo que llamamos el dominio, el core de nuestra aplicación, dónde está la lógica de verdad.
La capa más externa sería la lógica de aplicación y la interna sería la capa de empresa. Esto es, la lógica específica de esta app y la lógica que podemos compartir entre todas nuestras apps.
Estas dos capas son las únicas que yo os diría que diluyeseis un poco y la vierais como solo una, como la capa del dominio, eso sí, respetando que los casos de uso y entidades son cosas muy distintas. Si habéis separado bien las capas podréis reciclar toda la parte de empresa cuando hagáis una segunda aplicación.
Use Case y Entity
Un caso de uso es lo que puede hacer el usuario en nuestra app, o lo que la app puede hacer en si. Pero no tiene porque hacerse necesariamente aquí, puede utilizar colaboradores como las entidades para hacer esto.
Las entidades son nuestros modelos de datos, es donde almacenamos la información y tenemos la funcionalidad para transformar estos datos.
Vamos a poner un ejemplo para que se entienda mejor, dañar a un enemigo:

Tendríamos un UseCase de dañar al enemigo que recibiría 2 ids, la del atacante y la del que recibe daño. Este UseCase accedería a las entidades mediante un Repository y estas IDs y haría: entidad.RecibirDaño y le pasaría los datos pertinentes.
Entonces la entidad actualizaría su vida, etc. y generaría unos datos de salida. Es la entidad quien se resta vida y no el caso de uso porque si desde algún otro sitio queremos restar vida lo podemos reciclar. Además será la entidad la que tenga la regla de que la vida no sea negativa, respetamos el encapsulamiento y la regla de tell don’t ask.
public class PerformDamageToHero
{
private readonly IHeroRepository _heroRepository;
private readonly IEventDispatcherService _eventDispatcherService;
public PerformDamageToHero(IEventDispatcherService eventDispatcherService,
IHeroRepository heroRepository)
{
_eventDispatcherService = eventDispatcherService;
_heroRepository = heroRepository;
}
public void PerformDamage(HeroId attackerId, HeroId opponentId)
{
var attacker = _heroRepository.GetHero(attackerId);
var opponent = _heroRepository.GetHero(opponentId);
var damageData = opponent.ReceiveDamageFrom(attacker);
var heroDamagedEvent = new HeroDamagedEvent(attackerId, opponentId,
damageData.Damage,
damageData.CurrentHealth);
_eventDispatcherService.Dispatch(heroDamagedEvent);
}
}
En definitiva, los datos de las entidades sólo los modifican las entidades y punto 🙂.
The Dependency Rule

¿Veis estas flechitas de la izquierda que van desde la capa externa hasta las capas más internas? Pero que lo hacen de uno en uno. ¿Veis que no hay ninguna flecha que va de las internas a las externas?
¡Eso es la regla de la dependencia! Las dependencias SÓLO pueden ir en un sentido. ¿Y cómo nos comunicamos de dentro hacia afuera? Muy fácil, con eventos y con la inversión de control.
Las capas internas pueden publicar un evento en el bus o lo que tengamos para gestionar eventos, y quien este interesado solo tiene que suscribirse.
Si vemos el código de arriba, teníamos un _eventDispatcherService.Dispatch con el que lanzábamos un evento, ahora el receptor solo se tendría que suscribir a este evento y reaccionar.
public class HeroHudPresenter
{
public HeroHudPresenter(IEventDispatcherService eventDispatcherService)
{
eventDispatcherService.Subscribe<HeroDamagedEvent>(UpdateHeroHud);
}
private void UpdateHeroHud(HeroDamagedEvent eventData)
{
// React to HeroDamagedEvent
}
}
La otra opción es utilizar inversión de control, podemos ver como sería el flujo en la imagen de abajo. A nuestro use case que implementa una interfaz de entrada, le podemos inyectar una interfaz de salida que serán los interesados los que la implementen. Entonces el UseCase simplemente llamará al método de esta interfaz con la información de salida.

Os dejo AQUÍ un proyecto de Unity donde podéis ver el mismo ejemplo de arriba resolviéndolo por inversión de control.
Conclusión
Ahora ya conocéis la regla de la dependencia y cómo aplicarla, pero solo es la punta del iceberg. Lo ideal sería que además de seguir esta regla utilizáramos assemblies en C#, o packages y módulos en otros lenguajes, para forzar que siempre respetamos la regla.
El caso ideal sería que además de seguir la regla, tuviésemos una arquitectura que lo potenciara y nos hiciera la vida más fácil, como la Clean Architecture o Arquitectura Hexagonal que tanto me gustan.
En futuros posts hablaremos sobre assemblies y como configurarlos en Unity para ayudarnos a respetar la regla de la dependencia.
Como todo, esto se vuelve simple y sencillo cuando más lo practicas, así que ya sabéis, toca practicar 😉.
Como siempre digo, no os quedéis con la duda y no tengáis miedo a dejarnos un comentario con todas vuestras preguntas u opiniones. Y recordar pasaros por nuestro canal de YouTube para encontrar más contenido como este.
Si quieres aprender sobre los principios SOLID y Clean Code no dudes en pasarte por el curso que tenemos preparado aquí.
Fuentes
- Domain-Driven Design Distilled (Vaughn Vernon)
- Clean Architecture (Robert C. Martin)
- Implementing Domain-Driven Design (Vaughn Vernon)
Otros posts
- Cómo aumentar el rendimiento de tu equipo en Unity
- Patrones de diseño – Composite
- Devlog #00 – Empezamos proyecto nuevo
- Patrones de diseño – Abstract Factory
- Patrones de diseño – Object Pool

