sábado, 25 de enero de 2014

Inyección de Dependencia en Yii Framework para combatir las Hiper-Dependencias Cíclicas.


Inyección de Dependencia, para qué sirve.


La inyección de dependencia es un término muy usado en patrones de diseño, sobre todo común y conocido en Java pero quizá no tanto en PHP o Yii Framework.

La inyección de dependencia implica que un componente implementará ciertos comportamientos para que pueda servir para proveer solución a otro componente.

Esta frase podría ayudar a aclarar la idea:

"Yo (un modulo, componente o extensión) Necesito saber datos de facturación de un usuario, podrían darme un componente que cumpla mis especificaciones para de él poder leer información ? gracias. (Al componente que se supone servirá este requisito se le inyecta una dependencia al módulo para que pueda servirlo)"

El origen de la necesidad de la Inyección de Dependencia: La Hiper-Dependencia.

Muchas veces nos topamos que hemos fabricado un componente o un programa y en algún momento del diseño nos toca agregarle nuevas funciones.  Es aquí cuando comienza el drama: Las Dependencias.


Pensemos que hemos fabricado un componente que no depende de nadie y que es de fácil uso, como el gráfico a continuación el cual dice que una vista simplemente puede llamar al modulo y pedir una lista de usuarios.


El siguiente gráfico UML ilustra la aplicación antes de que el cliente pidiera los cambios que veremos mas adelante:



Hasta este punto, todo va muy bien, la vista recibe los usuarios y los presenta, pero ahora ocurre un cambio en la lógica de negocios:

//TODO: Necesitamos que el [ModuloAutenticacion] ahora nos de información de Facturación de cada usuario o de uno en específico.

Aquí tu podrías pensar:

"..Qué fácil.  Simplemente le agrego al [ModuloAutenticacion] las funciones de facturación, para qué complicarnos la vida, código aquí...código allá...parche aquí...parche allá..."
Gracias a esta "grandiosa" decisión ha sucedido que hemos sacado al módulo de autenticación de su tarea fundamental: Manejar usuarios, no facturación. 

Multiplica esto por la cantidad de lógica de negocio requerida y en pocas semanas tu ModuloAutenticacion se ha convertido en algo que no tiene nombre, pues hará de todo y muy probablemente será HiperDependiente, ha nacido la hiperdependencia en tu sistema.

He aquí la famosa ley de mi papa Pedro que si bien es de la era pasada aplica muy bien aún ya que ataca el punto de atomización de las funciones.

"..Cuando se imprime..se imprime, cuando se calcula..se calcula."

Solucionando la Hiper-Dependencia

En el caso anterior, nuestro [ModuloAutenticacion] podría estar en peligro de ser infectado con "código fuera de concepto" (funciones de facturación), sin embargo la lógica de negocios nos pide nueva información: "datos de facturación", el [ModuloAutenticacion] deberá ser capaz de darla sin caer en hiper-dependencias.

Qué podría suceder mas adelante: que así como hoy la lógica de negocios pide un resumen de facturación también mas adelante la misma lógica de negocios demande otro resumen de otro tipo, no podemos ni debemos darle semejante cantidad de lógica de negocios a un solo componente cada vez que algo nuevo sale al camino.

La clave de la solución a la hiperdependencia:


Como solución: el [ModuloAutenticacion] pedirá que le demos un componente que él usará para obtener de éste último datos necesarios para proveer un resumen de facturación del usuario que está listando o de uno indicado explícitamente.  Esto como alternativa muy eficiente a en contraste insertar código de facturación en el [ModuloAutenticacion].

Para que esto sea posible es necesario que ese componente que le daremos al módulo cumpla con algo para poder ser usado como proveedor de datos de facturación, aquí nace la inyección de dependencia:  cuando pedimos que el componente a ser dado al modulo cumpla con alguna interfaz para poder usarlo. Evitándose así que el módulo reciba cualquier cosa, provocando errores de programación de costosa detección.

El [ModuloAutenticacion] require que quien le provea las funciones de facturación lo haga de una manera tal que él pueda entender, por tanto entran en uso las interfaces (lee este articulo para entender las interfaces), es decir: 
"..Yo (el modulo de autenticación) solicito que el componente que me des para obtener de él funciones de facturación cumpla con este formato (la interfaz)...si no cumple, el programa se colgará."
El siguiente punto describe la configuración.

Configurando al Módulo para recibir un Componente con inyección de dependencia.

El [ModuloAutenticacion] podrá ser configurado para recibir un componente mediante su archivo de configuración de YiiFramework (en el caso de YiiFramework mediante el archivo config/main.php).

Qué diría este archivo de configuración:

"..hágame el favor y dígale al módulo de autenticación que use este componente que le indico acá, el cual cumple la interfaz requerida.."
En programación, algo así:

// protected/config/main.php
'modules'=>array(
   'ModuloAutenticacion'=> array(
      'billing_component' => 'billing',
    ),
),
// ahora declaramos al componente "billing" que cumple la interfaz y puede ser usado
// por el modulo. 
'components'=> array(
  'billing' => array(
   'class'=>'application.modules.modfact.components.MyBilling', 
  ),
 ),
Cuidado con la hiperdependencia.  El módulo de autenticación no va a acceder al componente de billing usando los métodos propios del sistema de Billing en cambio: El modulo de facturación va a leer los métodos de la interfaz, que pare eso se hizo: para que que el modulo dependa de lo que la interfaz tiene y nada mas. Cómo ha quedado el gráfico UML tras la adición de esta configuración:
Te fijarás que hay un nuevo [ModuloFacturacion] que ha sido creado para manejar todo lo relativo a Facturación, con pantallas independientes, formularios, casos de negocio etc, siendo lo mas importante: ofrece un componente (indicado en config/main por: Yii::app()->billing) que tiene una dependencia inyectada: la interfaz IModAutResumenFacturacion. Esta inyección de dependencia asegura que el componente MyBilling (cuya instancia Yii::app()->billing es la que se usa para acceder a él) tiene los métodos necesarios para que el [ModuloAutenticacion] lo pueda consumir. Tras este agregado el ModuloAutenticacion puediese invocar métodos del componente Yii::app()->billing, como los siguientes:
$identity = Yii::app()->user->id;
$saldo = Yii::app()->billing->saldoDisponible($identity);
$estatus = Yii::app()->billing->estadoCuenta($identity);
Pero cuidado !! solo es para que comprendas y veas claro: copiare de nuevo la nota anterior: El módulo de autenticación (o la vista) no van a acceder al componente de billing directamente usando los métodos propios del sistema de Billing en cambio: El modulo de facturación va a leer los métodos de la interfaz, que pare eso se hizo: para que que el modulo dependa de lo que la interfaz tiene y nada mas.   Por tanto vamos a proveerle a la vista un método para que ella reciba datos de facturación sin caer una ruidosa hiperdependencia (causada cuando tu haces que la vista lea al componente Yii::app()->billing "por facilidad"), no accederás a Yii::app()->billing desde la vista, aunque puedas, en cambio lo harás mediante el módulo lo cual asegura las dependencias sanas.
// vista: protected/views/site/vistausuario.php
$identity = Yii::app()->user->id;
list($saldo, $estatus) = 
   Yii::app()->getModule("ModuloAutenticacion")->getAccountStatus($identity);

printf("buenas, mi saldo es: %s, y estoy: %s",$saldo, $estatus);
Cuidado que dije: "Proveerle a la vista un metodo", y no dije: "que la vista use los métodos de la interfaz". Esto ultimo crearía una indeseada dependencia entre la vista y el modulo de facturación. Por tanto, en el modulo de autenticación creamos:
public function getAccountStatus($identity){
    $component = $this->getSelectedBillingComponent();

   $available = $component->saldoDisponible($identity);
   $status= $component->estadoCuenta($identity);
   return array($available, $status);

}
Si te has dado cuenta hay un metodo:
getSelectedBillingComponent()
dentro del método del modulo que hemos creado para la vista. Recuerda además que tempranamente dijimos que el módulo podría ser configurado para recibir cualquier componente que cumpla la interfaz, esto debe ser así para que el modulo pueda ajustarse a casos de negocio, pudiendo usar distintos componentes para distintas ocasiones. Procedemos finalmente a decirle al modulo que lea el componente de billing que tiene configurado para ser usado:
 public function getSelectedBillingComponent() {
  $id = $this->billing_component;  // configurado en config/main
  if(Yii::app()->hasComponent($id)){
   return Yii::app()->getComponent($id); 
  }else{
   throw new Exception("the required component "
    ."has not been yet included in the configuration: ".$id);
  }
 }
Los métodos hasComponent y getComponent son parte de YiiFramework en CWebApplication. Haz click en los links para leer acerca de ellos. Resumen Finalmente nuestra feliz vista puede saber datos de facturación así:
// vista: protected/views/site/vistausuario.php
$identity = Yii::app()->user->id;
list($saldo, $estatus) = 
   Yii::app()->getModule("ModuloAutenticacion")->getAccountStatus($identity);

printf("buenas, mi saldo es: %s, y estoy: %s",$saldo, $estatus);
Sin caer en hiper-dependencias, gracias a la inyección de dependencia, la cual garantizará que las partes de tu sistema será conceptualmente coherentes ofreciendo aquello que se supone deben hacer y nada mas, delegando en otros componentes las especializaciones y aprovechando el framework para ser configurado para cumplir exigencias de negocios de una forma sana y firme.