Inyección de dependencias
La inyección de dependencias es un patrón que permite a un elemento recibir sus dependencias desde fuentes externas en lugar de crearlas por sí mismo. Eso da más modularidad, facilita las pruebas unitarias y mejora la mantenibilidad del código.
Sword usa este enfoque para gestionar componentes y servicios dentro de la aplicación: las dependencias se inyectan automáticamente cuando se necesitan.
Contenedor de dependencias
La estructura DependencyContainer es el núcleo del patrón en Sword. Actúa como un registro centralizado donde se registran y resuelven las dependencias.
Inyectables
Un inyectable (Injectable) es cualquier estructura que puede usarse como dependencia. El contenedor puede inyectarla automáticamente cuando se solicita.
Proveedores
Un proveedor (Provider) es un inyectable que se instancia y registra manualmente en el contenedor. Suele representar conexiones a servicios externos, como bases de datos o APIs.
Definir un proveedor
Usa el atributo #[injectable(provider)] en la definición de la estructura:
#[injectable(provider)]
pub struct Database {
pool: Arc<PgPool>,
}
impl Database {
pub async fn new(db_conf: DatabaseConfig) -> Self {
let pool = PgPool::connect(&db_conf.uri)
.await
.expect("Failed to create Postgres connection pool");
Self {
pool: Arc::new(pool),
}
}
pub fn get_pool(&self) -> &PgPool {
&self.pool
}
}Registrar un proveedor
Puedes registrar una instancia del proveedor en el contenedor, ya sea dentro de un módulo o de forma global.
En un módulo:
struct SomeModule;
impl Module for SomeModule {
async fn register_providers(
config: &Config,
providers: &ProviderRegistry
) {
let db_config = config.expect::<DatabaseConfig>();
let database = Database::new(db_config)
.await
.expect("Failed to create Database provider");
providers.register(database);
}
}De forma global, si el proveedor no pertenece a un módulo en particular:
let db_provider = Database::new(db_conf)
.await
.expect("Failed to create Database provider");
Application::builder()
.with_module::<SomeModule>()
.with_provider(db_provider)
.build();Componentes
Un componente (Component) es un inyectable que se autoconstruye a partir de dependencias ya registradas en el contenedor. Es ideal para representar partes modulares de la aplicación que dependen de otros servicios o configuraciones.
Definir un componente
Usa el atributo #[injectable] en la definición de la estructura:
Consejo
La sintaxis #[injectable(component)] también es válida, pero no es necesaria. Úsala solo si quieres ser explícito sobre tu intención de definir un componente.
#[injectable]
pub struct TaskRepository {
db: Arc<Database>,
}
impl TaskRepository {
pub async fn find_all(&self) -> Vec<Task> {
sqlx::query_as::<_, Task>("SELECT id, title FROM tasks")
.fetch_all(self.db.get_pool())
.await
.expect("Failed to fetch tasks")
}
pub async fn create(&self, task: Task) {
sqlx::query("INSERT INTO tasks (id, title) VALUES ($1, $2)")
.bind(task.id)
.bind(task.title)
.execute(self.db.get_pool())
.await
.expect("Failed to insert task");
}
}TaskRepository no necesita un constructor específico: el contenedor resuelve cada campo y lo construye automáticamente.
Probablemente notaste que Database está envuelto en un puntero Arc. No es obligatorio, pero es útil para compartir referencias de un mismo componente o proveedor en varios lugares sin clonar de más.
Inyección múltiple
Un componente puede depender de varios proveedores, componentes y estructuras marcadas con el atributo #[config]. En el caso de las configuraciones no hace falta envolverlas en Arc:
#[config(key = "task")]
#[derive(Clone, Deserialize)]
pub struct TaskConfig {
max_tasks: usize,
}
#[injectable]
pub struct TaskService {
repo: Arc<TaskRepository>,
task_conf: TaskConfig,
}
